JNI 動態註冊

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!

完整代碼見 GitHub
https://github.com/alanwang4523/JNIExamples

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