Android JNI开发环境搭建与Hello World实现

📅 2026/7/19 7:24:03 ✍️ 编辑团队 👁️ 阅读次数
Android JNI开发环境搭建与Hello World实现
1. Android JNI开发环境搭建在开始编写Hello World示例之前我们需要先搭建好Android JNI开发环境。Android Studio是目前最主流的Android开发IDE它内置了对NDK和CMake的支持可以大大简化JNI开发流程。1.1 安装必要组件首先确保你已经安装了以下组件Android Studio最新稳定版Android NDK通过SDK Manager安装CMake通过SDK Manager安装在Android Studio中打开SDK Manager切换到SDK Tools标签页勾选以下项目NDK (Side by side)CMakeLLDB用于调试原生代码提示建议选择较新的NDK版本但不要使用预览版以避免兼容性问题。目前稳定版本是NDK r21或r22。1.2 创建支持C的新项目在Android Studio中选择File New New Project选择Native C模板按照向导完成项目创建这个模板会自动配置好CMake和NDK的基本设置为我们生成一个简单的JNI示例代码框架。2. JNI项目结构解析让我们看一下自动生成的项目结构app/ ├── src/ │ ├── main/ │ │ ├── cpp/ # 原生代码目录 │ │ │ └── native-lib.cpp │ │ ├── java/ # Java代码目录 │ │ └── res/ │ └── build.gradle # 模块级构建配置 ├── CMakeLists.txt # CMake构建脚本 └── build.gradle # 项目级构建配置2.1 CMakeLists.txt详解CMakeLists.txt是CMake的构建脚本定义了如何编译原生代码。模板生成的CMakeLists.txt通常包含以下关键内容cmake_minimum_required(VERSION 3.4.1) # 指定CMake最低版本 # 定义库名称、类型和源文件 add_library( native-lib # 库名称 SHARED # 库类型SHARED表示动态库 native-lib.cpp # 源文件 ) # 查找系统库 find_library( log-lib # 变量名 log # 要查找的库名称 ) # 链接库 target_link_libraries( native-lib # 目标库 ${log-lib} # 要链接的库 )2.2 build.gradle配置模块级的build.gradle中包含了与NDK和CMake相关的配置android { defaultConfig { externalNativeBuild { cmake { cppFlags # 可添加C编译选项 // 指定ABI过滤器 abiFilters armeabi-v7a, arm64-v8a, x86, x86_64 } } } externalNativeBuild { cmake { path CMakeLists.txt # 指定CMake脚本路径 version 3.10.2 # 指定CMake版本 } } }3. 实现JNI Hello World现在我们来编写一个简单的JNI Hello World示例。3.1 Java层代码在MainActivity.java中添加native方法声明public class MainActivity extends AppCompatActivity { static { System.loadLibrary(native-lib); // 加载动态库 } // 声明native方法 public native String stringFromJNI(); Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv findViewById(R.id.sample_text); tv.setText(stringFromJNI()); // 调用native方法 } }3.2 C层实现在native-lib.cpp中实现native方法#include jni.h #include string extern C JNIEXPORT jstring JNICALL Java_com_example_myapplication_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { std::string hello Hello from C; return env-NewStringUTF(hello.c_str()); }3.3 JNI方法命名规则JNI方法的命名遵循特定规则Java_包名_类名_方法名例如Java_com_example_myapplication_MainActivity_stringFromJNI注意如果包名包含下划线(_)在JNI方法名中需要替换为_1。类似地其他特殊字符也有对应的转义规则。4. 构建和运行4.1 构建过程当你点击运行按钮时Android Studio会执行以下步骤编译Java代码为DEX字节码使用CMake和NDK编译C代码为共享库(.so)将所有资源打包成APK安装APK到设备或模拟器4.2 查看生成的.so文件构建完成后你可以在以下路径找到生成的.so文件app/build/intermediates/cmake/debug/obj/ ├── arm64-v8a/ │ └── libnative-lib.so ├── armeabi-v7a/ │ └── libnative-lib.so ├── x86/ │ └── libnative-lib.so └── x86_64/ └── libnative-lib.so5. 调试JNI代码5.1 使用LLDB调试Android Studio支持使用LLDB调试原生代码在C代码中设置断点选择Debug而非Run来启动应用当执行到native方法时调试器会停在断点处5.2 常见调试技巧使用__android_log_print输出日志#include android/log.h #define LOG_TAG JNI_DEBUG #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) LOGD(Debug message: %d, value);检查JNI方法签名是否正确确保Java包名和类名与JNI方法中的完全匹配使用adb logcat查看原生层日志6. 进阶配置6.1 添加额外的C源文件要在项目中添加更多C源文件在cpp目录下创建新文件在CMakeLists.txt中添加源文件add_library( native-lib SHARED native-lib.cpp new-source.cpp # 新增的源文件 )6.2 使用第三方原生库要使用预编译的第三方原生库将库文件(.so)放入src/main/jniLibs/对应ABI目录在CMakeLists.txt中添加add_library( imported-lib SHARED IMPORTED ) set_target_properties( imported-lib PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libimported.so ) target_link_libraries( native-lib imported-lib )6.3 配置不同的构建类型可以为debug和release构建配置不同的选项if(CMAKE_BUILD_TYPE STREQUAL Debug) add_definitions(-DDEBUG1) set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -O0 -g) else() add_definitions(-DRELEASE1) set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -O2) endif()7. 常见问题解决7.1 UnsatisfiedLinkError如果遇到java.lang.UnsatisfiedLinkError可能的原因包括库名称不匹配Java中加载的名称与CMake中定义的不一致库未正确打包到APK中ABI不兼容设备CPU架构与构建的ABI不匹配解决方案检查System.loadLibrary()的参数确认build.gradle中配置了正确的ABI过滤器清理并重新构建项目7.2 JNI方法找不到如果native方法无法链接可能的原因方法签名不正确方法名拼写错误注意包名、类名的大小写方法未正确导出C中需要使用extern C解决方案使用javah或javac -h生成正确的头文件检查方法名是否完全匹配确保C函数使用了正确的JNIEXPORT和JNICALL修饰符7.3 CMake配置错误常见的CMake错误包括CMake版本不兼容路径配置错误语法错误解决方案检查CMakeLists.txt的语法确认使用的CMake版本符合要求查看Gradle Console中的详细错误信息8. 性能优化建议8.1 减少JNI调用JNI调用开销较大应尽量减少Java和原生层之间的频繁调用。最佳实践是将多个小操作合并为一个大操作在原生层缓存Java对象和类引用使用直接缓冲区(Direct Buffer)传递大量数据8.2 内存管理JNI编程中常见的内存问题未正确释放局部引用全局引用泄漏未检查JNI函数的返回值最佳实践使用DeleteLocalRef释放不再需要的局部引用谨慎使用全局引用确保在不再需要时释放始终检查JNI函数的返回值8.3 异常处理JNI中的异常处理需要特别注意检查是否有未处理的Java异常正确处理原生代码抛出的异常在调用可能抛出异常的JNI函数后检查异常示例代码jmethodID mid env-GetMethodID(cls, method, ()V); if (env-ExceptionCheck()) { env-ExceptionDescribe(); // 打印异常信息 env-ExceptionClear(); // 清除异常 return; }9. 项目实战扩展9.1 添加更多JNI方法让我们扩展示例添加一个计算斐波那契数列的native方法Java层public native long fibonacci(int n);C层extern C JNIEXPORT jlong JNICALL Java_com_example_myapplication_MainActivity_fibonacci( JNIEnv* env, jobject thiz, jint n) { if (n 1) return n; return Java_com_example_myapplication_MainActivity_fibonacci(env, thiz, n-1) Java_com_example_myapplication_MainActivity_fibonacci(env, thiz, n-2); }9.2 使用C STL要在JNI中使用C标准库需要在CMakeLists.txt中配置# 使用静态链接的C标准库 set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -stdc11) target_link_libraries( native-lib ${log-lib} android # 链接Android特定库 )9.3 回调Java方法从原生代码调用Java方法的示例在Java类中定义回调方法public void onProgressUpdate(int progress) { runOnUiThread(() - { progressBar.setProgress(progress); }); }在C中调用回调方法// 获取类引用和方法ID应该在合适的地方缓存这些引用 jclass clazz env-GetObjectClass(thiz); jmethodID methodId env-GetMethodID(clazz, onProgressUpdate, (I)V); // 调用Java方法 env-CallVoidMethod(thiz, methodId, progressValue);10. 构建变体与ABI管理10.1 配置构建变体在build.gradle中可以为不同构建类型配置不同的CMake选项android { buildTypes { debug { externalNativeBuild { cmake { cppFlags -DDEBUG1 arguments -DANDROID_ARM_MODEarm } } } release { externalNativeBuild { cmake { cppFlags -DNDEBUG1 arguments -DANDROID_ARM_MODEthumb } } } } }10.2 ABI过滤为了减小APK大小可以只包含特定ABI的库android { defaultConfig { ndk { abiFilters arm64-v8a, armeabi-v7a // 只包含ARM架构 } } }或者为不同ABI配置不同选项android { flavorDimensions abi productFlavors { arm8 { dimension abi ndk { abiFilters arm64-v8a } } arm7 { dimension abi ndk { abiFilters armeabi-v7a } } } }11. 测试与持续集成11.1 单元测试可以为原生代码编写单元测试创建测试目录结构app/ ├── src/ │ ├── test/ # Java单元测试 │ ├── androidTest/ # 仪器化测试 │ └── testcpp/ # 原生单元测试 │ └── test/ │ └── test.cpp在CMakeLists.txt中添加测试配置enable_testing() add_executable( native-tests test/test.cpp ../main/cpp/native-lib.cpp ) target_link_libraries( native-tests ${log-lib} gtest ) add_test(NAME native-tests COMMAND native-tests)11.2 持续集成在CI服务器上构建JNI项目的关键步骤安装必要的SDK组件sdkmanager cmake;3.10.2 ndk;21.3.6528147设置环境变量export ANDROID_NDK_HOME$ANDROID_HOME/ndk/21.3.6528147执行构建./gradlew assembleDebug12. 安全最佳实践12.1 输入验证在JNI边界处必须严格验证输入检查指针是否为NULL验证数组长度检查字符串编码示例extern C JNIEXPORT void JNICALL Java_com_example_myapplication_MainActivity_processData( JNIEnv* env, jobject thiz, jbyteArray data) { if (data NULL) { env-ThrowNew(env-FindClass(java/lang/NullPointerException), Input data cannot be null); return; } jsize length env-GetArrayLength(data); if (length 0) { return; // 空数组无需处理 } // 处理数据... }12.2 敏感数据保护处理敏感数据时的注意事项不要将敏感数据存储在Java字符串中使用完后立即清除内存中的敏感数据考虑使用Android的KeyStore系统12.3 符号隐藏为了安全性和减小库大小应该隐藏不必要的符号在CMakeLists.txt中添加set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_VISIBILITY_INLINES_HIDDEN ON)或者在源代码中#define JNIEXPORT __attribute__ ((visibility (default))) #define JNIIMPORT __attribute__ ((visibility (default)))13. 性能分析与优化13.1 使用Simpleperf分析性能Simpleperf是Android上的原生代码性能分析工具在设备上收集性能数据adb shell simpleperf record -p pid -o /data/local/tmp/perf.data --duration 10拉取分析数据adb pull /data/local/tmp/perf.data生成报告python $ANDROID_NDK_HOME/simpleperf/report.py -i perf.data -o report.txt13.2 关键优化技术提高JNI性能的常见技术缓存方法ID和字段ID使用局部引用而非全局引用预计算JNI方法签名使用GetPrimitiveArrayCritical处理大型数组示例缓存方法IDjclass nativeClass; jmethodID callbackMethodId; JNIEXPORT void JNICALL Java_com_example_myapplication_NativeClass_init( JNIEnv* env, jclass clazz) { nativeClass (jclass)env-NewGlobalRef(clazz); callbackMethodId env-GetStaticMethodID( nativeClass, callback, (I)V); }14. 跨平台开发考虑14.1 支持多平台如果代码需要支持Android以外的平台可以这样组织项目src/ ├── common/ # 平台无关代码 ├── android/ # Android特定实现 └── desktop/ # 桌面平台实现在CMake中使用条件编译if(ANDROID) add_subdirectory(android) else() add_subdirectory(desktop) endif()14.2 使用预处理器宏使用宏处理平台差异#if defined(__ANDROID__) #include android/log.h #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) #else #include stdio.h #define LOGI(...) printf(__VA_ARGS__); printf(\n) #endif15. 资源管理与清理15.1 JNI引用管理JNI引用类型局部引用在native方法返回后自动释放全局引用必须手动释放弱全局引用不会阻止GC回收对象最佳实践// 创建全局引用 jclass globalClazz (jclass)env-NewGlobalRef(localClazz); // 使用完后释放 env-DeleteGlobalRef(globalClazz);15.2 原生资源管理管理原生资源的模式在Java类中使用long字段存储原生指针提供native方法创建/销毁资源在finalize()方法中确保资源释放示例public class NativeResource { private long nativeHandle; public native void init(); public native void dispose(); protected void finalize() throws Throwable { try { dispose(); } finally { super.finalize(); } } }16. 高级主题16.1 使用JNI_OnLoad可以在库加载时初始化资源JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm-GetEnv(reinterpret_castvoid**(env), JNI_VERSION_1_6) ! JNI_OK) { return JNI_ERR; } // 初始化工作... return JNI_VERSION_1_6; }16.2 动态注册native方法除了使用静态方法名匹配还可以动态注册方法static JNINativeMethod methods[] { {stringFromJNI, ()Ljava/lang/String;, (void*)stringFromJNI}, {fibonacci, (I)J, (void*)fibonacci} }; JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm-GetEnv(reinterpret_castvoid**(env), JNI_VERSION_1_6) ! JNI_OK) { return JNI_ERR; } jclass clazz env-FindClass(com/example/myapplication/MainActivity); env-RegisterNatives(clazz, methods, sizeof(methods)/sizeof(JNINativeMethod)); return JNI_VERSION_1_6; }17. 工具与资源17.1 实用工具javah生成JNI头文件较旧版本javac -h生成JNI头文件JDK 8ndk-stack将原生崩溃堆栈符号化addr2line将地址转换为源代码行号17.2 学习资源Android NDK官方文档JNI规范Google NDK示例18. 项目打包与发布18.1 减小APK大小优化原生库大小的技术使用-Oz优化标志最大程度减小大小去除调试符号发布构建按ABI拆分APK在CMake中配置if(CMAKE_BUILD_TYPE STREQUAL Release) set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -Oz -flto) endif()18.2 App Bundle支持Android App Bundle支持自动按设备配置分发原生库在build.gradle中启用android { bundle { language { enableSplit true } density { enableSplit true } abi { enableSplit true } } }19. 兼容性考虑19.1 多API级别支持处理不同Android版本的兼容性问题检查ANDROID_PLATFORM值使用__ANDROID_API__宏为旧API提供替代实现示例#if __ANDROID_API__ 21 // 使用新API #else // 使用兼容实现 #endif19.2 64位支持从Android 5.0开始支持64位架构需要注意确保为所有ABI构建库检查指针大小相关的代码测试32位和64位设备上的行为20. 总结与后续学习通过这个Hello World示例我们学习了Android JNI开发的基础知识包括搭建开发环境配置CMake和NDK编写Java和C交互代码调试和优化技巧后续可以深入学习更复杂的JNI类型转换多线程JNI编程使用Android特定的原生API如Bitmap、传感器等结合其他原生库如OpenGL ES、Vulkan