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
沒有被調用,請檢查庫加載流程和編譯配置!