Android中JNI&NDK入門(三) 之 動態註冊Native函數

1 前言

前面兩篇文章中,已經對JNI有了一些介紹。現在我們來回顧一下,它主要是通過使用javac -h命令來生成了一個.h的頭文件,來產生Java和Native兩邊方法函的註冊關聯。這樣當Java代碼中去執行Native方法的時候,就會通過兩邊的關聯的映射關係來找到這些Native真正實現的地方。事實上,JNI有兩種關聯Native方法的途徑,分別是靜態註冊和動態註冊。

2 註冊方式

2.1 靜態註冊

前面文章所列舉的Demo中使用命令生成.h頭文件的方式就是使用了靜態註冊的方式。可以發現通過這種方式生成的.h頭文件中,函數的名稱是有規律的,如:Java_com_zyx_jnidemo_JNIUtils_getInfo,規律就是:Java前綴 + Native方法所在類的全稱(用_替換.) + Native方法名。

靜態註冊的好處就是全自動生成操作方便,但缺點也是相當明顯,就是函名過長和不夠靈活。

2.2 動態註冊

動態註冊是通過RegisterNatives函數來將Java層和Native層的方法和函數動態關聯起來,而且無需遵循特定的方法命名格式,使得代碼可以更加的靈活。

當我們在Java代碼中使用System.loadLibarary()方法來加載so文件時,Java虛擬機就會去調用JNI_OnLoad函數,該函數的作用是告訴虛機機應該使用哪個版本的JNI,如果我們沒有指定,它默認是使用JNI1.1版本。JNI_OnLoad函數中還可以做一些初始化的事件,它對應反加載函數是JNI_OnUnload。所以我們要想進行Native函數的動態註冊,最佳的時機就是在JNI_OnLoad函數中去執行。

4 繼續修改Hello World

繼續使用前面文章中的Demo,這次我們不通過命令生成.h頭文件,而且自己創建一個新的.h頭文件JNIUtils.h,內容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_zyx_jnidemo_JNIUtils */

#ifndef _Included_com_zyx_jnidemo_JNIUtils
#define _Included_com_zyx_jnidemo_JNIUtils
#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     com_zyx_jnidemo_JNIUtils
 * Method:    getInfo
 * Signature: ()Ljava/lang/String;
 */
//JNIEXPORT jstring JNICALL Java_com_zyx_jnidemo_JNIUtils_getInfo
//  (JNIEnv *, jclass);


/*
 * Native層對應Java層中的方法
 */
static jstring getInfoToNative(JNIEnv * env, jclass thiz);

/*
 * Java中所定義的Native方法映射表
 */
static JNINativeMethod gJniNativeMethods[] = {
        {"getInfo", "()Ljava/lang/String;", (void*)getInfoToNative},
};

/*
 * 初始化
 */
jint JNI_OnLoad(JavaVM* vm, void* reserved);


#ifdef __cplusplus
}
#endif
#endif

修改JNIUtils.cpp文件:

#include "JNIUtils.h"
#include <stdio.h>
#include <android/log.h>

//JNIEXPORT jstring JNICALL Java_com_zyx_jnidemo_JNIUtils_getInfo(JNIEnv * env, jclass thiz) {
//    __android_log_print(ANDROID_LOG_DEBUG, "zyx", "Hello world from JNI !");
//    //return env->NewStringUTF("Hello world from JNI !");
//
//    jclass clazz = env->FindClass("com/zyx/jnidemo/JNIUtils");
//    if (clazz == NULL) {
//        return env->NewStringUTF("find class error");
//    }
//    jmethodID methodId = env->GetStaticMethodID(clazz, "getJavaInfo", "(Ljava/lang/String;I)Ljava/lang/String;");
//    if(methodId == NULL) {
//        return env->NewStringUTF("find method error");
//    }
//    jstring info = env->NewStringUTF("Hello world from JNI !");
//    jint index = 2;
//    return (jstring)env->CallStaticObjectMethod(clazz, methodId, info, index);
//}

static jstring getInfoToNative(JNIEnv * env, jclass thiz) {
    return env->NewStringUTF("Hello world from JNI !3");
}

jint JNI_OnLoad(JavaVM* vm, void* reserved) {

    JNIEnv* jniEnv = NULL;

    // 從虛擬機中獲得JNIEnv,同時指定jni版本
    if (vm->GetEnv((void**) &jniEnv, JNI_VERSION_1_6) != JNI_OK || jniEnv == NULL) {
        return JNI_ERR;
    }

    // 獲得Java代碼中Natives方法所在類
    jclass clazz = (jniEnv)->FindClass("com/zyx/jnidemo/JNIUtils");
    if (clazz == NULL) {
        return JNI_ERR;
    }

    // 執行註冊
    jint nMethods = sizeof(gJniNativeMethods) / sizeof(JNINativeMethod);
    if ((jniEnv)->RegisterNatives(clazz, gJniNativeMethods, nMethods) < 0) {
        return JNI_ERR;
    }

    return JNI_VERSION_1_6;
}

也是非常簡單的修改,然後再重新編譯程序,運行可見:

 

說明:

JNIUtils.h中定義了兩個函數和一個數組:

getInfoToNative                               Native層對應Java層中要註冊關聯的函數

JNI_OnLoad                                     進行初始化和指定JNI版本

gJniNativeMethods                          是一個JNINativeMethod類型的數組,它是一個函數是映射表,用於關聯Java層中定義的Native方法和Native層的相應函數,其中第一個參數是Java中的方法名,第二個參數是方法的簽名,第三個參數是Native中函數的指針

JNIUtils.cpp中對兩個定義的函數進行了實現,可見在JNI_OnLoad函數內部使用了RegisterNatives函數傳入Java中的類和gJniNativeMethods數組來執行動態註冊關聯兩邊的方法函數。

 

點擊下載示例

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章