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數組來執行動態註冊關聯兩邊的方法函數。