源码版本:2.3.x
学习JNI实例:MediaScanner
MediaScanner的功能是扫描媒体文件并将它们存储到媒体数据库中,供其他程序使用
--> MediaScanner.java
--> android_media_MediaScanner.cpp
但是如何确定 Java
层的 native_init
函数对应的就是 JNI
层的 android_media_MediaScanner_native_init
函数。这就涉及到 JNI
函数的注册问题。
所谓 注册 就是将 Java
层声明的 Native
函数与 JNI
层对应的实现函数关联起来。在这个例子中, native_init
函数位于 android.media
包中,全路径名为 android.media.MediaScanner.native_int
,JNI
层函数的名字是 android_media_MediaScanner_native_init
。 JNI
函数的注册方法分为静态注册和动态注册。
使用 Java
的工具程序 javah
,如 javah -o output packagename.classname
。 packagename.classname
是 Java
代码编译后的 class
文件,而在生成的 output.h
文件里,声明了对应的 JNI
层函数,只要实现里面的函数即可。
--> androyid_media_MediaScanner.h::样例文件
缺点:
动态注册
JNI
技术中,有 JNINativeMethod
结构体用来记录 native
函数和 JNI
函数的对应关系。
MediaScanner JNI
层是这么做的~
--> android_media_MediaScanner.cpp
接下来是 registerNativeMethods
的实现
--> AndroidRuntime.cpp
jniRegisterNativeMethods
是 Android
平台为了方便 JNI
使用而提供的帮助函数。
--> JNIHelp.c
真是多重调用...
android_media_MediaScanner::register_android_media_MediaScanner -> AndroidRuntime::registerNativeMethods -> JNIHelp::jniRegisterNativeMethods
但说到最后也就 jniRegisterNativeMethods
中的两步
那么是什么时候完成注册呢?当 Java
层通过 System.loadLibray
加载完 JNI
动态库后,紧接着会查找 JNI_OnLoad
函数,如果有,就调用并进行注册。libmedia_jni.so
的 JNI_OnLoad
函数我们可以在 android_media_MediaPlayer.cpp
中找到。
--> android_media_MediaPlayer.cpp
这里没什么花里胡哨的,就两个表格
可以看到除了 Java
中基本数据类型的数组、Class
、String
、Throwable
外,其余所有的 Java
对象的数据类型在 JNI
中用 jobject
表示。
JNIEnv
是一个与线程相关的代表JNI环境的结构体。
通过调用JNIEnv的一些JNI系统函数进而可以调用 Java
的函数、操作 jobject
对象等很多事情。
操作 jobject
即是操作该对象的成员变量和成员函数,在 JNI
规则中,用 jfieldID
和 jmethodID
表示。
MS中这样使用它们
--> android_media_MediaScanner.cpp::MyMediaScannerClient构造函数
--> android_media_MediaScanner.cpp::MyMediaScannerClient的scanFile
Java
支持函数重载,即可以定义同名但不同参数的函数。仅仅通过函数名无法找到具体函数。为了解决这个问题,JNI
技术中将参数类型和返回值类型的组合作为了一个函数的签名信息,有了签名信息和函数名,就能很顺利的找到 Java
中的函数。
格式: ( 参数 1 类型标示 参数 2 类型标示 ... 参数 n 类型标示 ) 返回值类型标示
举例: ( Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient; )V
Java
中创建的对象最后由垃圾回收器回收和释放内存。
--> 垃圾回收的例子
这个做法会有问题,因为和 save_thiz
对应的 java
层中的 MediaScanner
很有可能已经被垃圾回收了,即 save_thiz
保存的 jobject
可能是个野指针,如果使用它,后果会很严重。在被引用中被清理的原因是: JNI
层使用 save_thiz = thiz
这种语句是不会增加 jobject
的引用计数的。
为此, JNI
提供了三种类型的引用:
--> android_media_MediaScanner.cpp::MyMediaScannerClient构造函数
调用 JNIEnv
的某些函数出错,会产生异常,但直到返回到 Java
层后才会抛出。异常不会中断本地函数的运行,但是只能做资源清理的工作,如果此时调用其他 JNIEnv
函数,则会导致程序死掉。
--> android_media_MediaScanner.cpp::MyMediaScanner的scanFile函数
JNIEnv
提供了三个函数帮助在代码中截获和修改这些异常
public class MediaScanner
{
//加载对应的jni库,库名为libmedia_jni
//在实际加载中会扩展成libmedia_jni.so,Windows平台则是media_jni.dll
static {
System.loadLibrary("media_jni");
native_init();
}
//声明一个native函数,native为Java关键字,表示将由JNI层完成
private static native final void Native_init();
}
//这个函数是native_init的JNI层实现
static void android_media_MediaScanner_native_init(JNIEnv *env)
{
jclass clazz;
clazz = env->FindClass("android/media/MediaScanner");
fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
}
}
#include <jni.h> //必须包含这个文件,否则编译不通过
#ifndef _Included_android_media_MediaScanner
#define _Included_android_media_MediaScanner
#ifdef __cplusplus
extern "C" {
#endif
//native_init对应的JNI函数
//Java层函数名中如果有一个"_",被转换成JNI之后就变成了"_l"。
JNIEXPORT void JNICALL Java_android_media_MediaScanner_native_linit(JNIEnv *, jclass);
}
#endif
#endif
typedef struct {
//Java中native函数的名字,不用携带包的路径,例如"native_init"。
const char* name;
//Java函数的签名信息,用字符串表示,时参数类型和返回值类型的组合
const char* signature;
void* fnPtr; //JNI层对应函数的函数指针, void* 类型
} JNINativeMethod
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)