美文网首页
jni-04、静态注册、动态注册、JavaVM与JNIEnv与j

jni-04、静态注册、动态注册、JavaVM与JNIEnv与j

作者: 喂_balabala | 来源:发表于2022-09-05 19:04 被阅读0次

    动态注册性能优于静态注册

    // 默认情况下,就是静态注册,静态注册是最简单的方式,NDK开发过程中,基本上使用静态注册
    // Android 系统的C++源码:基本上都是动态注册(麻烦)
    
    // 静态注册: 优点:开发简单
    // 缺点
    // 1.JNI函数名非常长
    // 2.捆绑 上层 包名 + 类名
    // 3.运行期 才会去 匹配JNI函数,性能上 低于 动态注册
    
    // 静态注册:
    // new Student.方法
    // new Student.方法
    
    // 动态注册:
    // s = new Student(); 所以的初始化已经做了
    // s.方法
    // s.方法
    

    动态注册 在JNI_OnLoad里面注册函数

    • Java native
        public native void dynamicJavaMethod01(); // 动态注册1
        public native int dynamicJavaMethod02(String valueStr); // 动态注册2
    
    • cpp
    // 日志输出
    #include <android/log.h>
    
    #define TAG "JNISTUDY"
    // __VA_ARGS__ 代表 ...的可变参数
    #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG,  __VA_ARGS__);
    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG,  __VA_ARGS__);
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG,  __VA_ARGS__);
    
    JavaVM *jVm = nullptr; // 0x003545 系统乱值,C++11后,取代NULL,作用是可以初始化指针赋值
    const char *mainActivityClassName = "com/derry/as_jni_project/MainActivity";
    
    // native 真正的函数
    // void dynamicMethod01(JNIEnv *env, jobject thiz) { // OK的
    void dynamicMethod01() { // 也OK  如果你用不到  JNIEnv jobject ,可以不用写
        LOGD("我是动态注册的函数 dynamicMethod01...");
    }
    
    int dynamicMethod02(JNIEnv *env, jobject thiz, jstring valueStr) { // 也OK
        const char *text = env->GetStringUTFChars(valueStr, nullptr);
        LOGD("我是动态注册的函数 dynamicMethod02... %s", text);
        env->ReleaseStringUTFChars(valueStr, text);
        return 200;
    }
    
    /*
         typedef struct {
            const char* name;       // 函数名
            const char* signature; // 函数的签名
            void*       fnPtr;     // 函数指针
         } JNINativeMethod;
         */
    static const JNINativeMethod jniNativeMethod[] = {
            {"dynamicJavaMethod01", "()V",                   (void *) (dynamicMethod01)},
            {"dynamicJavaMethod02", "(Ljava/lang/String;)I", (int *) (dynamicMethod02)},
    };
    
    
    // Java:像 Java的构造函数,如果你不写构造函数,默认就有构造函数,如果你写构造函数 覆写默认的构造函数
    // JNI JNI_OnLoad函数,如果你不写JNI_OnLoad,默认就有JNI_OnLoad,如果你写JNI_OnLoad函数 覆写默认的JNI_OnLoad函数
    extern "C"
    JNIEXPORT jint JNI_OnLoad(JavaVM *javaVm, void *) {
        // this.javaVm = javaVm;
        ::jVm = javaVm;
    
        // 做动态注册 全部做完
    
        JNIEnv *jniEnv = nullptr;
        int result = javaVm->GetEnv(reinterpret_cast<void **>(&jniEnv), JNI_VERSION_1_6);
    
        // result 等于0  就是成功    【C库 FFmpeg 成功就是0】
        if (result != JNI_OK) {
            return -1; // 会奔溃,故意奔溃
        }
    
        LOGE("System.loadLibrary ---》 JNI Load init");
    
        jclass mainActivityClass = jniEnv->FindClass(mainActivityClassName);
    
        // jint RegisterNatives(Class, 我们的数组==jniNativeMethod, 注册的数量 = 2)
        jniEnv->RegisterNatives(mainActivityClass,
                                jniNativeMethod,
                                sizeof(jniNativeMethod) / sizeof(JNINativeMethod));
    
        LOGE("动态 注册没有毛病");
    
        return JNI_VERSION_1_6; //  // AS的JDK在JNI默认最高1.6      存Java的JDKJNI 1.8
    }
    

    JNIEnv、jobject 都不能跨进程,会崩溃

    • Java 层代码
    public native void naitveThread(); // Java层 调用 Native层 的函数,完成JNI线程
    
    /**
     * TODO 下面是 被native代码调用的 Java方法
     * 第二部分 JNI线程
     */
    public void updateActivityUI() {
        if (Looper.getMainLooper() == Looper.myLooper()) { // TODO C++ 用主线程调用到此函数 ---->  主线程
            new AlertDialog.Builder(MainActivity.this)
                    .setTitle("UI")
                    .setMessage("updateActivityUI Activity UI ...")
                    .setPositiveButton("老夫知道了", null)
                    .show();
        } else {  // TODO  C++ 用异步线程调用到此函数 ---->  异步线程
            Log.d(TAG, "updateActivityUI 所属于子线程,只能打印日志了..");
    
            runOnUiThread(new Runnable() { // 哪怕是异步线程  UI操作 正常下去 runOnUiThread
                @Override
                public void run() {
    
                    // 可以在子线程里面 操作UI
                    new AlertDialog.Builder(MainActivity.this)
                            .setTitle("updateActivityUI")
                            .setMessage("所属于子线程,只能打印日志了..")
                            .setPositiveButton("老夫知道了", null)
                            .show();
                }
            });
        }
    }
    
    • cpp
    class MyContext {
    public:
        JNIEnv *jniEnv = nullptr;  // 不能跨线程 ,会奔溃
        jobject instance = nullptr; // 不能跨线程 ,会奔溃
    };
    
    void *myThreadTaskAction(void *pVoid) { // 当前是异步线程
        LOGE("myThreadTaskAction run");
    
        // 需求:有这样的场景,例如:下载完成 ,下载失败,等等,必须告诉Activity UI端,所以需要在子线程调用UI端
    
        // 这两个是必须要的
        // JNIEnv *env
        // jobject thiz   OK
    
        MyContext * myContext = static_cast<MyContext *>(pVoid);
    
        // jclass mainActivityClass = myContext->jniEnv->FindClass(mainActivityClassName); // 不能跨线程 ,会奔溃
        // mainActivityClass = myContext->jniEnv->GetObjectClass(myContext->instance); // 不能跨线程 ,会奔溃
    
        // TODO 解决方式 (安卓进程只有一个 JavaVM,是全局的,是可以跨越线程的)
        JNIEnv * jniEnv = nullptr; // 全新的JNIEnv  异步线程里面操作
        jint attachResult = ::jVm->AttachCurrentThread(&jniEnv, nullptr); // 附加当前异步线程后,会得到一个全新的 env,此env相当于是子线程专用env
        if (attachResult != JNI_OK) {
            return 0; // 附加失败,返回了
        }
    
        // 1.拿到class
        jclass mainActivityClass = jniEnv->GetObjectClass(myContext->instance);
    
        // 2.拿到方法
        jmethodID updateActivityUI = jniEnv->GetMethodID(mainActivityClass, "updateActivityUI", "()V");
    
        // 3.调用
        jniEnv->CallVoidMethod(myContext->instance, updateActivityUI);
    
        ::jVm->DetachCurrentThread(); // 必须解除附加,否则报错
    
        LOGE("C++ 异步线程OK")
    
        return nullptr;
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_derry_as_1jni_1project_MainActivity_naitveThread(JNIEnv *env, jobject job) { // 当前是主线程
        /*pthread_t pid;
        pthread_create(&pid, nullptr, myThreadTaskAction, nullptr);
        pthread_join(pid, nullptr);*/
    
        MyContext * myContext = new MyContext;
        myContext->jniEnv = env;
        // myContext->instance = job; // 默认是局部引用,会奔溃
        myContext->instance = env->NewGlobalRef(job); // 提升全局引用
    
        pthread_t pid;
        pthread_create(&pid, nullptr, myThreadTaskAction, myContext);
        pthread_join(pid, nullptr);
    }
    

    JavaVM、JNIEnv、jobject地址问题

    • Java
    public class MainActivity extends AppCompatActivity {
    
        public native void nativeFun1();
        public native void nativeFun2(); // 2
        public static native void staticFun3(); // 3
        public static native void staticFun4();
        
        public void clickMethod4(View view) {
            nativeFun1(); // main线程调用的
            nativeFun2(); // main线程调用的
            staticFun3(); // main线程调用的
            // 第四个  new Thread 调用  ThreadClass == clasz 当前函数clazz地址
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    staticFun4(); // Java的子线程调用
                }
            }.start();
        }
        
         public void clickMethod5(View view) {
            startActivity(new Intent(this, MainActivity2.class));
        }
        
    }
    
    public class MainActivity2 extends AppCompatActivity {
    
        public native void nativeFun5();
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            nativeFun5(); // main线程调用的
        }
    }
    
    • cpp
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_derry_as_1jni_1project_MainActivity_nativeFun1(JNIEnv *env, jobject job) {
        JavaVM * javaVm = nullptr;
        env->GetJavaVM(&javaVm);
    
        // 打印:当前函数env地址, 当前函数jvm地址, 当前函数job地址,  JNI_OnLoad的jvm地址
        LOGE("nativeFun1 当前函数env地址%p,  当前函数jvm地址:%p,  当前函数job地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, job, ::jVm);
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_derry_as_1jni_1project_MainActivity_nativeFun2(JNIEnv *env, jobject job) {
        JavaVM * javaVm = nullptr;
        env->GetJavaVM(&javaVm);
    
        // 打印:当前函数env地址, 当前函数jvm地址, 当前函数job地址,  JNI_OnLoad的jvm地址
        LOGE("nativeFun2 当前函数env地址%p,  当前函数jvm地址:%p,  当前函数job地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, job, ::jVm);
    }
    
    void * run(void *) { // native的子线程 env地址  和  Java的子线程env地址,一样吗  不一样的
        JNIEnv * newEnv = nullptr;
        ::jVm->AttachCurrentThread(&newEnv, nullptr);
        // 打印:当前函数env地址, 当前函数jvm地址, 当前函数clazz地址,  JNI_OnLoad的jvm地址
    
        LOGE("run jvm地址:%p,  当前run函数的newEnv地址:%p \n", ::jVm, newEnv);
    
        ::jVm->DetachCurrentThread();
        return nullptr;
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_derry_as_1jni_1project_MainActivity_staticFun3(JNIEnv *env, jclass clazz) {
        JavaVM * javaVm = nullptr;
        env->GetJavaVM(&javaVm);
    
        // 打印:当前函数env地址, 当前函数jvm地址, 当前函数clazz地址,  JNI_OnLoad的jvm地址
        LOGE("nativeFun3 当前函数env地址%p,  当前函数jvm地址:%p,  当前函数clazz地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, clazz, ::jVm);
    
        // 调用run
        pthread_t pid;
        pthread_create(&pid, nullptr, run, nullptr);
    }
    
    // Java子线程调用的
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_derry_as_1jni_1project_MainActivity_staticFun4(JNIEnv *env, jclass clazz) {
        JavaVM * javaVm = nullptr;
        env->GetJavaVM(&javaVm);
    
        // 打印:当前函数env地址, 当前函数jvm地址, 当前函数clazz地址,  JNI_OnLoad的jvm地址
        LOGE("nativeFun4 当前函数env地址%p,  当前函数jvm地址:%p,  当前函数clazz地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, clazz, ::jVm);
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_derry_as_1jni_1project_MainActivity2_nativeFun5(JNIEnv *env, jobject job) {
        JavaVM * javaVm = nullptr;
        env->GetJavaVM(&javaVm);
    
        // 打印:当前函数env地址, 当前函数jvm地址, 当前函数clazz地址,  JNI_OnLoad的jvm地址
        LOGE("nativeFun5 当前函数env地址%p,  当前函数jvm地址:%p,  当前函数job地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, job, ::jVm);
    }
    
    • 打印的log
    nativeFun1 当前函数env地址0x754b4c5460,  当前函数jvm地址:0x754b4bd6c0,  当前函数job地址:0x7fc4fa9d64, JNI_OnLoad的jvm地址:0x754b4bd6c0
    nativeFun2 当前函数env地址0x754b4c5460,  当前函数jvm地址:0x754b4bd6c0,  当前函数job地址:0x7fc4fa9d64, JNI_OnLoad的jvm地址:0x754b4bd6c0
    nativeFun3 当前函数env地址0x754b4c5460,  当前函数jvm地址:0x754b4bd6c0,  当前函数clazz地址:0x7fc4fa9db4, JNI_OnLoad的jvm地址:0x754b4bd6c0
    run jvm地址:0x754b4bd6c0,  当前run函数的newEnv地址:0x754b4c81e0
    nativeFun4 当前函数env地址0x7542e22840,  当前函数jvm地址:0x754b4bd6c0,  当前函数clazz地址:0x7518bfe984, JNI_OnLoad的jvm地址:0x754b4bd6c0
    nativeFun5 当前函数env地址0x754b4c5460,  当前函数jvm地址:0x754b4bd6c0,  当前函数job地址:0x7fc4fa9cb4, JNI_OnLoad的jvm地址:0x754b4bd6c0
    

    结论

    1. JavaVM全局,绑定当前进程, 只有一个地址
    2. JNIEnv线程绑定, 绑定主线程,绑定子线程
    3. jobject 谁调用JNI函数,谁的实例会给jobject
    • JNIEnv *env 不能跨越线程,否则奔溃, 他可以跨越函数 【解决方式:使用全局的JavaVM附加当前异步线程 得到权限env操作】
    • jobject thiz 不能跨越线程,否则奔溃,不能跨越函数,否则奔溃 【解决方式:默认是局部引用,提升全局引用,可解决此问题】
    • JavaVM 能够跨越线程,能够跨越函数

    相关文章

      网友评论

          本文标题:jni-04、静态注册、动态注册、JavaVM与JNIEnv与j

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