ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Android NDK/JIN 从入门到精通

2026/8/27 12:06:59 拓冰建站 浏览量
Android NDK/JIN 从入门到精通 1.1 JNI(Java Native Interface)提供一种Java字节码调用C/C的解决方案JNI描述的是一种技术1.2 NDK(Native Development Kit)Android NDK 是一组允许您将 C 或 C“原生代码”嵌入到 Android 应用中的工具NDK描述的是工具集。 能够在 Android 应用中使用原生代码对于想执行以下一项或多项操作的开发者特别有用在平台之间移植其应用。重复使用现有库或者提供其自己的库供重复使用。在某些情况下提高性能特别是像游戏这种计算密集型应用。1.3 JNI注册1.3.1 环境配置这里已有现成的文章就引用一下别人的了 讲的很仔细 关于JNI环境配置1.3.2 静态注册当Java层调用navtie函数时会在JNI库中根据函数名查找对应的JNI函数。如果没找到会报错。如果找到了则会在native函数与JNI函数之间建立关联关系其实就是保存JNI函数的函数指针。下次再调用native函数就可以直接使用这个函数指针。JNI函数名格式包名里面的”.”需要改为”_”Java_ 包名com_example_auto_jnitest 类名(_MainActivity) 函数名(_stringFromJNI)静态注册缺点要求JNI函数的名字必须遵循JNI规范的命名格式名字冗长容易出错初次调用会根据函数名去搜索JNI中对应的函数会影响执行效率需要编译所有声明了native函数的Java类每个所生成的class文件都要用javah工具生成一个头文件静态注册例子类 JNITest 包名com.hqk.jnitestonepackage com.hqk.jnitestone; public class JNITest { static { System.loadLibrary(native-lib); } public static native String sayHello(); }对应c代码 cpp 类名为native-lib.cpp#include jni.h #include string extern C JNIEXPORT jstring JNICALL Java_com_hqk_jnitestone_JNITest_sayHello(JNIEnv *env, jclass clazz) { std::string hello Hello from C; return env-NewStringUTF(hello.c_str()); }注cpp 类名为native-lib.cpp则对应java交互类 需要加入static { System.loadLibrary(native-lib); }java 对应交互类的包名com.hqk.jnitestone则cpp对应方法需要将对应[.]转化成[_](参照上面格式)1.3.2 动态注册通过提供一个函数映射表注册给JVM虚拟机这样JVM就可以用函数映射表来调用相应的函数就不必通过函数名来查找需要调用的函数。Java与JNI通过JNINativeMethod的结构来建立函数映射表它在jni.h头文件中定义其结构内容如下typedef struct { const char* name; // 对应交互java 类对应的方法名 const char* signature; //对应交互方法的函数签名 参考本文1.4.3 void* fnPtr; //对应交互cpp方法的 指针函数 指向对应函数 } JNINativeMethod;1、创建映射表后调用RegisterNatives函数将映射表注册给JVM; 2、当Java层通过System.loadLibrary加载JNI库时会在库中查JNI_OnLoad函数。可将JNI_OnLoad视为JNI库的入口函数需要在这里完成所有函数映射和动态注册工作及其他一些初始化工作。概念说完了实际操作一下JAVA类 JNITest 包名com.hqk.jnitestonepackage com.hqk.jnitestone; public class JNITest { static { System.loadLibrary(native-lib); } public static native String sayHello(); public static native String sayHello2(); }注意这里多了一个 sayHello2 方法方法返回值为: StringCPP类名 native-lib.cpp 对应代码#include jni.h #include string #include android/log.h extern C JNIEXPORT jstring JNICALL Java_com_hqk_jnitestone_JNITest_sayHello(JNIEnv *env, jclass clazz) { std::string hello Hello from C; return env-NewStringUTF(hello.c_str()); } //cpp 交互方法 jstring sayHello2(JNIEnv *env, jobject thiz) { std::string hello Hello我是动态注册成功的; return env-NewStringUTF(hello.c_str()); } // 动态注册 函数 结构数组 static const JNINativeMethod gMethods[] { {sayHello2, //对应java交互类的方法名 ()Ljava/lang/String;, //对应方法名的函数签名 参考本文1.4.3 (jstring *) sayHello2 //对应 cpp交互类的指针函数 } }; JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { __android_log_print(ANDROID_LOG_INFO, native, Jni_OnLoad); JNIEnv *env NULL; if (vm-GetEnv((void **) env, JNI_VERSION_1_4) ! JNI_OK) //从JavaVM获取JNIEnv一般使用1.4的版本 return -1; //注意这里 FindClass 必须要和交互类的 包名对应上并换成[/]符号 jclass clazz env-FindClass(com/hqk/jnitestone/JNITest); if (!clazz) { __android_log_print(ANDROID_LOG_INFO, native, cannot get class: com/hqk/jnitestone/JNITest); return -1; } if (env-RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) { __android_log_print(ANDROID_LOG_INFO, native, register native method failed!\n); return -1; } return JNI_VERSION_1_4; }从上面代码可以看出1、CPP类里面 创建了 JNI_OnLoad 方法–》 该方法是在 初始化的时候执行类似于 activity 的onCreate 核心代码就是通过Find找到对应Java类最后通过RegisterNatives 实现动态注册 2、CPP类里面 新增了JNINativeMethod 类型的 结构数组 3、CPP类里面 新增了 sayHello2 交互方法 4、Java类里面通用新增了 sayHello2方法1.4 数据类型转换1.4.1 基本数据转换1.4.2 引用数据类型转换除了Class、String、Throwable和基本数据类型的数组外其余所有Java对象的数据类型在JNI中都用jobject表示。Java中的String也是引用类型但是由于使用频率较高所以在JNI中单独创建了一个jstring类型。引用类型不能直接在 Native 层使用需要根据 JNI 函数进行类型的转化后才能使用;多维数组含二维数组都是引用类型需要使用 jobjectArray 类型存取其值例如二维整型数组就是指向一位数组的数组其声明使用方式如下//获得一维数组的类引用即jintArray类型 jclass intArrayClass env-FindClass([I); //构造一个指向jintArray类一维数组的对象数组该对象数组初始大小为length类型为 jsize jobjectArray obejctIntArray env-NewObjectArray(length ,intArrayClass , NULL);1.4.3 JNI函数签名信息由于Java支持函数重载因此仅仅根据函数名是没法找到对应的JNI函数。为了解决这个问题JNI将参数类型和返回值类型作为函数的签名信息。JNI规范定义的函数签名信息格式(参数1类型字符…)返回值类型字符函数签名例子:3.JNI常用的数据类型及对应字符:1.4.4 JNIEnv介绍JNIEnv概念 : JNIEnv是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境。通过JNIEnv可以调用到一系列JNI系统函数。JNIEnv线程相关性 每个线程中都有一个 JNIEnv 指针。JNIEnv只在其所在线程有效, 它不能在线程之间进行传递。注意在C创建的子线程中获取JNIEnv要通过调用JavaVM的AttachCurrentThread函数获得。在子线程退出时要调用JavaVM的DetachCurrentThread函数来释放对应的资源否则会出错。JNIEnv 作用访问Java成员变量和成员方法调用Java构造方法创建Java对象等。1.5 JNI编译1.5.1 Cmake编译CMake 则是一个跨平台的编译工具它并不会直接编译出对象而是根据自定义的语言规则CMakeLists.txt生成 对应 makefile 或 project 文件然后再调用底层的编译 在Android Studio 2.2 之后支持Cmake编译。cmake_minimum_required(VERSION 3.4.1) add_library( native-lib SHARED native-lib.cpp) find_library( log-lib log) target_link_libraries( native-lib ${log-lib})1.5.1.1 add_library 指令语法add_library(libname SHARED | STATIC | MODULE [source])将一组源文件 source 编译出一个库文件并保存为 libname.so (lib 前缀是生成文件时 CMake自动添加上去的)。其中有三种库文件类型不写的话默认为 STATIC;SHARED: 表示动态库可以在(Java)代码中使用 System.loadLibrary(name) 动态调用STATIC: 表示静态库集成到代码中会在编译时调用MODULE: 只有在使用 dyId 的系统有效如果不支持 dyId则被当作 SHARED 对待EXCLUDE_FROM_ALL: 表示这个库不被默认构建除非其他组件依赖或手工构建;#将compress.c 编译成 libcompress.so 的共享库 add_library(compress SHARED compress.c)1.5.1.2 target_link_libraries 指令语法target_link_libraries(target library debug | optimized library2…)这个指令可以用来为 target 添加需要的链接的共享库同样也可以用于为自己编写的共享库添加共享库链接。如#指定 compress 工程需要用到 libjpeg 库和 log 库 target_link_libraries(compress libjpeg ${log-lib})1.5.1.3 find_library 指令语法find_library( name1 path1 path2 …)VAR 变量表示找到的库全路径包含库文件名 。例如find_library(libX X11 /usr/lib) find_library(log-lib log) #路径为空应该是查找系统环境变量路径1.5.1.4 更多详细使用参考文献更多关于Cmake的详细使用1.5.2 Abi架构ABIApplication binary interface应用程序二进制接口。不同的CPU 与指令集的每种组合都有定义的 ABI (应用程序二进制接口)一段程序只有遵循这个接口规范才能在该 CPU 上运行所以同样的程序代码为了兼容多个不同的CPU需要为不同的 ABI 构建不同的库文件。当然对于CPU来说不同的架构并不意味着一定互不兼容。armeabi设备只兼容armeabiarmeabi-v7a设备兼容armeabi-v7a、armeabiarm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabiX86设备兼容X86、armeabiX86_64设备兼容X86_64、X86、armeabimips64设备兼容mips64、mipsmips只兼容mips根据以上的兼容总结我们还可以得到一些规律armeabi的SO文件基本上可以说是万金油它能运行在除了mips和mips64的设备上但在非armeabi设备上运行性能还是有所损耗64位的CPU架构总能向下兼容其对应的32位指令集如x86_64兼容X86arm64-v8a兼容armeabi-v7amips64兼容mips以上就是NDK/JNI的入门内容那么要如何进阶学习呢下面高能的地方来了有幸从一位字节跳动大神那里得到他本人吐血整理的“582页Android NDK七大模块学习宝典”从原理到实战一应俱全秉承好东西的当然要共享的原则今天就来秀一把试试这“582页Android NDK七大模块学习宝典”是否也能让你事半功倍这份宝典主要涉及以下几个方面NDK 模块开发JNI 模块Native 开发工具Linux 编程底层图片处理音视频开发机器学习一、NDK 模块开发主要内容C与 C#数据类型总结C 与 C之内存结构与管理C 与 C之预处理命令与用 typedef 命名已有类型C 与 C之结构体、共用体C 与 C之指针C/C 之多线程机制C/C 之函数与初始化列表二、JNI 模块主要内容JNI 开发之 静态注册与动态注册静态注册、动态注册、JNINativeMethod、数据类型映射、jni 函数默认参数JNI 开发之方法签名与 Java 通信Android NDK 开发 JNI 类型签名和方法签名、JNI 实现 java 与 c/c相互通讯JNI 开发之局部引用、全局引用和弱全局引用三、Native 开发工具主要内容编译器、打包工具与分析器十大最受欢迎的 React Native 应用开发编辑器、react-native 打包流程静态库与动态库CPU 架构与注意事项ABI 管理、处理 CPU 功能、NEON 支持构建脚本与构建工具环境搭建、NDK 项目、Cmake、Makefile交叉编译移植FFmpeg 编译、FFmpegLIBX264FACC 交叉编译 实现 264 流录制、移植 FFmpeg 在 arm 交叉编译时遇到的问题、FFmpeg 交叉编译、X264 FAAC 交叉编译、解决所有移植问题AS 构建 NDK 项目配置 NDK 环境、建立 app 项目、生成.h 头文件、创建 C 文件,实现 native 方法、jni.h 文件四、Linux 编程Linux 环境搭建系统管理权限系统和工具使用vim 等Linux 环境的搭建、Linux 系统管理操作(25 个命令)Shell 脚本编程Shell 脚本、编写简单 Shell 脚本、流程控制语句、计划任务服务程序五、底层图片处理PNG/JPEG/WEBP 图像处理与压缩四种图片格式、推荐几种图片处理网站、squoosh 在线无损图片压缩工具JPG/webP/PNG/ 互转微信图片压缩计算原始宽高、计算近似宽高、第一次采样获取目标图片、循环逼近目标大小GIF 合成原理与实现GIF 图片的解析、GIF 图片的合成序列图像合成 GIF 图像六、音视频开发多媒体系统Camera 与手机屏幕采集、图像原始数据格式 YUV420(NV21 与 YV12 等)、音频采集与播放系统、编解码器 MediaCodec、MediaMuxer 复用与 MediaExtractorFFmpegffmpeg 模块介绍、音视频解码,音视频同步、I 帧,B 帧,P 帧解码原理、x264 视频编码与 faac 音频编码、OpenGL 绘制与 NativeWindow 绘制流媒体协议RTMP 协议、、音视频通话 P2P WebRtcOpenGL ES 滤镜开发之美颜效果高斯模糊、高反差保留、强光处理、融合抖音视频效果分析与实现流程列表、视频拍摄、视频编辑、视频导出音视频变速原理变速入口分析、音频变速实现、视频变速实现七、机器学习Opencv图像预处理灰度化和二值化、腐蚀与膨胀、人脸检测、身份证识别最后由于篇幅限制文档的详解资料太全面细节内容太多所以只把部分知识点截图出来粗略的介绍每个小节点里面都有更细化的内容除了上面的之外我还自己整理了以下一系列的学习进阶资料《Android开发七大模块核心知识笔记》《2246页最新Android大厂高频面试题解析大全》由于篇幅限制这里只能展示部分内容朋友们如果需要这份完整版的PDF资料合集微信扫描下方CSDN官方二维码【免费获取】。