Android NDK - JNI原理和使用技巧

2019-11-19  本文已影响0人  Lucky胡

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__)

上一篇下一篇

猜你喜欢

热点阅读