共计 2275 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点分析
在 Android 开发中,通过 JNI 调用.so 文件中的函数是常见的性能优化手段,但开发者常面临以下问题:

-
UnsatisfiedLinkError:当.so 文件未正确加载或函数签名不匹配时,会抛出此异常。常见原因包括 ABI 不兼容(如 armeabi-v7a 设备加载 arm64-v8a 库)或函数名拼写错误。
-
内存泄漏 :JNI 局部引用未手动释放会导致内存累积。例如,在循环中创建 Java 对象而未调用 DeleteLocalRef,可能引发 OOM。
-
线程安全问题 :JNIEnv 是线程相关的,跨线程直接使用可能崩溃。
技术方案详解
静态注册 vs 动态注册
| 对比项 | 静态注册 | 动态注册 |
|---|---|---|
| 实现方式 | 通过 Java 方法名自动匹配 | 手动注册方法映射表 |
| 性能 | 首次调用需查找符号表 | 加载时即完成绑定,调用更快 |
| 灵活性 | 方法名需严格匹配 | 可自定义 JNI 函数名 |
| 维护成本 | 修改需重新编译 Java 层 | 只需维护注册逻辑 |
动态注册实战
- 定义 Native 方法映射表 :
static JNINativeMethod methods[] = {{"nativeCalculate", "(II)I", (void*)calculate}
};
- 在 JNI_OnLoad 中注册 :
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {return JNI_ERR;}
jclass clazz = env->FindClass("com/example/NativeLib");
env->RegisterNatives(clazz, methods, sizeof(methods)/sizeof(methods[0]));
return JNI_VERSION_1_6;
}
智能引用管理
使用 ScopedLocalRef 自动释放局部引用:
jobject result = env->NewObject(...);
ScopedLocalRef<jobject> guard(env, result);
// 使用 result 对象,退出作用域后自动释放
完整代码模板
native-lib.cpp
#include <jni.h>
#include <android/log.h>
#define LOG_TAG "NativeLib"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
extern "C" {JNIEXPORT jint JNICALL calculate(JNIEnv* env, jobject thiz, jint a, jint b) {LOGD("Input values: %d, %d", a, b);
if (env->ExceptionCheck()) {env->ExceptionClear();
return -1;
}
return a + b;
}
static JNINativeMethod methods[] = {{"nativeCalculate", "(II)I", (void*)calculate}
};
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {return JNI_ERR;}
jclass clazz = env->FindClass("com/example/NativeLib");
env->RegisterNatives(clazz, methods, 1);
return JNI_VERSION_1_6;
}
}
进阶优化技巧
多线程安全
- 获取线程专属 JNIEnv:
JavaVM* g_vm; // 在 JNI_OnLoad 中保存
void worker_thread() {
JNIEnv* env;
g_vm->AttachCurrentThread(&env, nullptr);
// 执行 JNI 操作
g_vm->DetachCurrentThread();}
体积优化
- 编译选项 :
android {
defaultConfig {
externalNativeBuild {
cmake {cppFlags "-Oz"}
}
}
}
- 剥离调试符号 :
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/arm-linux-androideabi-strip --strip-unneeded libnative.so
常见陷阱
- 签名错误 :
(I)V表示参数为 int,返回 void-
Ljava/lang/String;需注意末尾分号 -
ABI 不匹配 :
- 检查设备 CPU 架构:
adb shell getprop ro.product.cpu.abi - Gradle 中配置 ndk.abiFilters
流程图示例
sequenceDiagram
Java->>+Native: 调用 native 方法
Native->>+JVM: 通过 JNIEnv 操作 Java 对象
JVM-->>-Native: 返回结果 / 异常
Native-->>-Java: 返回处理结果
延伸思考
- 如何实现.so 文件的热更新?
- 混合使用多个.so 时如何避免符号冲突?
- 如何通过 JNI 传递复杂数据结构(如二维数组)?
总结
通过动态注册、智能引用管理和严格的异常检查,可以显著提升.so 调用的稳定性。建议在关键路径添加 NDK 日志,并利用 Android Studio 的 LLDB 调试器逐步验证 JNI 调用链。
正文完
