./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
// frameworks/base/media/java/android/media/MediaScanner.java
public 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。
//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.so include $(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;
}
}
大体的流程如下:
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.cpp
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
ALOGV("Registering %s natives", className);
scoped_local_ref<jclass> 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.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) {
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;
}
java层的数据类型和Jni层的数据类型的转换关系
Java | Native类型 | 符号属性 | 字长 |
---|---|---|---|
boolean | jboolean | 无符号 | 8位 |
byte | jbyte | 无符号 | 8位 |
char | jchar | 无符号 | 16位 |
short | jshort | 有符号 | 16位 |
int | jint | 有符号 | 32位 |
long | jlong | 有符号 | 64位 |
float | jfloat | 有符号 | 32位 |
double | jdouble | 有符号 | 64位 |
Java的基本类型和Native层的基本类型转换非常简单,不过必须注意转换成Native类型后对应数据类型的字长,例如jchar在Native语言中是16位,占两个字节,这和普通的char占一个自己的情况是不一样的。
下面是Java引用数据类型和Native类型的转换表
Java引用类型 | Native类型 |
---|---|
All objects | jobject |
java.lang.Class实例 | jclass |
java.lang.String实例 | jstring |
Object[] | jobjectArray |
boolean[] | jbooleanArray |
byte[] | jbyteArray |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
long[] | jlongArray |
float[] | floatArray |
double[] | jdoubleArray |
java.lang.Throwable实例 | jthrowable |
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) {
...
}
...
}
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 void
android_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);
}
Java中创建的对象最后是由垃圾回收器来回收和释放内存的,可它对JNI有什么影响呢?下面看一个例子
static jobject save_thiz = NULL;//定义一个全局的jobject
static void
android_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的引用计数
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");
}
<div class="se-preview-section-delimiter"></div>
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提供了三个函数给予帮助: