1.JNI_OnLoad
在 Android Native 开发中,JNI_OnLoad
是动态注册本地方法的标准入口点。以下是一个标准实现示例及其说明:
JNI_OnLoad 标准实现
#include <jni.h>
#include <string>// 声明本地方法对应的 C/C++ 函数
jint native_add(JNIEnv* env, jobject thiz, jint a, jint b) {return a + b;
}// 定义 JNINativeMethod 结构体数组
static JNINativeMethod gMethods[] = {// Java方法名 | 方法签名 | 本地函数指针{"add", "(II)I", (void*)native_add},
};// 缓存 JavaVM 实例(用于后续获取 JNIEnv)
JavaVM* gJavaVM = nullptr;JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {JNIEnv* env = nullptr;jint result = -1;// 1. 获取 JNIEnvif (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {return JNI_ERR;}// 2. 缓存 JavaVM 实例gJavaVM = vm;// 3. 找到目标 Java 类const char* className = "com/example/MyJniClass";jclass clazz = env->FindClass(className);if (clazz == nullptr) {return JNI_ERR;}// 4. 注册本地方法if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) < 0) {return JNI_ERR;}// 5. 返回使用的 JNI 版本(必须与获取 JNIEnv 时指定的版本一致)return JNI_VERSION_1_6;
}
关键步骤说明
-
获取 JNIEnv
通过JavaVM::GetEnv
获取JNIEnv
指针,需指定 JNI 版本(通常为JNI_VERSION_1_6
)。 -
缓存 JavaVM
将JavaVM
实例保存到全局变量中,以便在其他线程中通过AttachCurrentThread
获取JNIEnv
。 -
查找 Java 类
使用FindClass
找到需要注册本地方法的 Java 类(需全限定类名,如com/example/MyJniClass
)。 -
注册本地方法
调用RegisterNatives
将JNINativeMethod
数组中的方法动态注册到目标类。每个JNINativeMethod
包含:- Java 方法名:与 Java 代码中的
native
方法名一致。 - 方法签名:描述方法的参数和返回值类型(可通过
javap -s <ClassName>
生成)。 - 本地函数指针:C/C++ 实现的函数地址。
- Java 方法名:与 Java 代码中的
-
返回 JNI 版本
需返回与GetEnv
中指定的版本一致的 JNI 版本号(如JNI_VERSION_1_6
)。
注意事项
-
方法签名
必须与 Java 方法的参数和返回值类型完全匹配。例如:(II)I
表示接收两个int
参数并返回int
。(Ljava/lang/String;)V
表示接收一个String
参数且无返回值。
-
避免名称改编(C++)
如果使用 C++,需通过extern "C"
或#pragma GCC visibility
确保函数名称不被编译器改编:extern "C" JNIEXPORT jint JNICALL Java_com_example_MyJniClass_add(JNIEnv*, jobject, jint, jint);
-
错误处理
检查FindClass
和RegisterNatives
的返回值,避免因注册失败导致崩溃。 -
线程安全
通过缓存的JavaVM
,可在其他线程中安全获取JNIEnv
:JNIEnv* env; gJavaVM->AttachCurrentThread(&env, nullptr);
优势(动态注册 vs 静态注册)
- 更高效:避免运行时通过方法名动态查找本地函数。
- 更灵活:无需遵循
Java_<Package>_<Class>_<Method>
的命名格式。 - 可控性:可在
JNI_OnLoad
中集中管理所有注册逻辑。
通过以上实现,可确保本地方法在 JNI 库加载时正确注册,提升代码可维护性和性能。
2.调用JNI_OnLoad
在 Android 开发中,JNI_OnLoad
不是由开发者直接调用的,而是由 Java 虚拟机(JVM)在加载动态库(.so
文件)时自动触发的。以下是它的调用机制和触发方式:
1. JNI_OnLoad
的调用机制
当你在 Java 代码中加载一个 JNI 动态库(.so
文件)时,系统会自动检查该库是否实现了 JNI_OnLoad
函数。如果存在,JVM 会在库加载后立即调用它。
触发 JNI_OnLoad
的方式
// Java 代码(例如 MainActivity.java)
static {System.loadLibrary("myjni"); // 加载 libmyjni.so
}
System.loadLibrary("myjni")
会尝试加载libmyjni.so
(在 Android 中会自动添加lib
前缀和.so
后缀)。- JVM 检查
JNI_OnLoad
如果libmyjni.so
实现了JNI_OnLoad
,JVM 会在加载后立即调用它。
2. JNI_OnLoad
的调用流程
- Java 代码调用
System.loadLibrary("myjni")
- Android Runtime(ART/Dalvik)加载
libmyjni.so
- JVM 检查是否实现了
JNI_OnLoad
- 如果存在,调用
JNI_OnLoad(JavaVM* vm, void* reserved)
。 - 如果不存在,JNI 会使用默认的静态注册方式(基于
JNIEXPORT
函数名匹配)。
- 如果存在,调用
JNI_OnLoad
执行动态注册- 注册
JNINativeMethod
数组中的本地方法。 - 返回 JNI 版本(如
JNI_VERSION_1_6
)。
- 注册
3. 如何确保 JNI_OnLoad
被正确调用?
(1)确保 .so
文件正确编译
在 CMakeLists.txt
或 Android.mk
中正确配置:
# CMakeLists.txt
add_library(myjni SHARED native-lib.cpp)
或
# Android.mk
LOCAL_MODULE := myjni
LOCAL_SRC_FILES := native-lib.cpp
(2)确保 Java 代码正确加载库
// 在静态代码块中加载库(避免重复加载)
static {System.loadLibrary("myjni");
}
(3)检查 JNI_OnLoad
是否被调用
可以在 JNI_OnLoad
中添加日志:
#include <android/log.h>
#define LOG_TAG "JNI_DEBUG"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {LOGD("JNI_OnLoad called!"); // 检查是否执行// ... 其他注册逻辑return JNI_VERSION_1_6;
}
然后在 logcat
中查看日志:
adb logcat | grep "JNI_DEBUG"
4. 常见问题
(1)JNI_OnLoad
没有被调用?
- 库未正确加载:检查
System.loadLibrary
是否成功执行(无UnsatisfiedLinkError
)。 .so
文件未正确编译:确保JNI_OnLoad
被包含在最终.so
中(可通过nm -D libmyjni.so | grep JNI_OnLoad
检查)。- JNI 版本不匹配:确保
JNI_OnLoad
返回正确的版本(如JNI_VERSION_1_6
)。
(2)JNI_OnLoad
和静态注册的区别?
特性 | JNI_OnLoad (动态注册) | 静态注册(默认方式) |
---|---|---|
注册方式 | 主动调用 RegisterNatives | 基于函数名匹配(如 Java_com_example_MyClass_method ) |
性能 | 更快(避免运行时查找) | 较慢(首次调用需解析) |
灵活性 | 可自定义注册逻辑 | 必须遵循固定命名规则 |
适用场景 | 推荐用于复杂项目 | 简单 JNI 调用 |
5. 总结
JNI_OnLoad
由 JVM 自动调用,开发者只需实现它并在其中注册本地方法。- 触发方式:在 Java 中调用
System.loadLibrary("mylib")
。 - 核心作用:动态注册 JNI 方法,提升性能并增强灵活性。
- 调试技巧:通过
__android_log_print
打印日志,确保JNI_OnLoad
被正确执行。
如果你的 JNI_OnLoad
没有被调用,请检查库加载流程和编译配置!