一、分析的文件路径
- ./frameworks/base/media/java/android/media/MediaScanner.java./frameworks/base/media/jni/android_media_MediaScanner.cpp./frameworks/base/media/jni/android_media_MediaPlayer.cpp./frameworks/base/media/jni/AndroidRuntime.cpp./libnativehelper/JNIHelp.cpp
二、代码分析 1. java 层
- // frameworks/base/media/java/android/media/MediaScanner.javapublic class MediaScanner { static {//class被加载的时候自动掉用static里边的函数,,java的基础,, /*加载对应的JNI库*/ System.loadLibrary("media_jni"); //这里负责加载JNI模块编译出来的库。在实际加载动态库时会将其拓展为libmedia_jni.so。 native_init(); //调用native_init()函数,这个函数是对应的cpp文件里边的函数 } ........ //声明一个native函数,native为java关键字,表示它将由JNI层完成 private static native final void native_init(); private native final void native_setup(); ........}
注:native_init 函数位于 android.media 这个包中,其全路径名称为 android.media.MediaScanner.nantive_init。
根据规则其对应的 JNI 层函数名称为:android_media_MediaScanner_native_init。
2. JNI 层
- //frameworks/base/media/jni/android_media_MediaScanner.cpp//以下是frameworks/base/media/jni/Android.mk的编译脚本/*LOCAL_SRC_FILES:= \ ...\ android_media_MediaScanner.cpp \ ...\LOCAL_MODULE:= libmedia_jni //编译生成库的名字为libmedia_jni.soinclude $(BUILD_SHARED_LIBRARY)*/static const char* const kClassMediaScanner = //MediaScanner.java的路径!! "android/media/MediaScanner"; ......../*native_init函数的JNI层实现*/static void android_media_MediaScanner_native_init(JNIEnv *env){ ALOGV("native_init"); //FindClass根据路径寻找java class! jclass clazz = env->FindClass(kClassMediaScanner); if (clazz == NULL) { return; } fields.context = env->GetFieldID(clazz, "mNativeContext", "I"); if (fields.context == NULL) { return; }}
2.1 JNI 注册方法静态注册
大体的流程如下:
1. 先编译 java 代码,然后编译生成. class 文件
2. 使用 java 的工具程序 javah,如 javah -o output packagename.classname,这样它会生成一个叫 output.h 的 JNI 层头文件。这里 packagename.classname 就是上面编译生成的 class 文件,而在这里生成的 output.h 文件里则声明了对应的 JNI 层函数,只要实现里边的函数即可。
静态注册中,java 函数是怎么找到对应的 jni 函数的?其实就是用名字找到的。比如在 java 中调用 native_init 函数的时候,它就会在 JNI 库中寻找 android_media_MediaScanner_native_init 函数,如果没有就会报错。如果找到,则会为这两个函数建立连接,其实就是保存 JNI 层函数的函数指针。以后再调用 native_init 函数时,直接使用这个函数指针就可以了,当然这项工作是虚拟机完成的。
动态注册
上面说过 java native 函数和 JNI 函数是一一对应的,所以动态注册方式就采用 JNINativeMethod 的结构体来记录这种关系。JNINativeMethod 都是定义在各自的 JNI 层文件中。
- typedef struct {
- const char * name; //保存JNI对应的java函数的名字,比如"native_init",不用加路径 const char* signature;//保存java函数的签名信息,用字符串表示,是参数类型和返回值类型的组合 void* fnPtr;//JNI层对应函数的函数指针,注意它是void *类型} JNINativeMethod;/*比如android_media_MediaScanner.cpp文件中,定义了如下一个JNINativeMethod数组*/static JNINativeMethod gMethods[] = {{ "processDirectory",//java中native函数的函数名 //processFile的签名信息 "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V", (void *)android_media_MediaScanner_processDirectory//JNI层对应的函数指针},.......{ "native_init",//java中native函数的函数名 "()V",//native_init函数的签名信息 (void *)android_media_MediaScanner_native_init /*JNI层native_init函数的函数指针*/ },{ "native_setup", "()V", (void *)android_media_MediaScanner_native_setup},.......};/*注册JNINativeMethod数组*/int register_android_media_MediaScanner(JNIEnv *env){ return AndroidRuntime::registerNativeMethods(env, kClassMediaScanner, gMethods, NELEM(gMethods));}
接着调用 AndroidRuntime 中的 registerNativeMethods:
- //AndroidRuntime.cpp/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods){ return jniRegisterNativeMethods(env, className, gMethods, numMethods); }//JNIHelp.cppextern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods){ JNIEnv* e = reinterpret_cast(env); ALOGV("Registering %s natives", className); scoped_local_ref c(env, findClass(env, className)); if (c.get() == NULL) { ALOGE("Native registration unable to find class '%s', aborting", className); abort(); } if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { ALOGE("RegisterNatives failed for '%s', aborting", className); abort(); } return 0;}
上面这些说了 JNI 层怎么定义的注册函数等等,那上面的 register_android_media_MediaScanner() 这种注册函数是在什么时间被调用来完成注册的呢??
当 Java 层通过 System.loadLibrary 加载完 JNI 动态库后,紧接着会查找一个 JNI_OnLoad 的函数。如果有就调用它,动态注册的工作在这里完成。
- //android_media_MediaPlayer.cppjint JNI_OnLoad(JavaVM* vm, void* reserved){ JNIEnv* env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { ALOGE("ERROR: GetEnv failed\n"); goto bail; } assert(env != NULL); ...... //下面可以看到有调用动态注册函数!! if (register_android_media_MediaScanner(env) < 0) { ALOGE("ERROR: MediaScanner native registration failed\n"); goto bail; } ....... /* success -- return valid version number */ result = JNI_VERSION_1_4;bail: return result;}
2.2 数据类型转换
java 层的数据类型和 Jni 层的数据类型的转换关系
JavaNative 类型符号属性字长 booleanjboolean 无符号 8 位 bytejbyte 无符号 8 位 charjchar 无符号 16 位 shortjshort 有符号 16 位 intjint 有符号 32 位 longjlong 有符号 64 位 floatjfloat 有符号 32 位 doublejdouble 有符号 64 位
Java 的基本类型和 Native 层的基本类型转换非常简单,不过必须注意转换成 Native 类型后对应数据类型的字长,例如 jchar 在 Native 语言中是 16 位,占两个字节,这和普通的 char 占一个自己的情况是不一样的。
下面是 Java 引用数据类型和 Native 类型的转换表
Java 引用类型 Native 类型 All objectsjobjectjava.lang.Class 实例 jclassjava.lang.String 实例 jstringObject[]jobjectArrayboolean[]jbooleanArraybyte[]jbyteArraychar[]jcharArrayshort[]jshortArrayint[]jintArraylong[]jlongArrayfloat[]floatArraydouble[]jdoubleArrayjava.lang.Throwable 实例 jthrowable2.3 JNIEnv 介绍
JNIEnv 用来操作 java 类的对象,比如读取修改 java 类的类成员变量,或者直接调用 java 类的成员函数。
JNIEnv 变量,在每个 JNI 层函数中都是以第一个参数传入。JNIEnv 变量可以看到在 JNI_Onload() 函数中,由 Java VM 相关函数 GetEnv 函数取出
- jint JNI_OnLoad(JavaVM * vm, void *
- /* reserved */
- ) {
- JNIEnv * env = NULL;...
- if (vm - >GetEnv((void * *) & env, JNI_VERSION_1_4) != JNI_OK) {...
- }...
- }
2.3.1 通过 JNIEnv 操作 jobject
jobject 即 java 对象在 JNI 中的表示,通过 JNIEnv 可以操作 jobject 以达到读取修改 java 类的成员变量和调用 java 函数的目的。
jfieldID 和 jmethodID 介绍
jfieldID 和 jmethodID 分别代表 jobject 中成员变量以及成员函数,可以从 JNIEnv 对应的函数中得到 jfieldID
和 jmethodID
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
获取 jmethodID 和使用方法
- class MyMediaScannerClient: public MediaScannerClient {
- public: MyMediaScannerClient(JNIEnv * env, jobject client) //构造函数中设置mClient等 : mEnv(env), mClient(env->NewGlobalRef(client)), mScanFileMethodID(0), mHandleStringTagMethodID(0), mSetMimeTypeMethodID(0) { ... mSetMimeTypeMethodID = env->GetMethodID( mediaScannerClientInterface, "setMimeType", "(Ljava/lang/String;)V"); ... }}
- virtual status_t setMimeType(const char * mimeType) {
- jstring mimeTypeStr;... //mClient就是构造函数中获取的jobject //mSetMimeTypeMethodID就是构造函数中调用GetMethodID获取到的响应的jmethondID mEnv->CallVoidMethod(mClient, mSetMimeTypeMethodID, mimeTypeStr); ...}
获取 jfieldID 和使用的例子
- static voidandroid_media_MediaScanner_native_init(JNIEnv * env) {...jclass clazz = env - >FindClass(kClassMediaScanner);
- fields.context = env - >GetFieldID(clazz, "mNativeContext", "J");....
- }
使用下面的函数修改或者读取 jfieldID 对应的成员变量,这里注意变量的类型!!!
- //修改对应的变量static void setNativeScanner_l(JNIEnv* env, jobject thiz, MediaScanner *s){ env->SetLongField(thiz, fields.context, (jlong)s);}//读取对应的变量static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz){ return (MediaScanner *) env->GetLongField(thiz, fields.context);}
2.3.2 JNI 类型签名介绍 2.3.3 垃圾回收
Java 中创建的对象最后是由垃圾回收器来回收和释放内存的,可它对 JNI 有什么影响呢?下面看一个例子
- static jobject save_thiz = NULL; //定义一个全局的jobjectstatic voidandroid_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client){ ... //保存Java层传入的jobject对象,代表MediaScanner对象 save_thiz = thiz; ... return;}//假设在某个时间,有地方调用callMediaScanner函数void callMediaScanner(){ //在这个函数中操作save_thiz会有什么问题?}
上面的做法肯定会有问题,因为和 save_thiz 对应的 Java 层中的 MediaScanner 很有可能已经被垃圾回收了,也就是说,save_thiz 保存的这个 jobject 可能是一个野指针,如果使用它,后果会很严重。
可能有人会问,对一个引用类型执行赋值操作,它的引用计数不会增加吗? 而垃圾回收机制只会保证那些没有被引用的对象才会被清理。问得对,但如果在 JNI 层使用下面这样的语句,是不会增加引用计数的
- save_thiz = thiz; //这种赋值不会增加jobject的引用计数
Local Reference:本地引用。在 JNI 层函数中使用的非全局引用对象都是 Local Reference,它包含函数调用时传入的 jobject 和在 JNI 层函数中创建的 jobject。Local Reference 最大的特点就是,一旦 JNI 层函数返回,这些 jobject 就可能被垃圾回收 Global Reference:全局引用,这种对象如果不主动释放,它永远不会被垃圾回收
Weak Global Reference:弱全局引用,一种特殊的 Global Reference,在运行过程中可能会被垃圾回收。所以在使用它之前,需要调用 JNIEnv 的 IsSameObject 判断它是否被回收了
平时用得最多的是 Local Reference 和 Global Reference,下面来看一个例子,代码如下:
"`c
public:
MyMediaScannerClient(JNIEnv *env, jobject client)
: mEnv(env),
// 调用 NewGlobalRef 创建一个 Global Reference,这样 mClient 就不用担心被回收了
mClient(env->NewGlobalRef(client)),
mScanFileMethodID(0),
mHandleStringTagMethodID(0),
mSetMimeTypeMethodID(0)
{
…
}
// 析构函数
virtual ~MyMediaScannerClient()
{
mEnv->DeleteGlobalRef(mClient); //DeleteGlobalRef 函数释放这个全局引用
}
- 像上面这样,每当JNI层想要保存Java层中的某个对象时,就可以使用Global Reference,使用完后记住释放它就可以了。下面来看一下Local Reference。```c virtual status_t scanFile(const char * path, long long lastModified, long long fileSize, bool isDirectory, bool noMedia) {
- jstring pathStr; //调用NewStringUTF创建一个jstring对象,它是Local Reference类型 if ((pathStr = mEnv->NewStringUTF(path)) == NULL) { mEnv->ExceptionClear(); return NO_MEMORY; } mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize, isDirectory, noMedia); //调用DeleteLocalRef释放Local Reference,按照前面的说法,在return之后, //pathStr就会被释放,所以这里释放Local Reference好像是多余的,但有区别 //1)如果不调用DeleteLocalRef,pathStr将在函数返回后被释放 //2)调用DeleteLocalRef,pathStr将立即被释放 //由于垃圾回收时间不定而且如果在频繁调用NewStringUTF的时候, //还是需要马上释放Local Reference,像下面这样不马上释放的话内存会马上被耗光 for(int i=0;i<100;i++){ jstring pathStr = mEnv->NewStringUTF(path); //mEnv->DeleteLocalRef(pathStr); } mEnv->DeleteLocalRef(pathStr); return checkAndClearExceptionFromCallback(mEnv, "scanFile"); }
2.3.4 JNI 中的异常处理
JNI 中也有一场,如果调用 JNIEnv 的某些函数出错了,则会产生一个异常,但这个异常不会中断本地函数的执行,直到从 JNI 层返回到 Java 层后,虚拟机才会抛出这个异常。虽然在 JNI 层中产生的异常不会中断本地函数的运行,但一旦产生异常后,就只能做一些资源清理工作了 (例如释放全局引用,或者 ReleaseStringChars)。如果这时调用除上面提到的函数之外的其他 JNIEnv 函数,则会导致程序死掉。
来看一个和异常处理有关的例子,代码如下所示:
- virtual status_t scanFile(const char * path, long long lastModified, long long fileSize, bool isDirectory, bool noMedia) {
- jstring pathStr;
- if ((pathStr = mEnv - >NewStringUTF(path)) == NULL) {
- mEnv - >ExceptionClear(); //清理当前JNI层中发生的异常 return NO_MEMORY; } }
JNI 层函数可以在代码中截获和修改这些异常,JNIEnv 提供了三个函数给予帮助:
ExceptionOccured 函数,用来判断是否发生异常 ExceptionClear 函数,用来清理当前 JNI 层中发生的异常 ThrowNew 函数,用来向 Java 层抛出异常就爱阅读 www.92to.com 网友整理上传, 为您提供最全的知识大全, 期待您的分享,转载请注明出处。
来源: http://www.92to.com/bangong/2017/02-16/17229367.html