故事得從這里開始-靜態注冊
沒有對比,就沒有傷害。靜態注冊自出生以來,就非議頗多。繁瑣的過程(javah生成頭文件),每增加一個接口,渣握h一下。長長長的函數名稱,一個詳細的身份證明。運行效率較低,第一次進行交流的時候,根據詳細的身份去jni中查找對應身份的人,這個難度比較大,一個一個去對比,運氣好,一次匹配就建立了關系,如果,假如說如果,等到最后,黃花菜都涼了。艱難的時候,到了要放棄的時候,man是時候登場了,動態注冊,對,就是他。JNI中有一個函數映射表,注冊給Jave虛擬機,一見鐘情,交流就是這么順暢。
動態注冊,是需要畫幾個圈
規則,還是要有的。早早建立數據庫,交流一觸即發。
一。入口 JNI_OnLoad 方法
System.loadLibrary加載完 JNI 動態庫之后,調用JNI_OnLoad函數,開始動態注冊。這里就是報名注冊的地方,地方不能找錯,你報個警抓小偷,非要打個119,我也很悲傷,但無能為力。注意:一個.so只能存在一個onload方法。
二。重頭戲 RegisterNatives 方法
在 JNI 帝國中,不得不介紹下 JNIEnv*,JVM代言人,掌握java生殺大權,java環境變量指針,是一個包含了JVM接口的結構,它包含了與JVM進行交互以及與Java對象協同工作所必需的函數。而 RegisterNatives 只是其中的一顆棋子,登記員。但只有通過他,你才能進入函數映射表。函數映射表?不急,我們慢慢來,一步一步剝離他的心。在 jni.h 帝國中,描述有這么一段:
typedef struct {
const char* name; //Java中函數的名字
const char* signature; //描述了函數的參數和返回值
void* fnPtr; //函數指針,指向我們調用別人家c++的封裝 JNI 函數方法
} JNINativeMethod;
這就是函數映射表的數據結構,注釋加上,似乎也沒什么需要解釋的了,至于繁瑣的源代碼解析,這個結構又是怎么被使用,腦補,腦補。咳咳咳,回歸正題,重頭戲 RegisterNatives,這是一部宮廷史詩劇,精心的布局,宏大的場面。這么說吧,演員甲乙丙,就三個,共同出演一部不是你死就是我死的故事結局。劇本中記載了這么一段:
/**
* 向JNI環境注冊一個本地方法
* @param clazz 包含本地方法的Java類
* @param methods 本地方法描述數組
* @param nMethods 本地方法個數
* @return 成功返回0,否則注冊失敗
*/
jint RegisterNatives(jclass clazz, const JNINativeMethod *methods, jint nMethods);
不知不覺,后知后覺,一個注釋完成了解析。簡單點,說話的方式簡單點。該配合你演出的我演視而不見。這個登記員的描述,大體如此了,如有更多需求,請自行研究。
三。結束 JNI_OnUnload 方法
出來混的遲早都是要還的,龐大的 JNI 帝國也有結束的那一天。當VM釋放該組件時會調用 JNI_OnUnload 方法,曾經擁有的,比如說對象啊,通通在這里回歸大地,化作春泥更護花。 當然也可以選擇放棄,這個方法甩在一邊,那么毒蘑菇總有一天會茁壯成長的,慢慢慢慢,然后boom。優化,是你我的一個可選項。
四。奇跡見證時刻
不得不說,這個時刻,我特別激動。花兒,為什么這么紅?略微貼下代碼,稍顯細節:
static const char *jniClassName = "net/mapout/jni/JNILoader";
static JNINativeMethod methods[] = {
{"sayHello", "()Ljava/lang/String;", (void*)jniSayHello},
};
static int registerNatives(JNIEnv* env) {
jclass clazz = env->FindClass(jniClassName);
if (clazz == NULL)
return JNI_FALSE;
jint methodSize = sizeof(methods) / sizeof(methods[0]);
if ( env->RegisterNatives(clazz, methods, methodSize) < 0 )
return JNI_FALSE;
return JNI_TRUE;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK)
return JNI_ERR;
//注冊方法
if (!registerNatives(env))
return JNI_ERR;
result = JNI_VERSION_1_6;
return result;
}
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
JNIEnv *env = nullptr;
jint ret = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (ret != JNI_OK) {
return ;
}
//回收二手女朋友,回收... 嘿嘿嘿
}
一目了然,我們所需要維護的就是methods這個數組了,剩下的都是復制粘貼。稍微來個對比:
/**
* 動態注冊。命名簡潔,清晰明了
*/
jstring jniSayHello(JNIEnv *env, jobject obj){
return str2jstring( env, sayHello() );
}
/**
* 靜態注冊。噗噗噗,一口老血噴涌而出
*/
JNIEXPORT jstring JNICALL Java_net_mapout_jni_JNILoader_sayHello
(JNIEnv *env, jclass jclass){
return str2jstring( env, sayHello() );
}
又是一個注釋。代碼,沒有一個注釋不能說清楚的,如果有,那就請用兩個注釋。說個細節,這里函數都有2個參數,一個 JNIEnv * 已經解釋過了,還一個jobject。稍微講一下,native方法如果沒有static,那么就是類的實例,如果武裝了static,進化加強,變為了類的class對象的實例。動態注冊,就是這么回事,是時候展示實力的時候了。
附送一張機票,帶你領略世界的浩瀚