Android JNI之動態注冊(android studio)

故事得從這里開始-靜態注冊

沒有對比,就沒有傷害。靜態注冊自出生以來,就非議頗多。繁瑣的過程(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對象的實例。動態注冊,就是這么回事,是時候展示實力的時候了。

動態注冊項目結構圖

附送一張機票,帶你領略世界的浩瀚

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容