JNI静态注册和动态注册

作者: 千浪 | 来源:发表于2020-06-11 13:41 被阅读0次

    JNI开发看似简单,但是初学者,通过搜索引擎东拼西凑的资料来写代码,几乎一定会踩坑,比方内存泄露,引用泄漏以及各种崩溃。本来代码只写了一小时,但是解决问题却花了一天。这篇文章的目的就是系统的讲解常见的这些问题,除了官方文档外,让你不再需要参考网上任何一篇文章。

    静态注册和动态注册

    静态注册

    现在在Android Studio上可以直接创建“Native C++”的模版工程。native-lib.cpp使用了静态注册,最终会编译出“libnative-lib.so”,文件内容如下:

    #include <jni.h>
    #include <string>
    
    extern "C" JNIEXPORT jstring JNICALL
    Java_com_jianshu_qianlang_jnitutorial_MainActivity_stringFromJNI(
            JNIEnv* env,
            jobject /* this */) {
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
    
    

    对应的Java native方法如下:

    package com.jianshu.qianlang.jnitutorial;
    public class MainActivity extends AppCompatActivity {
        /**
         * A native method that is implemented by the 'native-lib' native library,
         * which is packaged with this application.
         */
        public native String stringFromJNI();
    }
    

    静态注册之所以能被Java虚拟机通过指定的规则访问到,是因为动态库中导出了指定的符号。JNI中使用了“JNIEXPORT”宏进行符号的导出。

    #define JNIIMPORT
    #define JNIEXPORT  __attribute__ ((visibility ("default")))
    #define JNICALL
    

    attribute ((visibility ("default")))即是将当前符号导出。

    在NDK的目录下有“arm-linux-androideabi-nm”命令,可以用来查看导出的符号:

    toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-nm
    

    “-D” 参数表示 “Display dynamic symbols instead of normal symbols”

    arm-linux-androideabi-nm -D libnative-lib.so
    

    命令执行后显示,我们截取片段如下,会看到第一个就是我们要导出的符号 “ Java_com_autonavi_minimap_myapplication_MainActivity_stringFromJNI”:

    00008bf8 T Java_com_autonavi_minimap_myapplication_MainActivity_stringFromJNI
    00009692 T _ZN10__cxxabiv119__getExceptionClassEPK21_Unwind_Control_Block
    0000968c T _ZN10__cxxabiv119__setExceptionClassEP21_Unwind_Control_Blocky
    00009698 T _ZN10__cxxabiv121__isOurExceptionClassEPK21_Unwind_Control_Block
    00008ca2 W _ZN7_JNIEnv12NewStringUTFEPKc
    000124fc T _ZNKSt10bad_typeid4whatEv
    0000a7c8 T _ZNKSt11logic_error4whatEv
    0000a714 T _ZNKSt13bad_exception4whatEv
    ...
    

    所以按照指定的规则——“包名+类名+方法名”,在编译期就生成并导出相应的符号,Java虚拟机在执行时便可通过Java方法查找到对应的Native方法,这便是被称为静态注册的原因。
    静态注册的函数会在运行时虚拟机会通过dlsym调用。

    动态注册

    JNI_OnLoad是System.loadLibrary之后加载的第一个方法,所以需要在JNI_OnLoad方法进行动态注册
    以下是完整的示意代码:

    #include <jni.h>
    #include <string>
    
    jstring stringFromJNI(JNIEnv* env, jobject /* this */) {
        std::string hello = "Hello from C++ dynamic";
        return env->NewStringUTF(hello.c_str());
    }
    
    jint RegisterNatives(JNIEnv *env) {
        // 反射Java类
        jclass clazz = env->FindClass("com/jianshu/qianlang/jnitutorial/MainActivity");
        // 如果因修改了包名或者类不存在则反射失败
        if (clazz == NULL) {
            return JNI_ERR;
        }
        
        // 方法数组,分别为:
        // 方法名 | 方法签名 | 函数指针
        JNINativeMethod methods_MainActivity[] = {
                {"stringFromJNI", "()Ljava/lang/String;", (void *) stringFromJNI},
        };
    
        jint nMethods = sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]);
        return env->RegisterNatives(clazz, methods_MainActivity, nMethods);
    }
    
    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
        // 首先获取JNIEnv
        JNIEnv* env = NULL;
        if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
            return JNI_ERR;
        }
        
        // 动态注册方法
        if (RegisterNatives(env) != JNI_OK) {
            return JNI_ERR;
        }
    
        return JNI_VERSION_1_6;
    }
    

    从 JNI_OnLoad 进行的任何 FindClass 调用都会在用于加载共享库的类加载器上下文中解析类。从其他上下文调用时,FindClass 会使用与 Java 堆栈顶部的方法相关联的类加载器,如果没有(因为调用来自刚刚附加的原生线程),则会使用“系统类加载器”。
    由于系统类加载器不知道应用的类,因此您将无法在该上下文中使用 FindClass 查找您自己的类。这使得 JNI_OnLoad 成为了查找和缓存类的便捷位置:一旦有了有效的 jclass,您就可以从任何附加的线程使用它。

    两者比较

    动态注册可以预先检查符号是否存在,比方要反射的类或者方法不存在时能主动返回错误,而且还可以通过只导出 JNI_OnLoad 来获得规模更小、速度更快的共享库。
    静态注册的优势在于要写的代码更少一些。
    综上所述推荐大家使用动态注册的方式。

    动态注册时的方法签名

    动态注册需要获取Java方法的签名,可以使用javap命令。参考:javap 获取JNI方法签名

    本专题的其他内容

    - JavaVM 和 JNIEnv
    - javap 获取JNI方法签名
    - JNI静态注册和动态注册

    参考

    Android Developers - JNI tips
    Java Native Interface Specification—Contents

    相关文章

      网友评论

        本文标题:JNI静态注册和动态注册

        本文链接:https://www.haomeiwen.com/subject/efemtktx.html