天天看点

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中启动一个线程来进行签名校验判断。

继续阅读