JNI(Java Native Interface)是一套編程接口, 用來實現 Java 代碼與本地的 C/C++ 代碼進行交互,其有兩種註冊方式:靜態註冊和動態註冊。
靜態註冊
- 理解和使用方式簡單, 使用相關工具按流程操作就行, 編碼出錯率低
- JNI 層函數名特別長,且當需要更改類名,包名或者方法時, 需要按照之前方法重新生成頭文件, 靈活性不高
- 初次調用 native 函數時要根據函數名搜索對應的 JNI 層函數來建立聯繫,效率較低
動態註冊
- 靈活性高, 更改類名、包名或方法時, 只需對更改模塊進行少量修改, 效率高
- 使用用函數映射表來調用相應的函數,效率較高(Android 源碼使用的動態註冊)
- 理解和使用方式稍複雜, 容易搞錯簽名、方法, 導致註冊失敗
本文主要講動態註冊,靜態註冊見我另外一篇博客《JNI 傳遞和返回基本參數》
動態註冊
動態註冊基本思想是:當在 Java 層調用 System.loadLibrary(libName) 的時候,底層會調用 JNI_OnLoad () 函數,在這個函數中通過 NDK 提供的 RegisterNatives() 方法來將 C/C++ 方法和 Java 方法註冊映射起來,後續從 Java 層調用聲明爲 native 的方法時就會根據映射表找到對應的 C/C++ 方法。註冊整體流程如下:
1、編寫 Java 層的相關 Native 方法,如
package com.alan.jniexamples.dynamic;
/**
* Author: AlanWang4523.
* Date: 19/5/18 11:14.
* Mail: [email protected]
*/
public class NativeDynamic {
static {
System.loadLibrary("jni_example");
}
public native int nativeSetBasicArgs(int iArg, float fArg, long lArg, boolean bArg);
public native void nativeSetStringArgs(String str);
}
2、編寫 C/C++ 代碼,將 Java 方法和 C/C++ 方法通過簽名信息對應起來,如:
static int JNISetBasicArgs(JNIEnv *env, jobject obj,
jint iArg, jfloat fArg, jlong lArg, jboolean bArg) {
LOGD("NativeDynamicJNI", "JNISetBasicArgs()-->>iArg = %d, fArg = %f, lArg = %ld, bArg = %d\n",
iArg, fArg, lArg, bArg);
return 0;
}
static void JNISetStringArgs(JNIEnv *env, jobject obj,jstring strArg) {
jboolean iscopy;
// 這裏不能直接使用 strArg,需要將其通過 GetStringUTFChars 接口將其轉成 UTF-8 的 字符串的指針
char *cStr = (char *) env->GetStringUTFChars(strArg, &iscopy);
LOGD("NativeDynamicJNI", "JNISetStringArgs()--->cStr = %s", cStr);
// 最後需要釋放,否則可能導致內存泄漏
env->ReleaseStringUTFChars(strArg, cStr);
}
// Java 層聲明 native 方法的類的全路徑
static const char *className = "com/alan/jniexamples/dynamic/NativeDynamic";
static JNINativeMethod gJni_Methods[] = {
// 如: nativeSetBasicArgs 是 Java 聲明的方法
// "(IFJZ)I" 是函數簽名
// JNISetBasicArgs 是 C/C++ 聲明的方法
{"nativeSetBasicArgs", "(IFJZ)I", (void*)JNISetBasicArgs},
{"nativeSetStringArgs", "(Ljava/lang/String;)V", (void*)JNISetStringArgs},
};
3、在 JNI_OnLoad () 函數中通過 NDK 提供的 RegisterNatives() 方法來將 C/C++ 方法和 Java 方法註冊映射起來:
jint JNI_OnLoad(JavaVM* jvm, void* reserved){
JNIEnv* env = NULL;
jint result = -1;
if (jvm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}
jniRegisterNativeMethods(env, className, gJni_Methods, NELEM(gJni_Methods));
return JNI_VERSION_1_4;
}
jniRegisterNativeMethods 方法如下:
int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) {
LOGD("JNIHelper", "Start registering %s native methods.\n", className);
jclass clazz = (env)->FindClass(className);
if (clazz == NULL) {
LOGE("JNIHelper", "Native registration unable to find class '%s'.\n", className);
return -1;
}
int result = 0;
if ((env)->RegisterNatives(clazz, gMethods, numMethods) < 0) {
LOGE("JNIHelper", "RegisterNatives failed for '%s'.\n", className);
result = -1;
}
(env)->DeleteLocalRef(clazz);
LOGD("JNIHelper", "Registering %s native methods success.\n", className);
return result;
}
4、測試調用:
private void testDynamicJNIs() {
NativeDynamic nativeDynamic = new NativeDynamic();
nativeDynamic.nativeSetBasicArgs(2, 3.2f, 1000L, true);
nativeDynamic.nativeSetStringArgs("Hello Alan From Java!");
}
輸出:
05-18 21:13:28.891 8156-8156/com.alan.jniexamples D/NativeDynamicJNI: JNISetBasicArgs()-->>iArg = 2, fArg = 3.200000, lArg = 1000, bArg = 1
05-18 21:13:28.891 8156-8156/com.alan.jniexamples D/NativeDynamicJNI: JNISetStringArgs()--->cStr = Hello Alan From Java!