天天看點

Android NDK系列(6) — 動态注冊native方法

我在部落格上發表一些我的NDK學習心得,希望對大家能有幫助。 這一篇我們講述如何動态注冊native方法

介紹

首先,之前寫的文章中通過一個簡單的例子來使用了一下NDK,實作了從Native中調用Java方法。

下面,我們要介紹的是實作動态綁定native方法來破除命名限制。

問題

在靜态注冊的情況,所有的方法都是有固定的方法名:Java_<包名> <類名> <方法名>,這種情況下,調用一個方法比較繁瑣,同時也有命名限制,是以使用動态綁定來解決這個問題。

實踐

首先,還是老樣子,加載so,定義native方法

public class MyJni {

    static {
        System.loadLibrary("myjni");
    }

    public native void nativePrint();
    public native int nativeCount(int a, int b);

    public MyJni() {
        Log.d("123", "Load MyJni nativePrint & nativeCount");
        nativePrint();
        int a = nativeCount(,);
        Log.d("123", a+"");
    }


}

           

為了在程式初始化的時候進行方法的動态注冊,當so被加載時就會調用JNI_OnLoad函數,需要重寫JNI_OnLoad方法,在該方法中注冊

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -;

    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("GetEnv failed!");
        return -1;
    }
    //===========================================
    assert(env != NULL);

    if (!registerNatives(env)) // 注冊本地方法
    {
        return -1;
    }
    //===========================================
    /* success -- return valid version number */
    result = JNI_VERSION_1_4;

    return result;
}
           

調用registerNatives進行注冊,其中需要指定注冊的類資訊,這樣才能夠擷取其jclass對象

/*
* 為所有類注冊本地方法
*/
static int registerNatives(JNIEnv* env) {
    const char* kClassName = "com/example/qiuyu/testhellojni/MainActivity";//指定要注冊的類
    return registerNativeMethods(env, kClassName, gMethods,
                                 sizeof(gMethods) / sizeof(gMethods[]));
}
           

調用registerNativeMethods,使用RegisterNatives進行注冊

/*
* 為某一個類注冊本地方法
*/
static int registerNativeMethods(JNIEnv* env
        , const char* className
        , JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < ) {
        return JNI_FALSE;
    }

    return JNI_TRUE;
}
           

需要定義gMethods JNINativeMethod數組,實際就是修改指針指向執行的函數,進而實作方法注冊

// 該結構表示一個Java本地方法與native層的c方法的映射  
typedef struct {  
    char *name; // Java本地方法名稱  
    char *signature; // Java本地方法簽名  
    void *fnPtr; // c函數指針  
} JNINativeMethod;  

/**
* Table of methods associated with a single class.
*/
static JNINativeMethod gMethods[] =
{
    {"nativePrint", "()V", (void*)native_Print },
    {"nativeCount", "(II)I", (void*)native_Count },
};

// 實際執行函數
void native_Print(JNIEnv * env, jobject thiz) {
    LOGE("native_Print");
}

jint native_Count(JNIEnv * env, jobject thiz, jint a, jint b) {
    return a+b;
}
           

最終執行結果如下:

- :: -/com.example.qiuyu.testhellojni D/: Load MyJni nativeSetup
- :: -/com.example.qiuyu.testhellojni E/jni_thread: native_Print
- :: -/com.example.qiuyu.testhellojni D/: 
           

注:classname千萬不能寫錯,寫錯之後會出現java.lang.NoClassDefFoundError錯誤

注:如果使用ProGuard進行混淆,很可能會找不到native方法,這個要注意

源碼

Github : https://github.com/QyMars/AndroidNativeCode

總結

通過動态注冊,可以避免寫死,更加靈活,下面一章學習Native線程使用,之後要完成如何Native中啟動一個線程來進行簽名校驗判斷。

繼續閱讀