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

📅 2026/7/19 7:47:09 👁️ 阅读次数 📝 编程学习
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)
  • CMake
  • LLDB(用于调试原生代码)

提示:建议选择较新的NDK版本,但不要使用预览版,以避免兼容性问题。目前稳定版本是NDK r21或r22。

1.2 创建支持C++的新项目

  1. 在Android Studio中选择"File > New > New Project"
  2. 选择"Native C++"模板
  3. 按照向导完成项目创建

这个模板会自动配置好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会执行以下步骤:

  1. 编译Java代码为DEX字节码
  2. 使用CMake和NDK编译C++代码为共享库(.so)
  3. 将所有资源打包成APK
  4. 安装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.so

5. 调试JNI代码

5.1 使用LLDB调试

Android Studio支持使用LLDB调试原生代码:

  1. 在C++代码中设置断点
  2. 选择"Debug"而非"Run"来启动应用
  3. 当执行到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++源文件:

  1. 在cpp目录下创建新文件
  2. 在CMakeLists.txt中添加源文件:
add_library( native-lib SHARED native-lib.cpp new-source.cpp # 新增的源文件 )

6.2 使用第三方原生库

要使用预编译的第三方原生库:

  1. 将库文件(.so)放入src/main/jniLibs/对应ABI目录
  2. 在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(-DDEBUG=1) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g") else() add_definitions(-DRELEASE=1) 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")

解决方案:

  • 使用javahjavac -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} -std=c++11") target_link_libraries( native-lib ${log-lib} android # 链接Android特定库 )

9.3 回调Java方法

从原生代码调用Java方法的示例:

  1. 在Java类中定义回调方法:
public void onProgressUpdate(int progress) { runOnUiThread(() -> { progressBar.setProgress(progress); }); }
  1. 在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 "-DDEBUG=1" arguments "-DANDROID_ARM_MODE=arm" } } } release { externalNativeBuild { cmake { cppFlags "-DNDEBUG=1" arguments "-DANDROID_ARM_MODE=thumb" } } } } }

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 单元测试

可以为原生代码编写单元测试:

  1. 创建测试目录结构:
app/ ├── src/ │ ├── test/ # Java单元测试 │ ├── androidTest/ # 仪器化测试 │ └── testcpp/ # 原生单元测试 │ └── test/ │ └── test.cpp
  1. 在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项目的关键步骤:

  1. 安装必要的SDK组件
sdkmanager "cmake;3.10.2" "ndk;21.3.6528147"
  1. 设置环境变量
export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/21.3.6528147
  1. 执行构建
./gradlew assembleDebug

12. 安全最佳实践

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上的原生代码性能分析工具:

  1. 在设备上收集性能数据:
adb shell simpleperf record -p <pid> -o /data/local/tmp/perf.data --duration 10
  1. 拉取分析数据:
adb pull /data/local/tmp/perf.data
  1. 生成报告:
python $ANDROID_NDK_HOME/simpleperf/report.py -i perf.data -o report.txt

13.2 关键优化技术

提高JNI性能的常见技术:

  • 缓存方法ID和字段ID
  • 使用局部引用而非全局引用
  • 预计算JNI方法签名
  • 使用GetPrimitiveArrayCritical处理大型数组

示例(缓存方法ID):

jclass 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") #endif

15. 资源管理与清理

15.1 JNI引用管理

JNI引用类型:

  • 局部引用:在native方法返回后自动释放
  • 全局引用:必须手动释放
  • 弱全局引用:不会阻止GC回收对象

最佳实践:

// 创建全局引用 jclass globalClazz = (jclass)env->NewGlobalRef(localClazz); // 使用完后释放 env->DeleteGlobalRef(globalClazz);

15.2 原生资源管理

管理原生资源的模式:

  1. 在Java类中使用long字段存储原生指针
  2. 提供native方法创建/销毁资源
  3. 在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_cast<void**>(&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_cast<void**>(&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 8+)
  • ndk-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 // 使用兼容实现 #endif

19.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)