3.1 Java中加載動態(tài)庫
java中通過系統(tǒng)提供的如下方法來加載動態(tài)庫。
static {
System.loadLibrary("cmakecompile");
}
ps: java中的static代碼塊隨著類的加載而執(zhí)行,并且只會執(zhí)行一次,并且還優(yōu)先于main函數(shù)。
其實只需要在調(diào)用JNI方法之前l(fā)oad就行,可以捕獲一下異常來檢查是否加載成功,加載不成功就不能調(diào)用native方法 否則肯定會出現(xiàn)找不到方法而閃退。
try {
System.loadLibrary("cmakecompile");
} catch (Throwable e) {
e.printStackTrace();
}
3.2 JNI中的JNIEnv和JavaVM的關系
-
JavaVM:代表Java虛擬機。所有的方法調(diào)用從這里開始。
- 可以通過實現(xiàn)
JNIEXPORT int JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);方法來獲取JavaVM的指針。 - 也可以用
jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*);來主動創(chuàng)建JavaVM。但此方法在安卓上沒有導出所以沒法調(diào)用,先忽略。
- 可以通過實現(xiàn)
-
JNIEnv:是提供各個JNI Native函數(shù)的結構體跟線程相關,不同線程之前的JNIEnv相互獨立。但同一個線程中的
JNIEnv*是一樣的。如果不能確保方法都在同一個線程中調(diào)用,就不能把這個JNIEnv* 存起來。
兩者中JavaVM是在各個線程中共享。android只允許有一個JavaVM。在不同線程中需要JNIEnv*時可以通過JavaVM獲取。所以可以考慮全局存儲JavaVM。 Java 的dex字節(jié)碼和c/c++的.so同時運行Dalvik虛擬機之內(nèi),共同使用一個進程空間。之所以可以相互調(diào)用,也是因為有Dalvik虛擬機。
3.3 Java中調(diào)用Native方法方式,命名規(guī)則和JNI頭文件創(chuàng)建方式
java調(diào)用native方法 首先需要用native關鍵字聲明java方法。例如:
public native String intFromJNI();
此時Android Studio會提示找不到這個方法的JNI實現(xiàn),我們點擊提示的創(chuàng)建該方法的JNI實現(xiàn)選擇實現(xiàn)文件就會自動添加進去,然后我們?nèi)崿F(xiàn)相關的C++實現(xiàn)即可。先看看自動添加的代碼:
extern "C" JNIEXPORT jstring JNICALL Java_com_memetghini_cmakecompile_MainActivity_intFromJNI(JNIEnv *env, jobject this) {
}
首先extern "C"是因為c/c++編繹器對函數(shù)名譯碼的方式不同所引起的。c語言的函數(shù)是通過函數(shù)名來識別的,但c++有方法重載等問題所以函數(shù)編譯后生成的符號里面體現(xiàn)了函數(shù)的名字,參數(shù)類型,返回值類型等信息,產(chǎn)物就不一樣。所以就算是一樣的代碼通過c和c++編譯器編譯之后連接方式就不太一樣。所以這里extern "C"是為了避免c++編譯按照c++的方式編譯c函數(shù),這里jni函數(shù)就是一個c函數(shù)。可以通過用__cplusplus宏來包圍的形式同時支持c編譯來編譯是忽略這個extern c,因為c編譯壓根就不認識這個。
#ifdef __cplusplus
extern "C" {
#endif
jni methods
#ifdef __cplusplus
}
#endif
JNIEXPORT 顯然就是要導出此方法。Windows平臺中需要導出動態(tài)庫的方法需要#define JNIEXPORT __declspec(dllexport)。如果此方法不在導出的符號表中JNI也無法調(diào)用它。例如在mac平臺下可以不加,默認會導出。如果替換為__attribute__ ((visibility ("hidden"))) jint JNICALL就找不到了。
JNICALL在windows中的值為__stdcall,用于約束函數(shù)入棧順序和堆棧清理的規(guī)則。在linux,mac平臺下是一個空聲明。所以簡單來說在Linux平臺上這兩個宏不加也沒有影響。
方法命名規(guī)則是:Java開頭然后拼接包名再拼接類名最后是方法名,每一個都用下劃線_連接即可。默認帶兩個參數(shù)。
- JNIEnv* 是定義任意native函數(shù)的第一個參數(shù)(包括調(diào)用JNI的RegisterNatives函數(shù)注冊的函數(shù)),指向JVM函數(shù)表的指針,函數(shù)表中的每一個入口指向一個JNI函數(shù),每個函數(shù)用于訪問JVM中特定的數(shù)據(jù)結構。簡單來說在JNI中獲取Java的類和相關方法都得通過這個JNIEnv*指針來獲取。
- 調(diào)用java中native方法的實例或Class對象,如果這個native方法是實例方法,則該參數(shù)是jobject,如果是靜態(tài)方法,則是jclass。
- 在后面就是方法本身的參數(shù),上面的方法目前是沒有參數(shù)的所以為空。