diff --git a/README.md b/README.md index f0b2be4..35c8e2d 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,10 @@ Android Notes [Android 系统启动](https://github.com/Omooo/Android-Notes/blob/master/blogs/Android/Framework/Android%20%E7%B3%BB%E7%BB%9F%E5%90%AF%E5%8A%A8.md) +《深入理解 Android 卷一》 + +- [深入理解 JNI](https://github.com/Omooo/Android-Notes/blob/master/blogs/Android/Framework/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%20Android%20%E5%8D%B7%E4%B8%80/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%20JNI.md) + #### Java 基础 [final 你需要知道的一切](https://github.com/Omooo/Android-Notes/blob/master/blogs/Java/final.md) diff --git a/blogs/Android/Framework/深入理解 Android 卷一/深入理解 JNI.md b/blogs/Android/Framework/深入理解 Android 卷一/深入理解 JNI.md index 9787179..d117cc2 100644 --- a/blogs/Android/Framework/深入理解 Android 卷一/深入理解 JNI.md +++ b/blogs/Android/Framework/深入理解 Android 卷一/深入理解 JNI.md @@ -4,9 +4,18 @@ #### 目录 -1. 前言 -2. 实例分析之 MediaScanner -3. Java 层的 MediaScanner +1. 思维导图 +2. 前言 +3. 实例分析之 MediaScanner +4. Java 层的 MediaScanner +5. JNI 层 MediaScanner + - JNI 函数注册 + - 垃圾回收 + - JNI 中的异常处理 + +#### 思维导图 + +![](https://i.loli.net/2019/05/23/5ce61fe19639165339.png) #### 前言 @@ -52,7 +61,7 @@ public class MediaScanner implements AutoCloseable { 但是在 JNI 层,要完成的任务可没那么轻松了。 -#### JNI 层 MeidaScanner +#### JNI 层 MediaScanner 看下 android.media.MediaScanner.cpp 源码: @@ -76,6 +85,8 @@ android_media_MediaScanner_processFile( Java 层,native_init 函数位于 android.media 包中,它的全路径名应该是 android.media.MediaScanner.native_init,而 JNI 层函数的名字就是把 "." 替换成了 "_",通过这样,就可以把 Java 中的 Native 函数和 JNI 层的函数关联起来了。 +##### JNI 函数注册 + 其实上面说的就是 JNI 函数的注册问题。注册,即将 Java 层的 native 函数和 JNI 层对应的实现函数关联起来。JNI 函数的注册方法实际上有以下两种: 1. 静态注册 @@ -90,6 +101,134 @@ Java 层,native_init 函数位于 android.media 包中,它的全路径名应 2. 动态注册 - Java native 函数和 JNI 函数是一一对应的,在 JNI 中,用一个叫 JNINativeMethod 的结构来保存这种关联关系。 + Java native 函数和 JNI 函数是一一对应的,在 JNI 中,用一个叫 JNINativeMethod 的结构来保存这种关联关系。定义如下: + + ```c + typedef struct { + //Java 中 native 函数的名字,不用携带包的路径,例如 "native_init" + const char* name; + //Java 函数的签名信息,用字符串表示,是参数类型和返回值类型的组合 + //因为 Java 支持函数重载,所以通过 name 和 signature 可确定唯一 + const char* signature; + //JNI 层对应函数的函数指针,注意它是 void* 类型 + void* fnPtr; + }JNINativeMethod; + ``` + + 然后看下 MediaScanner JNI 层是如何做的? + + ```c + static const JNINativeMethod gMethods[] = { + + { + "processFile", + "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)Z", + (void *)android_media_MediaScanner_processFile + }, + //... + { + "native_init", + "()V", + (void *)android_media_MediaScanner_native_init + } + }; + //注册 JNINativeMethod 数组 + // This function only registers the native methods, and is called from + // JNI_OnLoad in android_media_MediaPlayer.cpp + int register_android_media_MediaScanner(JNIEnv *env) + { + return AndroidRuntime::registerNativeMethods(env, + kClassMediaScanner, gMethods, NELEM(gMethods)); + } + ``` + + AndroidRunTime 类提供了一个 registerNativeMethods 函数来完成注册工作,其实现为: + + ```c + //AndroidRunTime.cpp + /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env, + const char* className, const JNINativeMethod* gMethods, int numMethods) + { + return jniRegisterNativeMethods(env, className, gMethods, numMethods); + } + ``` + + 其中 jniRegisterNativeMethods 是 Android 平台中为了方便 JNI 使用而提供的一个帮助函数,代码为: + + ```c + //JNIHelp.c + MODULE_API int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, + const JNINativeMethod* gMethods, int numMethods) + { + JNIEnv* e = reinterpret_cast(env); + + //因为 JNINativeMethod 使用的函数名并非路径名,所以要先指明是哪个类 + scoped_local_ref c(env, findClass(env, className)); + + //实际上调用 JNIEnv 的 RegisterNatives 函数完成注册 + int result = e->RegisterNatives(c.get(), gMethods, numMethods); + + return 0; + } + ``` + + 以上,在自己的 JNI 层代码中使用这种方法,就可以完成动态注册了。那么这些动态注册的函数在什么时候在上面地方被调用的呢? + + 当 Java 层通过 System.loadLibrary 加载完 JNI 动态库后,紧接着会查找该库中一个叫 JNI_OnLoad 的函数。如果有,就调用它,而动态注册的工作就是在这里完成的。 + + 所以,如果想使用动态注册方法,就必须实现 JNI_OnLoad 函数,只有在这个函数中才有机会完成动态注册的工作。 + + ```c + //android_media_MediaPlayer.cpp + jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) + { + JNIEnv* env = NULL; + jint result = -1; + + if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + goto bail; + } + assert(env != NULL); + + //动态注册 MediaScanner 的 JNI 函数 + if (register_android_media_MediaScanner(env) < 0) { + goto bail; + } + + //... + + /* success -- return valid version number */ + result = JNI_VERSION_1_4; + + bail: + return result; + } + ``` + +##### 垃圾回收 + +Java 中创建的对象最后是由垃圾回收器来回收和释放内存的,如果 Java 层的对象被回收了,那么肯定会影响 JNI 层。JNI 技术一共提供了三种类型的引用,它们分别是: + +- Local Reference 本地引用 + + 在 JNI 层函数中使用的非全局引用对象都是 Local Reference,它包括函数调用时传入的 jobject 和在 JNI 层函数中创建的 jobject。Local Reference 最大的特点就是,一旦 JNI 层函数返回,这些 jobject 就可能被垃圾回收。 + +- Glabal Reference 全局引用 + + 这种对象如不主动释放,它永远不会被垃圾回收。 + +- Weak Global Reference 弱全局引用 + + 一种特殊的 Global Reference,在运行过程中可能会被垃圾回收。所以使用它之前,需要调用 JNIEnv 的 isSameObject 判断它是否被回收了。 + +所以每当 JNI 层想要保存 Java 层中的某个对象时,就可以使用 Global Reference,使用完后记住释放它就可以了。 + +##### JNI 中的异常处理 + +JNI 中也有异常,不过它和 C++、Java 的异常不太一样。如果调用 JNIEnv 的某些函数出错了,则会产生一个异常,但这个异常不会中断本地函数的执行,直到从 JNI 层返回到 Java 层后,虚拟机才会抛出这个异常。虽然在 JNI 层中产生的异常不会中断本地函数的运行,但一旦产生异常后,就只能做一些资源清理工作了(例如释放全局引用,或者 ReleaseStringChars)。如果这时调用除上面所说函数之外的其他 JNIEnv 函数,则会导致程序死掉。 + +JNI 层函数可以在代码中截获和修改这些异常,JNIEnv 提供了三个函数给予帮助: - \ No newline at end of file +1. ExceptionOccured 函数,用来判断是否发生异常 +2. ExceptionClear 函数,用来清理当前 JNI 层中发生的异常 +3. ThrowNew 函数,用来向 Java 层抛出异常 \ No newline at end of file diff --git a/images/Android/Framework/深入理解 Android 卷一/JNI.png b/images/Android/Framework/深入理解 Android 卷一/JNI.png new file mode 100644 index 0000000..cbc9947 Binary files /dev/null and b/images/Android/Framework/深入理解 Android 卷一/JNI.png differ