Android NDK - JNI原理和使用技巧
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__)