美文网首页
Android NDK - JNI原理和使用技巧

Android NDK - JNI原理和使用技巧

作者: Lucky胡 | 来源:发表于2019-11-19 17:37 被阅读0次

    JNI Reference

    JNI Reference

    一切都是LocalReference

    LocalReference

    局部引用引起泄漏导致crash

        jsize size = env->GetArrayLength(messages);
        //这里如果size太大,一直不销毁局部引用的话,会导致泄漏crash
        for (int i = 0; i < size; ++i) {
            //这里是一个局部引用
            jobject message = env->GetObjectArrayElement(messages,i);
            //使用完需要销毁局部引用
            env->DeleteLocalRef(message);
        }
    

    DeleteLocalRef是结束一个局部变量的生命周期,也可以直接结束一组局部变量。

        jobject message = env->GetObjectArrayElement(messages,1);
    
        //开始加入局部变量,参数为预估可能会生成多少个局部变量
        env->PushLocalFrame(3);
    
        //开始生成局部变量
        jobject local1 = env->NewLocalRef(message);
        jobject local2 = env->NewLocalRef(message);
        jobject local3 = env->NewLocalRef(message);
        jobject local4 = env->NewLocalRef(message);
    
        jstring s = env->GetStringUTFChars(jstring1,NULL);
    
        //中间生成了多个局部引用
        //调用PopLocalFrame一次性将上面生成的局部引用全部销毁
        env->PopLocalFrame(NULL);
    

    可以在需要生成局部引用前,先判断是否还有足够的局部引用容量。

    //参数3代表我需要生成3个局部引用,看是否还有足够空间生成3个,没有则返回负数
        jint result = env->EnsureLocalCapacity(3);
        if (result < 0) {
            //异常处理,说明没有容量来生成新的局部引用了
        } else{
            //可以生成新的局部引用
        }
    

    全局引用 GlobalReference

    
    //全局变量,提供给之后使用
    jclass messageClz;
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_google_android_filament_RenderableManager_setMessage(JNIEnv *env, jclass clazz,
                                                                  jobject message) {
        //由于查找类的方法很耗时,所以需要保存下来,以后继续使用
        //此时需要保存为全局引用,供以后调用
        if (messageClz == NULL) {
            //这里生成的引用是局部引用,当这个方法结束后,该局部引用就销毁了,无法使用了
            jclass clz = env->GetObjectClass(message);
            messageClz = static_cast<jclass>(env->NewGlobalRef(clz));
            //也可以生成弱全局引用,当java GC时,会被删除
    //        messageClz = static_cast<jclass>(env->NewWeakGlobalRef(clz));
        }
    
        //全局引用和局部引用一样,也是有容量的,不能无限制的生成全局引用
        //在最后使用完后需要删除
        //env->DeleteGlobalRef(messageClz);
        //删除弱全局引用
    //    env->DeleteWeakGlobalRef(messageClz);
    }
    

    由于jmethodID和jfieldID不是jobject,不会被局部引用删除,所以可以直接赋值给全局变量。

    初始化

    只调用一次,在so加载的时候。
    JNI_OnLoad()

    在so取消加载的时候调用一次。
    JNI_OnUnLoad()

    namespace filament {
        extern jint JNI_OnLoad(JavaVM* vm, void* reserved);
    };
    
    jint JNI_OnLoad(JavaVM* vm, void* reserved) {
        JNIEnv* env;
        if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
            return -1;
        }
    
        registerCallbackUtils(env);
        registerMaterial(env);
        registerNioUtils(env);
    
    #if ANDROID
        ::filament::JNI_OnLoad(vm, reserved);
    #endif
    
        return JNI_VERSION_1_6;
    }
    
    

    流程优化

    常规JNI开发流程
    1、生成java文件
    2、编译后生成class文件,在build/tmp/kotlin-classes-debug
    java的在build/intermediates/classes
    如果使用了Android的类,需要引入classpath
    javah -d jni -cp /Users/junhu/Library/Android/sdk/platforms/android-28/android.jar:. com.hujun.opengldemo.jni.Jni

    3、javah生成.h头文件
    4、在C文件里实现

    缺点:
    1、命名方式很受限,需要包名、类名;
    2、虚拟机查找的性能损耗

    RegisterNatives

    利用RegisterNatives方法,可以一次性将所有java方法和native方法进行绑定。
    1、在java类中声明native方法。
    类名:com.google.android.filament.RenderableManager

        private static native void sayHello();
        private static native String getMyName(long userId);
    

    2、编译生成class然后javah生成.h头文件
    这一步不执行也可以,主要是为了自动生成签名,避免手写出错。
    com_google_android_filament_RenderableManager.h

    /*
     * Class:     com_google_android_filament_RenderableManager
     * Method:    sayHello
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_com_google_android_filament_RenderableManager_sayHello
      (JNIEnv *, jclass);
    
    /*
     * Class:     com_google_android_filament_RenderableManager
     * Method:    getMyName
     * Signature: (J)Ljava/lang/String;
     */
    JNIEXPORT jstring JNICALL Java_com_google_android_filament_RenderableManager_getMyName
      (JNIEnv *, jclass, jlong);
    

    3、重写JNI_OnLoad()方法

    jint JNI_OnLoad(JavaVM* vm, void* reserved) {
        JNIEnv* env;
        if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
            return -1;
        }
        onLoad(vm);
        return JNI_VERSION_1_6;
    }
    
    void JNI_OnUnload(JavaVM* vm, void* reserved){
        onUnLoad();
    };
    

    4、注册方法

    
    //com.google.android.filament.Message
    //Lcom/google/android/filament/RenderableManager
    static const char *classPathName = "com/google/android/filament/RenderableManager";
    
    /**
    typedef struct {
        const char* name;
        const char* signature;
        void*       fnPtr;
    } JNINativeMethod;
    */
    
    
    JavaVM *vm;
    
    void sayHello() {
        printf("hello");
    }
    
    //    private static native String getMyName(long userId);
    jstring getMyName(jlong id) {
        JNIEnv *env;
        vm->AttachCurrentThread(&env, NULL);
        return env->NewStringUTF("myname");
    }
    
    //    private static native void sayHello();
    //    private static native String getMyName(long userId);
    static JNINativeMethod methods[] = {
            {
                    "sayHello",  "()V",                   (void *) sayHello
            },
            {
                    "getMyName", "(J)Ljava/lang/String;", (void *) getMyName
            }
    };
    
    void onLoad(JavaVM *vm) {
        ::vm = vm;
        JNIEnv *env;
        vm->AttachCurrentThread(&env, NULL);
        jclass methodClz = env->FindClass(classPathName);
        int res = env->RegisterNatives(methodClz, methods, sizeof(methods) / sizeof(methods[0]));
        printf("RegisterNatives %d", res);
    }
    
    void onUnLoad() {
        JNIEnv *env;
        vm->AttachCurrentThread(&env, NULL);
        jclass methodClz = env->FindClass(classPathName);
        env->UnregisterNatives(methodClz);
    }
    

    Android JNI

    可以在jni层直接调用Android的API。
    1、首先需要链接Android的动态库。
    如下就链接了log库和android库。

        target_link_libraries(filament-jni
                log
                android
                )
    

    2、在C文件里引用头文件

    #include <android/log.h>
    #include <android/asset_manager.h>
    

    3、使用Android JNI函数

        //调用日志打印
        __android_log_print(ANDROID_LOG_DEBUG,"JNI","getMyName");
        //宏定义的log打印
        LOGD(TAG,"getMyName");
    

    其中宏定义为:

    #include <android/log.h>
    
    #define LOGD(TAG,...) __android_log_print(ANDROID_LOG_VERBOSE,TAG,__VA_ARGS__)
    
    

    相关文章

      网友评论

          本文标题:Android NDK - JNI原理和使用技巧

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