[卷I]第2章 深入理解JNI
JAVA世界 | MediaScanner | MediaScanner.java |
native_init(); processFile(); |
JNI层 | libmedia_jni.so | android_media_mediaScanner.cpp | android_media_MediaScanner_native_init |
Natvie世界 | libmedia.so |
关注:native_init 与 android_media_MediaScanner_native_init是如何对应的?注册。
注册有静态注册和动态注册两种:
1、静态注册,编写MediaScanner.java java -o javah -o 生成android_media_MediaScanner.h
JNIEXPORT void JNICALL Java_android_media_MediaScanner_native_linit
JNIEXPORT void JNICALL Java_android_media_MediaScanner_processFile
2、动态注册
由结构体JNINativeMethod保存对应关系
由register_android_media_MediaScanner(JNIEnv *env) 调用注册函数
而register_android_media_MediaScanner由JNI_OnLoad调用(JNI_OnLoad需要自己实现)
因为:当JAVA层通过System.loadLibrary加载完JNI动态库后,紧接着就会查找库中的JNI_OnLoad函数。
2.4.3 JNIEnv介绍
JNIEnv 与线程相关的代表JNI环境的结构体。
JNIEnv一般都是native函数转换成JNI层函数后由虚拟机传进来的,可以直接使用。
但如果是后台线程收到一个网络消息,而又需要由native层函数主动回调Java层函数时,JNIEnv从何而来?
注意:
jint JNI_OnLoad(JavaVM* vm, void* reserved)
一个进程对应一个JavaVM,即JavaVM是与进程相关的。
调用JavaVM的AttachCurrentThread函数,就可得到这个线程的JNIEnv结构体。另外,在后台线程退出前,需要调用JavaVM的DetachCurrentThread函数来释放对应的资源。
2.4.4 通过JNIEnv操作jobject
先写一下体会:
在JNI层,先通过GetFieldID,取得在Java层定义的成员变量,如mNativeContext(在MediaScanner.java中private int mNativeContext;)
给jfieldID:fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
且fields是文件中的全局变量。
在Java层会通过native函数调用,到JNI层调用设置来设置Java层的mNativeContext。(呵呵,这是用下层的方法设置自己层的变量啊)。
MediaScanner.java native_setup();
android_media_MediaScanner.cpp android_media_MediaScanner_native_setup env->SetIntField(thiz, fields.context, (int)mp);
当然,设置好后,JNI层可以通过(MediaScanner *)env->GetIntField(thiz, fields.context);直接使用。
在MediaScanner.java中,代码逻辑非常明确:
加载完jni库后调用native_init,会让jni层取得mNativeContext
创建MediaScanner对象时,会调用native_setup,将StagefrightMediaScanner对象指针给mNativeContext;
对象销毁时,调用native_finalize,将mNativeContext设为0。
------------------------------------------------------------------------------------------------------------
JNI层会用jobject来表示对象的数据类型。
在JNI规则中,用jfiledID和jmethodID来表示Java类的成员变量和成员函数,可通过JNIEnv的下面两个函数得到:
jfieldID GetField(jclass clazz, const char * name, const char *sig);
jmethodID GetMethodID(jclass clazz, const char * name, const char *sig);
jclass代表java类,name表示成员函数或成员变量的名字,sig为这个函数和变量的签名信息。
调用 GetMethodID 保存为成员变量,为了程序运行效率。
mScanFileMethodID = env->GetMethodID(
mediaScannerClientInterface,
"scanFile",
"(Ljava/lang/String;JJZZ)V");
取得Java对象的方法scanFile。
mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
fileSize, isDirectory, noMedia);
CallVoidMethod是调用Java对象的函数scanFile。
2.4.5 jstring介绍
调用JNIEnv的NewString(JNIEnv *env, const jchar* unicodeChars, jsize len),可以从Native的字符串得到一个jstring对象。
调用JNIEnv的NewStringUTF将根据Native的一个UTF-8字符串得到一个jstring对象。
GetStringChars GetStringUTFChars 可以将Java String对象转换成本地字符串。
需要调用ReleaseStringChars ReleaseStringUTFChars释放资源, 否则会导致JVM内存泄露。
2.4.7 垃圾回收
JNI技术一共提供三种类型的引用:
Local Reference 本地引用,一旦JNI层函数返回,就可能被垃圾回收。
Global Reference 全局引用,不主动释放,永远不会被回收。
Weak Global Reference 弱全局引用,一种特殊的Global Reference,在运行中可能被回收,所以在使用之前需要调用JNIEnv的IsSameObject判断。
根据Local Reference的说明,函数返回后,对象就会被回收,看起来调用DeleteLocalRef是多余的,其实,调用DeleteLocalRef是立即回收。
所以,没有及时回收Local Reference或许是进程占用内存过多的一个原因。