AndroidO 平台JNI机制的学习
2017-12-15 09:12
423 查看
第一章 JNI的含义
JNI全称Java Native Interface,意指Java本地调用。JNI是一种技术,通过JNI我们可以做到如下两点:
(1) Java程序中的函数可以调用到Native语言编写的函数。Java-- >c/c++
(2) Native程序中的函数也可反向调用Java层的函数。c/c++ -- > Java
在Android平台,JNI是上层Java与Native层沟通的桥梁,示意图如下:
图1 JNI通信结构 图2 Android示例MediaScanner
本文基于《深入理解Android卷一》进行对AndroidO平台进行解析JNI技术的实质过程。
(1) Java 层MediaScanner是对应于上层Media。
(2) JNI 层libmedia_jni.so是动态库,模块media命名,是一个桥梁。
(3) Native层libmedia.so是动态库,是真实的c/c++底层Native的具体实现。
涉及到的文件路径:
android8.1_trunk/frameworks/base/media/java/android/media/MediaPlayer.java
android8.1_trunk/frameworks/base/media/java/android/media/MediaScanner.java
android8.1_trunk/prebuilts/ndk/r11/platforms/android-24/arch-arm/usr/include/jni.h
android8.1_trunk/libnativehelper/JNIHelp.cpp
android8.1_trunk/frameworks/base/core/jni/AndroidRuntime.cpp
android8.1_trunk/frameworks/base/media/jni/android_media_MediaPlayer.cpp
android8.1_trunk/frameworks/base/media/jni/android_media_MediaScanner.cpp
android8.1_trunk/frameworks/av/media/libmedia/MediaScanner.cpp
android8.1_trunk/frameworks/av/media/libstagefright/StagefrightMediaScanner.cpp
本文档基于JNI的实际流程去解析全过程,按顺序:
1. 简要介绍JNI层的数据结构类型
2. Java层加载JNI库,并书写响应功能的native函数
3. JNI层注册JNI函数
4. Native层与Java层通过JNI互相调用的实现机制
第二章 JNI层的数据结构
Java层和JNI层对应的数据类型是不一样的,但是有对应的转换关系。其类型的具体定义是在如下路径文件:
android8.1_trunk/prebuilts/ndk/r11/platforms/android-24/arch-arm/usr/include/jni.h
该头文件定义了所有的JNI层的数据结构类型以及JNI调用Java的方法集。
上面两个表说明了Java和JNI数据类型的对应关系,在头文件jni.h中都可以查到,一般不属于基本类型的java类对象,在jni层使用jobject表示。
下面说几个重点的数据结构:
当Java层使用的参数不是基本类型,而是类对象时,在JNI层对应的jobject如何获取类对象的方法与成员变量?
定义的jFieldID和jmethodID就是用来在JNI层保存字段和方法,而这些字段和方法的获取是通过结构体 JNIEnv,其定义了一系列的JNI层函数指针方法。其中,GET开头的方法在JNI层获取对应的类型对象、方法等以供JNI层使用。也包含了某些SET方法。例如:
jlong GetLongField(jobject obj, jfieldID fieldID) ;
返回一个对象实例(非静态)域的值,长整型数域。
void (*SetLongField)(JNIEnv*, jobject,
jfieldID, jlong);
设置一个对象设置长整数域,即设置一个对象实例(非静态)域的值。
CallVoidMethod方法一般在JNI层需要native层反向回调Java层时使用。
JNIEnv是一个与线程相关的,每个线程对应各自的JNIEnv结构体,当java层native函数调用时,会转换成jni层函数,并由虚拟机传进来该JNIEnv结构体。有一种情况,当后台线程收到一个网络消息,且需要由Native层函数主动回调java层函数时,JNIEnv哪里来?我们不能保存另外一个线程的JNIEnv结构体,把它放到后台线程用。
JavaVM结构体是虚拟机在JNI层叠代码,一个进程只有一个JavaVM对象,不论该进程有几个线程,该JavaVM只有一份,因此,进程的任何线程均可以使用,妙处就在这里,JavaVM结构体调用函数AttachCurrentThread 函数即可获取得到该线程的JNIEnv结构体对象。注意用完需要释放资源 ,调用DetachCurrentThread函数。
第三章 JNI通信Java层的实现
Java层通过JNI方式去调用Native层函数,必须先加载jni动态库,并初始化,书写响应的native函数,该函数对应于jni层相应的函数。
基本步骤就是上面提到的加载动态库、初始化、书写native函数。下面就以MediaScanner的源码为例,展开从Java到Native层整个过程的流程。本章介绍流程中的准备工作。
相应和Media相关的源码:
android8.1_trunk/frameworks/base/media/java/android/media/MediaPlayer.java
android8.1_trunk/frameworks/base/media/java/android/media/MediaScanner.java
1. 加载jni动态库并初始化
上面是MediaScanner.java类加载动态库并初始化的实现,其实,在MediaPlayer.java中也包含了同样的静态快去加载”media_jni”动态库,并初始化。
2. 书写native函数
上面是MediaScanner.java定义的一些列native函数,该函数实际需要通过JNI通信调用jni函数,jni函数中和native层c/c++MediaScanner.cpp进行交互。
第四章 JNI层注册函数
JNI函数的注册问题,十九上就是将Java层的native函数和JNI层对应的实现函数关联起来,有了关联,调用Java层的native函数时,就能转到JNI层对应的函数执行。JNI函数注册包括两种。
1. 静态注册
(1) 编写Java代码,编译生成.class文件
(2) 使用Javah程序,执行:javah –o output 包名.类名,生成output.h的JNI层头文件,声明了Java层native函数对应的JNI层函数,实现JNI函数即可。
例如:MediaScanner.java 所在包:android.media, 方法:
生成:android_media_MediaScanner.h
JNIEXPORT void JNICALL Java_android_media_MediaScanner_processFile
当Java层调用processFile函数时,会从JNI库找JNI函数,没找到会报错,找到会为这个processFile和JNI函数建立一个关联关系,保存JNI层该函数的函数指针。当再调用processFile,直接使用这个函数指针就可以了,这些是由虚拟机完成。
缺点如下:
(1) 编译所有声明native函数Java类,并使用Javah生成JNI头文件
(2) javah生成的JNI层函数名较长,书写不方便
(3) 第一次调用native函数,根据函数名搜索对应的JNI层函数建立关联,会影响效率。
2. 动态注册
Java native函数是通过函数指针和JNI函数建立一一对应关系,如果有一个结构体来保存这个关联,直接使用该函数指针就可以了,对应的结构体: JNINativeMethod,该结构体是在第二章中jni.h头文件定义的。再来回顾一下:
那么,该结构体是怎样具体使用把Java native函数和JNI层函数通过函数指针绑定到一起?
当Java层MediaScanner.java通过System.loadLibrary(“media_jni”)加载jni动态库后,紧接着会在该libmedia_jni.so库中去查找一个叫 JNI_OnLoad()的函数,如果找到就调用它,而动态注册的工作就是通过 JNI_OnLoad()函数来实现的。
通过查找,该函数位于如下android_media_MediaPlayer.cpp文件:
android8.1_trunk/frameworks/base/media/jni/android_media_MediaPlayer.cpp
来看下该函数的具体实现:
JNI_OnLoad()函数注册了许多与media相关的JNI函数。也包含我们关注的MediaScanner注册函数register_android_media_MediaScanner(env),查看其它相关的与media相关的注册函数对应的java类,加载同一个动态库”media_jni”。再查看这些注册函数的声明,均是external类型,表面是外部全局的,其他文件也可访问。
上面只是列出来部分,还有其他的注册函数,由于是在android_media_MediaPlayer.cpp内部,因此,关于MediPlayer的注册直接实现了,是使用AndroidRuntime的注册接口。那么,我们关注的MediaScanner的注册,应该也在对应的android_media_MediaScanner.cpp中具体实现。
android8.1_trunk/frameworks/base/media/jni/android_media_MediaScanner.cpp
事实如此,在MediaScanner的JNI层cpp文件中,通过AndroidRuntime的注册方法实现。
关注参数kClassMediaScanner、gMethods,最后一个是方法个数。
先来看一下android_media_MediaScanner.cpp的头文件等前面信息:
该信息包含了mediascanner.h、StagefrightMediaScanner.h、JNIHelp.h、AndroidRuntime.h等。
这些头文件的引入,我们可以在该c++文件中使用声明的类及方法。
此外,还定义了常量字符串指针,字符串信息是对应 java层的类的结构路径。下面再来看看gMethods参数的具体信息:
是不是很惊讶,该JNINativeMethod结构体数组包含了MediaScanner.java中的所有native方法、JNI层参数签名以及在JNI层该方法对应的函数指针。现在,AndroidRuntime调用 registerNativeMethods()函数的参数清晰了,在继续往下分析前整理注册的思路:
Java层MediaScanner.java System.LoadLibrary()加载动态库 --- >
JNI层动态库查找JNI_OnLoad() 位于android_media_MediaPlayer.cpp并调用 -- >
JNI层android_media_MediaScanner.cpp 调用register_android_media_MediaScanner() -- >
JNI层AndroidRuntime.cpp调用
registerNativeMethods(env,kClassMediaScanner, gMethods, NELEM(gMethods)) -- > 待续
前面的分析到这里,继续往下分析,AndroidRuntime.cpp:
文件路径:android8.1_trunk/frameworks/base/core/jni/AndroidRuntime.cpp
调用jniRegisterNativeMethods()方法,它是 Android平台中为方便JNI使用由JNIHelp.cpp所提供的帮助函数。前面分析头文件时,已经include了JNIHelp.h,因此可直接调用。文件路径: android8.1_trunk/libnativehelper/JNIHelp.cpp
这个帮助函数有两点较为重要:
(1) scoped_local_ref<jclass> c(env, findClass(env, className));
获取Java层类名所对应的JNI层jclass对象
(2) (*env)->RegisterNatives(e,c.get(), gMethods, numMethods)
通过JNIEnv结构体指针调用 RegisterNative()方法完成实际的java层到JNI层类、方法的关联,即完成了注册任务。
以MediaScanner.java为例,实际这两个步骤的参数如下:
className: kClassMediaScanner “android/media/MediaScanner”
gMethods: 指的定义的JNINativeMethod gMethods[]结构体数组,包含了processFile等。
第五章Java层与Native层通信
第四章主要分析了注册JNI函数,建立java层native函数和JNI层函数的对应关系,通过函数指针保存指向JNI层函数,下次使用时,直接使用该函数指针就可以操作到JNI层函数。
现在,当我们在Java层MediaScanner.java中调用其方法scanFile()来扫描文件时:
该方法获取调用 doScanFile()函数,针对不同类型文件调用不同的处理函数,当文件是audio或者video时,会调用processFile()函数,该函数是native函数,由于MediaScanner.java已经加载过动态库并初始化以及完成了一些列java层native函数对应JNI层函数注册,当调用该processFile()时,JNI层android_media_MediaScanner.cpp中的JNI函数会通过函数指针被找到调用:android_media_MediaScanner_processFile()
android8.1_trunk/frameworks/base/media/jni/android_media_MediaScanner.cpp
先看下如下getNativeScanner_l()方法:
Java层MediaScanner的native_setup()函数在构造器中调用。因此,创建java对象也就立即创建了Native层对应的StagefrightMediaScanner对象。
JNI层函数android_media_MediaScanner_processFile()的关键有如下几点:
(1) 获取对象MediaScanner *mp =
getNativeScanner_l(env, thiz);
调用的JNIEnv的GETLongField()获取长整数型的一个对象实例mp。
(2) 创建 MyMediaScannerClient myClient(env, client);
具体查看该构造器,将 java层MediaScannerClient对应的方法获取,保存到JNI层的对象中,以便以后使用。
当构造器初始化这些对象后,在哪里使用?在MyMediaScannerClient类内部定义如下函数:
这个MyMediaScannerClient的c++类的scanFile()和setMimeType()函数是在Native层MediaScanner.cpp被调用的。
(3) Native层MediaScanner调用 mp->processFile(pathStr, mimeTypeStr, myClient);
这个方法很重要,Native层调用自己的processFile函数,即StagefrightMediaScanner该类的方法。
android8.1_trunk/frameworks/av/media/libstagefright/StagefrightMediaScanner.cpp#58
processFile(constchar *path, const char *mimetype, MediaScannerClient &client);
调用如下方法,完成实际的工作:
我们看到 103 status =client.setMimeType(value);//
这个地方就要去掉Java层的啦
再来到Java层,看看是不是有这个函数?
android8.1_trunk/frameworks/base/media/java/android/media/MediaScanner.java
除了这个函数,还包括其他的许多函数。至此,我们看到了JNI通信的所有涉及到过程知识了。
本文参考了《深入理解Android卷一》,转载请标明出处!
如果喜欢的话,欢迎到如下下载文档!
http://download.csdn.net/download/snail201211/10159467
JNI全称Java Native Interface,意指Java本地调用。JNI是一种技术,通过JNI我们可以做到如下两点:
(1) Java程序中的函数可以调用到Native语言编写的函数。Java-- >c/c++
(2) Native程序中的函数也可反向调用Java层的函数。c/c++ -- > Java
在Android平台,JNI是上层Java与Native层沟通的桥梁,示意图如下:
图1 JNI通信结构 图2 Android示例MediaScanner
本文基于《深入理解Android卷一》进行对AndroidO平台进行解析JNI技术的实质过程。
(1) Java 层MediaScanner是对应于上层Media。
(2) JNI 层libmedia_jni.so是动态库,模块media命名,是一个桥梁。
(3) Native层libmedia.so是动态库,是真实的c/c++底层Native的具体实现。
涉及到的文件路径:
android8.1_trunk/frameworks/base/media/java/android/media/MediaPlayer.java
android8.1_trunk/frameworks/base/media/java/android/media/MediaScanner.java
android8.1_trunk/prebuilts/ndk/r11/platforms/android-24/arch-arm/usr/include/jni.h
android8.1_trunk/libnativehelper/JNIHelp.cpp
android8.1_trunk/frameworks/base/core/jni/AndroidRuntime.cpp
android8.1_trunk/frameworks/base/media/jni/android_media_MediaPlayer.cpp
android8.1_trunk/frameworks/base/media/jni/android_media_MediaScanner.cpp
android8.1_trunk/frameworks/av/media/libmedia/MediaScanner.cpp
android8.1_trunk/frameworks/av/media/libstagefright/StagefrightMediaScanner.cpp
本文档基于JNI的实际流程去解析全过程,按顺序:
1. 简要介绍JNI层的数据结构类型
2. Java层加载JNI库,并书写响应功能的native函数
3. JNI层注册JNI函数
4. Native层与Java层通过JNI互相调用的实现机制
第二章 JNI层的数据结构
Java层和JNI层对应的数据类型是不一样的,但是有对应的转换关系。其类型的具体定义是在如下路径文件:
android8.1_trunk/prebuilts/ndk/r11/platforms/android-24/arch-arm/usr/include/jni.h
该头文件定义了所有的JNI层的数据结构类型以及JNI调用Java的方法集。
上面两个表说明了Java和JNI数据类型的对应关系,在头文件jni.h中都可以查到,一般不属于基本类型的java类对象,在jni层使用jobject表示。
30/* 31 * Primitive types that match up with Java equivalents. 32 */ 33#ifdefHAVE_INTTYPES_H 34 #include <inttypes.h> /* C99 */ 35 typedef uint8_t jboolean; /* unsigned 8 bits */ 36 typedef int8_t jbyte; /* signed 8 bits */ 37 typedef uint16_t jchar; /* unsigned 16 bits */ 38 typedef int16_t jshort; /* signed 16 bits */ 39 typedef int32_t jint; /* signed 32 bits */ 40 typedef int64_t jlong; /* signed 64 bits */ 41 typedef float jfloat; /* 32-bit IEEE 754 */ 42 typedef double jdouble; /* 64-bit IEEE 754 */ 43#else 44 typedef unsigned char jboolean; /* unsigned 8 bits */ 45 typedef signed char jbyte; /* signed 8 bits */ 46 typedef unsigned short jchar; /* unsigned 16 bits */ 47 typedef short jshort; /* signed 16 bits */ 48 typedef int jint; /* signed 32 bits */ 49 typedef long long jlong; /* signed 64 bits */ 50 typedef float jfloat; /* 32-bit IEEE 754 */ 51 typedef double jdouble; /* 64-bit IEEE 754 */ 52#endif 53 54/* "cardinal indices and sizes" */ 55typedefjint jsize; 57#ifdef__cplusplus // 如果定义使用c++数据类型,相反是c数据类型 58 /* 59 * Reference types, in C++ 60 */ 61 class _jobject {}; 62 class _jclass :public _jobject {}; 63 class _jstring :public _jobject {}; 64 class _jarray :public _jobject {}; 65 class _jobjectArray : public _jarray {}; 66 class _jbooleanArray :public _jarray {}; 67 class _jbyteArray :public _jarray {}; 68 class _jcharArray :public _jarray {}; 69 class _jshortArray :public _jarray {}; 70 class _jintArray :public _jarray {}; 71 class _jlongArray :public _jarray {}; 72 class _jfloatArray :public _jarray {}; 73 class _jdoubleArray : public _jarray {}; 74 class _jthrowable :public _jobject {}; 75 76 typedef _jobject* jobject; 77 typedef _jclass* jclass; 78 typedef _jstring* jstring; 79 typedef _jarray* jarray; 80 typedef _jobjectArray* jobjectArray; 81 typedef _jbooleanArray* jbooleanArray; 82 typedef _jbyteArray* jbyteArray; 83 typedef _jcharArray* jcharArray; 84 typedef _jshortArray* jshortArray; 85 typedef _jintArray* jintArray; 86 typedef _jlongArray* jlongArray; 87 typedef _jfloatArray* jfloatArray; 88 typedef _jdoubleArray* jdoubleArray; 89 typedef _jthrowable* jthrowable; 90 typedef _jobject* jweak; 91 92 93#else/* not __cplusplus */ // 不使用c++,使用c类型数据 95 /* 96 * Reference types, in C. 97 */ 98 typedef void* jobject; 99 typedef jobject jclass; 100 typedefjobject jstring; 101 typedef jobject jarray; 102 typedef jarray jobjectArray; 103 typedef jarray jbooleanArray; 104 typedefjarray jbyteArray; 105 typedef jarray jcharArray; 106 typedef jarray jshortArray; 107 typedef jarray jintArray; 108 typedef jarray jlongArray; 109 typedef jarray jfloatArray; 110 typedef jarray jdoubleArray; 111 typedef jobject jthrowable; 112 typedef jobject jweak; 113 114#endif/* not __cplusplus */ |
下面说几个重点的数据结构:
116struct _jfieldID; /* opaque structure */ 117typedef struct _jfieldID* jfieldID; /* field IDs */ 119struct _jmethodID; /* opaque structure */ 120typedef struct _jmethodID* jmethodID; /* method IDs */ 143typedef struct { 144 const char* name; //对应Java层native函数的名,例如:processFile 145 const char* signature; //函数的参数签名信息,使用JNI层定义的类型 146 void* fnPtr; // 函数指针,指向JNI层对应函数 147} JNINativeMethod; 149struct _JNIEnv; 150struct _JavaVM; 151typedef const struct JNINativeInterface* C_JNIEnv; 153#if defined(__cplusplus) // c++版本条件选择,决定JNIEnv和JavaVM结构体 154 typedef _JNIEnv JNIEnv; 155 typedef _JavaVM JavaVM; 156#else 157 typedef const struct JNINativeInterface* JNIEnv; // 接口函数指针表 158 typedef const struct JNIInvokeInterface* JavaVM; // JNI调用接口,几个函数 159#endif 161/* 162 * Table of interface function pointers. 163 */ 164struct JNINativeInterface { 212 jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); 241 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 306 jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*); 314 jlong (*GetLongField)(JNIEnv*, jobject, jfieldID); 324 void (*SetLongField)(JNIEnv*, jobject, jfieldID, jlong); 390 jstring (*NewStringUTF)(JNIEnv*, const char*); 392 /* JNI spec says this returns const jbyte*, but that's inconsistent */ 393 const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*); 497}; // end 结构体 505struct _JNIEnv { 506 /* do not rename this; it does not seem to be entirely opaque */ 507 const struct JNINativeInterface* functions; // … 605 jmethodID GetMethodID(jclass clazz, const char* name, const char* sig) 606 { return functions->GetMethodID(this, clazz, name, sig); } 646 void CallVoidMethod(jobject obj, jmethodID methodID, ...) 647 { 648 va_list args; 649 va_start(args, methodID); 650 functions->CallVoidMethodV(this, obj, methodID, args); 651 va_end(args); 652 } 714 jfieldID GetFieldID(jclass clazz, const char* name, const char* sig) 715 { return functions->GetFieldID(this, clazz, name, sig); } 729 jlong GetLongField(jobject obj, jfieldID fieldID) 730 { return functions->GetLongField(this, obj, fieldID); } 750 void SetLongField(jobject obj, jfieldID fieldID, jlong value) 751 { functions->SetLongField(this, obj, fieldID, value); } 872 jstring NewStringUTF(const char* bytes) 873 { return functions->NewStringUTF(this, bytes); } 878 const char* GetStringUTFChars(jstring string, jboolean* isCopy) 879 { return functions->GetStringUTFChars(this, string, isCopy); } } // end JNIEnv 1067 /* JNI invocation interface.JNI调用接口 1068 */ 1069struct JNIInvokeInterface { 1070 void* reserved0; 1071 void* reserved1; 1072 void* reserved2; 1074 jint (*DestroyJavaVM)(JavaVM*); 1075 jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*); 1076 jint (*DetachCurrentThread)(JavaVM*); 1077 jint (*GetEnv)(JavaVM*, void**, jint); 1078 jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*); 1079}; 1081/* 1082 * C++ version. 1083 */ 1084struct _JavaVM { 1085 const struct JNIInvokeInterface* functions; 1087#if defined(__cplusplus) 1088 jint DestroyJavaVM() 1089 { return functions->DestroyJavaVM(this); } 1090 jint AttachCurrentThread(JNIEnv** p_env, void* thr_args) 1091 { return functions->AttachCurrentThread(this, p_env, thr_args); } 1092 jint DetachCurrentThread() 1093 { return functions->DetachCurrentThread(this); } 1094 jint GetEnv(void** env, jint version) 1095 { return functions->GetEnv(this, env, version); } 1096 jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args) 1097 { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); } 1098#endif /*__cplusplus*/ 1099}; |
当Java层使用的参数不是基本类型,而是类对象时,在JNI层对应的jobject如何获取类对象的方法与成员变量?
定义的jFieldID和jmethodID就是用来在JNI层保存字段和方法,而这些字段和方法的获取是通过结构体 JNIEnv,其定义了一系列的JNI层函数指针方法。其中,GET开头的方法在JNI层获取对应的类型对象、方法等以供JNI层使用。也包含了某些SET方法。例如:
jlong GetLongField(jobject obj, jfieldID fieldID) ;
返回一个对象实例(非静态)域的值,长整型数域。
void (*SetLongField)(JNIEnv*, jobject,
jfieldID, jlong);
设置一个对象设置长整数域,即设置一个对象实例(非静态)域的值。
CallVoidMethod方法一般在JNI层需要native层反向回调Java层时使用。
JNIEnv是一个与线程相关的,每个线程对应各自的JNIEnv结构体,当java层native函数调用时,会转换成jni层函数,并由虚拟机传进来该JNIEnv结构体。有一种情况,当后台线程收到一个网络消息,且需要由Native层函数主动回调java层函数时,JNIEnv哪里来?我们不能保存另外一个线程的JNIEnv结构体,把它放到后台线程用。
JavaVM结构体是虚拟机在JNI层叠代码,一个进程只有一个JavaVM对象,不论该进程有几个线程,该JavaVM只有一份,因此,进程的任何线程均可以使用,妙处就在这里,JavaVM结构体调用函数AttachCurrentThread 函数即可获取得到该线程的JNIEnv结构体对象。注意用完需要释放资源 ,调用DetachCurrentThread函数。
第三章 JNI通信Java层的实现
Java层通过JNI方式去调用Native层函数,必须先加载jni动态库,并初始化,书写响应的native函数,该函数对应于jni层相应的函数。
基本步骤就是上面提到的加载动态库、初始化、书写native函数。下面就以MediaScanner的源码为例,展开从Java到Native层整个过程的流程。本章介绍流程中的准备工作。
相应和Media相关的源码:
android8.1_trunk/frameworks/base/media/java/android/media/MediaPlayer.java
android8.1_trunk/frameworks/base/media/java/android/media/MediaScanner.java
1. 加载jni动态库并初始化
124public class MediaScanner implements AutoCloseable { 125 static { 126 System.loadLibrary("media_jni"); 127 native_init(); 128 } |
2. 书写native函数
2078 private native void processDirectory(String path, MediaScannerClient client); 2079 private native void processFile(String path, String mimeType, MediaScannerClient client); 2080 private native void setLocale(String locale); 2081 2082 public native byte[] extractAlbumArt(FileDescriptor fd); 2083 2084 private static native final void native_init(); 2085 private native final void native_setup(); 2086 private native final void native_finalize(); |
第四章 JNI层注册函数
JNI函数的注册问题,十九上就是将Java层的native函数和JNI层对应的实现函数关联起来,有了关联,调用Java层的native函数时,就能转到JNI层对应的函数执行。JNI函数注册包括两种。
1. 静态注册
(1) 编写Java代码,编译生成.class文件
(2) 使用Javah程序,执行:javah –o output 包名.类名,生成output.h的JNI层头文件,声明了Java层native函数对应的JNI层函数,实现JNI函数即可。
例如:MediaScanner.java 所在包:android.media, 方法:
private native void processFile(String path, String mimeType, MediaScannerClient client);
生成:android_media_MediaScanner.h
JNIEXPORT void JNICALL Java_android_media_MediaScanner_processFile
当Java层调用processFile函数时,会从JNI库找JNI函数,没找到会报错,找到会为这个processFile和JNI函数建立一个关联关系,保存JNI层该函数的函数指针。当再调用processFile,直接使用这个函数指针就可以了,这些是由虚拟机完成。
缺点如下:
(1) 编译所有声明native函数Java类,并使用Javah生成JNI头文件
(2) javah生成的JNI层函数名较长,书写不方便
(3) 第一次调用native函数,根据函数名搜索对应的JNI层函数建立关联,会影响效率。
2. 动态注册
Java native函数是通过函数指针和JNI函数建立一一对应关系,如果有一个结构体来保存这个关联,直接使用该函数指针就可以了,对应的结构体: JNINativeMethod,该结构体是在第二章中jni.h头文件定义的。再来回顾一下:
143typedef struct {
144 const char* name; //对应Java层native函数的名,例如:processFile
145 const char* signature; //函数的参数签名信息,使用JNI层定义的类型
146 void* fnPtr; // 函数指针,指向JNI层对应函数
147} JNINativeMethod;
那么,该结构体是怎样具体使用把Java native函数和JNI层函数通过函数指针绑定到一起?
当Java层MediaScanner.java通过System.loadLibrary(“media_jni”)加载jni动态库后,紧接着会在该libmedia_jni.so库中去查找一个叫 JNI_OnLoad()的函数,如果找到就调用它,而动态注册的工作就是通过 JNI_OnLoad()函数来实现的。
通过查找,该函数位于如下android_media_MediaPlayer.cpp文件:
android8.1_trunk/frameworks/base/media/jni/android_media_MediaPlayer.cpp
来看下该函数的具体实现:
1464jintJNI_OnLoad(JavaVM*vm,void* /* reserved */) 1465{ 1466 JNIEnv* env =NULL; 1467 jint result = -1; 1468 // 参数vm是虚拟机在JNI层的代表,每个java进程只一个 1469 if (vm->GetEnv((void**) &env,JNI_VERSION_1_4) != JNI_OK) { 1470 ALOGE("ERROR: GetEnv failed\n"); 1471 goto bail; 1472 } 1473 assert(env !=NULL); 1474 1475 if (register_android_media_ImageWriter(env) !=JNI_OK) { 1476 ALOGE("ERROR: ImageWriter native registration failed"); 1477 goto bail; 1478 } 1479 1480 if (register_android_media_ImageReader(env) < 0) { 1481 ALOGE("ERROR: ImageReader native registration failed"); 1482 goto bail; 1483 } 1484 1485 if (register_android_media_MediaPlayer(env) < 0) { 1486 ALOGE("ERROR: MediaPlayer native registration failed\n"); 1487 goto bail; 1488 } 1489 1490 if (register_android_media_MediaRecorder(env) < 0) { 1491 ALOGE("ERROR: MediaRecorder native registration failed\n"); 1492 goto bail; 1493 } 1494 // 动态注册MediaScanner的JNI函数 1495 if (register_android_media_MediaScanner(env) < 0) { 1496 ALOGE("ERROR: MediaScanner native registration failed\n"); 1497 goto bail; 1498 } 1499 1500 if (register_android_media_MediaMetadataRetriever(env) < 0) { 1501 ALOGE("ERROR: MediaMetadataRetriever native registration failed\n"); 1502 goto bail; 1503 } 1534 1535 if (register_android_media_MediaSync(env) < 0) { 1536 ALOGE("ERROR: MediaSync native registration failed"); 1537 goto bail; 1538 } 1539 1540 if (register_android_media_MediaExtractor(env) < 0) { 1541 ALOGE("ERROR: MediaCodec native registration failed"); 1542 goto bail; 1543 } 1555 if (register_android_media_Crypto(env) < 0) { 1556 ALOGE("ERROR: MediaCodec native registration failed"); 1557 goto bail; 1558 } 1569 1570 if (register_android_media_MediaHTTPConnection(env) < 0) { 1571 ALOGE("ERROR: MediaHTTPConnection native registration failed"); 1572 goto bail; 1573 } 1574 1575 /* success -- return valid version number */ 1576 result = JNI_VERSION_1_4; 1577 1578bail: 1579 return result; 1580} |
1438// This function only registers the native methods 1439static int register_android_media_MediaPlayer(JNIEnv *env) 1440{ 1441 return AndroidRuntime::registerNativeMethods(env, 1442 "android/media/MediaPlayer", gMethods, NELEM(gMethods)); 1443} 1444extern int register_android_media_ImageReader(JNIEnv *env); 1453extern int register_android_media_MediaMetadataRetriever(JNIEnv *env); 1455extern int register_android_media_MediaRecorder(JNIEnv *env); 1456extern int register_android_media_MediaScanner(JNIEnv *env); 1462extern int register_android_mtp_MtpServer(JNIEnv *env); |
android8.1_trunk/frameworks/base/media/jni/android_media_MediaScanner.cpp
459// This function only registers the native methods, and is called from 460// JNI_OnLoad in android_media_MediaPlayer.cpp 461int register_android_media_MediaScanner(JNIEnv *env) 462{ 463 return AndroidRuntime::registerNativeMethods(env, 464 kClassMediaScanner, gMethods, NELEM(gMethods)); 465} |
关注参数kClassMediaScanner、gMethods,最后一个是方法个数。
先来看一下android_media_MediaScanner.cpp的头文件等前面信息:
19#define LOG_TAG "MediaScannerJNI" 20#include <utils/Log.h> 21#include <utils/threads.h> 22#include <media/mediascanner.h> 23#include <media/stagefright/StagefrightMediaScanner.h> 24#include <private/media/VideoFrame.h> 25 26#include "jni.h" 27#include <nativehelper/JNIHelp.h> 28#include "android_runtime/AndroidRuntime.h" 29#include "android_runtime/Log.h" 30 31using namespace android; 32 33 34static const char* const kClassMediaScannerClient = 35 "android/media/MediaScannerClient"; 36 37static const char* const kClassMediaScanner = 38 "android/media/MediaScanner"; // … 省略其他 |
这些头文件的引入,我们可以在该c++文件中使用声明的类及方法。
此外,还定义了常量字符串指针,字符串信息是对应 java层的类的结构路径。下面再来看看gMethods参数的具体信息:
415static const JNINativeMethod gMethods[] = { 416 { 417 "processDirectory", 418 "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V", 419 (void *)android_media_MediaScanner_processDirectory 420 }, 421 422 { 423 "processFile", 424 "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", 425 (void *)android_media_MediaScanner_processFile 426 }, 427 428 { 429 "setLocale", 430 "(Ljava/lang/String;)V", 431 (void *)android_media_MediaScanner_setLocale 432 }, 433 434 { 435 "extractAlbumArt", 436 "(Ljava/io/FileDescriptor;)[B", 437 (void *)android_media_MediaScanner_extractAlbumArt 438 }, 439 440 { 441 "native_init", 442 "()V", 443 (void *)android_media_MediaScanner_native_init 444 }, 445 446 { 447 "native_setup", 448 "()V", 449 (void *)android_media_MediaScanner_native_setup 450 }, 451 452 { 453 "native_finalize", 454 "()V", 455 (void *)android_media_MediaScanner_native_finalize 456 }, 457}; |
Java层MediaScanner.java System.LoadLibrary()加载动态库 --- >
JNI层动态库查找JNI_OnLoad() 位于android_media_MediaPlayer.cpp并调用 -- >
JNI层android_media_MediaScanner.cpp 调用register_android_media_MediaScanner() -- >
JNI层AndroidRuntime.cpp调用
registerNativeMethods(env,kClassMediaScanner, gMethods, NELEM(gMethods)) -- > 待续
前面的分析到这里,继续往下分析,AndroidRuntime.cpp:
文件路径:android8.1_trunk/frameworks/base/core/jni/AndroidRuntime.cpp
282/* 283 * Register native methods using JNI. 284 */ 285/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env, 286 const char* className, const JNINativeMethod* gMethods, int numMethods) 287{ 288 return jniRegisterNativeMethods(env, className, gMethods, numMethods); 289} |
75extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, 76 const JNINativeMethod* gMethods, int numMethods) 77{ 78 JNIEnv* e = reinterpret_cast<JNIEnv*>(env); 80 ALOGV("Registering %s's %d native methods...", className, numMethods); 82 scoped_local_ref<jclass> c(env, findClass(env, className)); 83 if (c.get() == NULL) { // 处理打印一些错误的信息 95 } 96 97 if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { // 处理打印一些错误的信息 107 } 109 return 0; 110} |
(1) scoped_local_ref<jclass> c(env, findClass(env, className));
获取Java层类名所对应的JNI层jclass对象
(2) (*env)->RegisterNatives(e,c.get(), gMethods, numMethods)
通过JNIEnv结构体指针调用 RegisterNative()方法完成实际的java层到JNI层类、方法的关联,即完成了注册任务。
以MediaScanner.java为例,实际这两个步骤的参数如下:
className: kClassMediaScanner “android/media/MediaScanner”
gMethods: 指的定义的JNINativeMethod gMethods[]结构体数组,包含了processFile等。
第五章Java层与Native层通信
第四章主要分析了注册JNI函数,建立java层native函数和JNI层函数的对应关系,通过函数指针保存指向JNI层函数,下次使用时,直接使用该函数指针就可以操作到JNI层函数。
现在,当我们在Java层MediaScanner.java中调用其方法scanFile()来扫描文件时:
607 public void scanFile(String path, long lastModified, long fileSize, 608 boolean isDirectory, boolean noMedia) { 611 doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia); 612 } private native void processFile(String path, String mimeType, MediaScannerClient client); |
android8.1_trunk/frameworks/base/media/jni/android_media_MediaScanner.cpp
267static void android_media_MediaScanner_processFile( 269 JNIEnv *env, jobject thiz, jstring path, 270 jstring mimeType, jobject client) 271{ // env便是运行当前线程的JNI环境JNIEnv, thiz 表示Java层MediaScanner对象 273 // 获取java层MediaScanner对象所对应Native层的MediaScanner.cpp类对象 274 // Lock already hold by processDirectory 275 MediaScanner *mp = getNativeScanner_l(env, thiz); 276 if (mp == NULL) { 277 jniThrowException(env, kRunTimeException, "No scanner available"); 278 return; 279 } 281 if (path == NULL) { 282 jniThrowException(env, kIllegalArgumentException, NULL); 283 return; 284 } 285 // 将 JNI类型jstring对象转化为char* 字符串指针类型 286 const char *pathStr = env->GetStringUTFChars(path, NULL); 287 if (pathStr == NULL) { // Out of memory 288 return; 289 } 291 const char *mimeTypeStr = 292 (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL); 293 if (mimeType && mimeTypeStr == NULL) { // Out of memory 294 // ReleaseStringUTFChars can be called with an exception pending. 295 env->ReleaseStringUTFChars(path, pathStr); 296 return; 297 } 298 // 创建 MediaScannerClient.cpp的子类对象,涉及到 JNIEnv操jobject 299 MyMediaScannerClient myClient(env, client); 300 MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient); 301 if (result == MEDIA_SCAN_RESULT_ERROR) { 302 ALOGE("An error occurred while scanning file '%s'.", pathStr); 303 } 304 env->ReleaseStringUTFChars(path, pathStr); 305 if (mimeType) { 306 env->ReleaseStringUTFChars(mimeType, mimeTypeStr); 307 } 308} |
先看下如下getNativeScanner_l()方法:
228static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz){ 230 return (MediaScanner *) env->GetLongField(thiz, fields.context); 231} // 看来native_setup调用,就会创建Native层的MediaScanner.cpp子类对象,并将该对象mp // 通过绑定thiz、fields.context设置进去,以便通过相同参数获取。 389static void 390android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz){ 393 MediaScanner *mp = new StagefrightMediaScanner; // 设置长整数域,即设置一个对象实例(非静态)域的值 400 env->SetLongField(thiz, fields.context, (jlong)mp); 401} |
thiz 对象表示Java层的MediaScanner实例对象;
fields.context对象是当java层native_init()调用时在jni层函数初始化的:
class clazz = env->FindClass(kClassMediaScanner);
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
Java层MediaScanner的native_setup()函数在构造器中调用。因此,创建java对象也就立即创建了Native层对应的StagefrightMediaScanner对象。
JNI层函数android_media_MediaScanner_processFile()的关键有如下几点:
(1) 获取对象MediaScanner *mp =
getNativeScanner_l(env, thiz);
调用的JNIEnv的GETLongField()获取长整数型的一个对象实例mp。
(2) 创建 MyMediaScannerClient myClient(env, client);
具体查看该构造器,将 java层MediaScannerClient对应的方法获取,保存到JNI层的对象中,以便以后使用。
111 MyMediaScannerClient(JNIEnv *env, jobject client) 112 : mEnv(env), 113 mClient(env->NewGlobalRef(client)) 117 { 118 ALOGV("MyMediaScannerClient constructor"); 119 jclass mediaScannerClientInterface = 120 env->FindClass(kClassMediaScannerClient); 125 mScanFileMethodID = env->GetMethodID( 126 mediaScannerClientInterface, 127 "scanFile", 128 "(Ljava/lang/String;JJZZ)V"); 135 mSetMimeTypeMethodID = env->GetMethodID( 136 mediaScannerClientInterface, 137 "setMimeType", 138 "(Ljava/lang/String;)V"); 140 } |
148 virtual status_t scanFile(const char* path, long long lastModified, 149 long long fileSize, bool isDirectory, bool noMedia) 150 { 151 ALOGV("scanFile: path(%s), time(%lld), size(%lld) and isDir(%d)", 152 path, lastModified, fileSize, isDirectory); 154 jstring pathStr; 155 if ((pathStr = mEnv->NewStringUTF(path)) == NULL) { 156 mEnv->ExceptionClear(); 157 return NO_MEMORY; 158 } 159 // JNI层调用,反向调用到java层MediaScannerClient类的 scanFile()方法 160 mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, 161 fileSize, isDirectory, noMedia); 163 mEnv->DeleteLocalRef(pathStr); 164 return checkAndClearExceptionFromCallback(mEnv, "scanFile"); 165 } 204 virtual status_t setMimeType(const char* mimeType) 205 { 206 ALOGV("setMimeType: %s", mimeType); 207 jstring mimeTypeStr; 208 if ((mimeTypeStr = mEnv->NewStringUTF(mimeType)) == NULL) { 209 mEnv->ExceptionClear(); 210 return NO_MEMORY; 211 } 212 // JNI层调用,实现调用Java层MediaScannerClient类的 setMimeType()方法 213 mEnv->CallVoidMethod(mClient, mSetMimeTypeMethodID, mimeTypeStr); 214 215 mEnv->DeleteLocalRef(mimeTypeStr); 216 return checkAndClearExceptionFromCallback(mEnv, "setMimeType"); 217 } |
(3) Native层MediaScanner调用 mp->processFile(pathStr, mimeTypeStr, myClient);
这个方法很重要,Native层调用自己的processFile函数,即StagefrightMediaScanner该类的方法。
android8.1_trunk/frameworks/av/media/libstagefright/StagefrightMediaScanner.cpp#58
processFile(constchar *path, const char *mimetype, MediaScannerClient &client);
调用如下方法,完成实际的工作:
70MediaScanResultStagefrightMediaScanner::processFileInternal( 71 const char *path, const char */* mimeType */, 72 MediaScannerClient &client) { 73 const char *extension =strrchr(path,'.'); 75 if (!extension) { 76 return MEDIA_SCAN_RESULT_SKIPPED; 77 } 79 if (!FileHasAcceptableExtension(extension)) { 80 return MEDIA_SCAN_RESULT_SKIPPED; 81 } 83 sp<MediaMetadataRetriever> mRetriever(new MediaMetadataRetriever); 85 int fd = open(path, O_RDONLY | O_LARGEFILE); 86 status_t status; 87 if (fd < 0) { 88 // couldn't open it locally, maybe the media server can? 89 sp<IMediaHTTPService> nullService; 90 status = mRetriever->setDataSource(nullService,path); 91 } else { 92 status = mRetriever->setDataSource(fd, 0,0x7ffffffffffffffL); 93 close(fd); 94 } 96 if (status) { 97 return MEDIA_SCAN_RESULT_ERROR; 98 } 100 const char *value; 101 if ((value = mRetriever->extractMetadata( 102 METADATA_KEY_MIMETYPE)) != NULL) { 103 status =client.setMimeType(value);// 这个地方就要去掉Java层的啦 104 if (status) { 105 return MEDIA_SCAN_RESULT_ERROR; 106 } 107 } 109 struct KeyMap { 110 const char *tag; 111 int key; 112 }; 113 static const KeyMap kKeyMap[] = { 114 {"tracknumber", METADATA_KEY_CD_TRACK_NUMBER }, 115 {"discnumber", METADATA_KEY_DISC_NUMBER }, 116 {"album", METADATA_KEY_ALBUM }, 117 {"artist", METADATA_KEY_ARTIST }, 118 {"albumartist", METADATA_KEY_ALBUMARTIST }, 119 {"composer", METADATA_KEY_COMPOSER }, 120 { "genre", METADATA_KEY_GENRE }, 121 {"title", METADATA_KEY_TITLE }, 122 {"year", METADATA_KEY_YEAR }, 123 {"duration", METADATA_KEY_DURATION }, 124 {"writer", METADATA_KEY_WRITER }, 125 {"compilation", METADATA_KEY_COMPILATION }, 126 {"isdrm", METADATA_KEY_IS_DRM }, 127 {"date", METADATA_KEY_DATE }, 128 {"width", METADATA_KEY_VIDEO_WIDTH }, 129 {"height", METADATA_KEY_VIDEO_HEIGHT }, 130 }; 131 static const size_t kNumEntries = sizeof(kKeyMap) /sizeof(kKeyMap[0]); 133 for (size_t i = 0; i < kNumEntries; ++i) { 134 const char *value; 135 if ((value = mRetriever->extractMetadata(kKeyMap[i].key)) !=NULL) { 136 status = client.addStringTag(kKeyMap[i].tag,value); 137 if (status !=OK) { 138 return MEDIA_SCAN_RESULT_ERROR; 139 } 140 } 141 } 143 return MEDIA_SCAN_RESULT_OK; 144} |
这个地方就要去掉Java层的啦
再来到Java层,看看是不是有这个函数?
android8.1_trunk/frameworks/base/media/java/android/media/MediaScanner.java
500 protected class MyMediaScannerClient implements MediaScannerClient 889 public void setMimeType(String mimeType) { 890 if ("audio/mp4".equals(mMimeType) && 891 mimeType.startsWith("video")) { 892 // for feature parity with Donut, we force m4a files to keep the 893 // audio/mp4 mimetype, even if they are really "enhanced podcasts" 894 // with a video track 895 return; 896 } 897 mMimeType = mimeType; 898 mFileType = MediaFile.getFileTypeForMimeType(mimeType); 899 } } |
本文参考了《深入理解Android卷一》,转载请标明出处!
如果喜欢的话,欢迎到如下下载文档!
http://download.csdn.net/download/snail201211/10159467
相关文章推荐
- Android平台Native开发与JNI机制详解
- Android平台添加外设的架构流程(kernel->HAL->JNI->API->APP)之学习提高篇
- Android平台Native开发与JNI机制详解
- Android平台Native开发与JNI机制详解
- 深入浅出 - Android系统移植与平台开发(十二)- Android JNI机制
- Android平台Native开发与JNI机制详解
- Android平台Native开发与JNI机制详解
- Android平台Native开发与JNI机制
- 深入浅出 - Android系统移植与平台开发(十二)- Android JNI机制
- Android平台Native开发与JNI机制详解
- Android平台Native开发与JNI机制详解
- Android平台添加外设的架构流程(kernel->HAL->JNI->API->APP)之学习提高篇
- Android平台Native开发与JNI机制详解
- Android平台Native开发与JNI机制详解
- Android平台Native开发与JNI机制详解
- Android平台Native开发与JNI机制详解
- android平台从froyo 2.2开始支持jni单步调试了
- Android Activity和Intent机制学习笔记
- Android平台学习基础(2)-SQLite基本操作
- Android 分享两个你学习android 平台开发必须碰到的几个知识点的组件【天气预报、日期】View 组件