基于Android U
JNI用途 JNI是Java Native Interface的缩写,译为Java本地接口,是Java与其他语言通信的桥梁。当出现一些用Java无法处理的任务时,开发人员就可以使用JNI技术来完成。一般来说主要有以下情况需要用到JNI技术。
需要调用Java语言不支持的依赖于操作系统平台特性的一些功能。例如:需要调用当前的UNIX系统的某个功能,而Java不支持这个功能,就需要用到JNI技术来实现。
为了整合一些以前的非Java语言开发的系统。例如:需要用到早期实现的C/C++语言开发的一些功能或系统,将这些功能整合到当前的系统或新的版本中。
为了节省程序的运行时间,必须采用其他语言(比如C/C++语言)来提升运行效率。例如:游戏、音视频开发设计的音视频编解码和图像绘制需要更快的处理速度。
JNI不只是应用于Android开发,它有着非常广泛的应用场景。JNI在Android中的应用场景也十分广泛,主要有音视频开发、热修复和插件化、逆向开发、系统源码调用等。为了更方便地使用JNI技术,Android还提供了NDK这个工具集合,NDK开发是基于JNI的,它和JNI开发本质上并没有区别。
Android系统按语言来划分的话由两个世界组成,分别是Java世界和Native世界。JNI就是将它们连接在一起的桥梁。
MediaRecorder用于录音和录像。Java Framework层对应的是MediaRecorder.java,也就是我们在应用开发中直接调用的类。JNI层对应的是libmedia_jni.so,可以看到这个动态库的名称含有“_jni”,这说明它是一个JNI的动态库。Native层对应的是libmedia.so,这个动态库完成了实际调用的功能。
和JNI有关的源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 frameworks/base/media/java/android/media/MediaRecorder.javapublic class MediaRecorder implements AudioRouting , AudioRecordingMonitor, AudioRecordingMonitorClient, MicrophoneDirection { static { System.loadLibrary("media_jni" ); native_init(); } ... private static native final void native_init () ; ... public native void start () throws IllegalStateException; ... }
在静态代码块中首先调用了注释1处的代码,用来加载名为media_jni的动态库,也就是libmedia_jni.so。接着调用注释2处的native_init方法,其内部会调用Native方法,用来完成JNI的注册。注释3处的native_init方法用native来修饰,说明它是一个native方法,表示由JNI来实现。MediaRecorder的start()方法同样也是一个native方法。对于Java Framework层来说,只需要加载对应的JNI库,接着声明native方法就可以了,剩下的工作由JNI层来完成。
MediaRecorder的JNI层由android_media_recorder.cpp实现,native方法native_init()和start()的JNI层实现如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 frameworks/base/media/jni/android_media_MediaRecorder.cpp static void android_media_MediaRecorder_native_init (JNIEnv *env) { ... }static void android_media_MediaRecorder_start (JNIEnv *env, jobject thiz) { ... }
android_media_MediaRecorder_native_init()方法是native_init()方法在JNI层的实现,android_media_MediaRecorder_start()方法则是start()方法在JNI层的实现。那么native_init()方法是如何找到对应的android_media_MediaRecorder_native_init()方法的呢?这就涉及到了JNI方法注册。
Native方法注册 Native方法注册分为静态注册和动态注册,其中静态注册多用于NDK开发,而动态注册多用于Framework开发。
按照JNI规范书写函数名:java_包名_类名_方法名,包名也用下划线隔开。
静态注册使用简单,但是也有一些缺点:
JNI层的函数名称过长;
声明Native方法的类需要用javah生成头文件;
初次调用Native方法时需要建立关联,影响效率。
JNI中有一种结构用来记录Java的Native方法和JNI方法的关联关系,它就是JNINativeMethod,它在jni.h中被定义。
1 2 3 4 5 typedef struct { const char * name; const char * signature; void * fnPtr; } JNINativeMethod;
系统的MediaRecorder采用的就是动态注册。
1 2 3 4 5 6 7 8 9 10 11 12 frameworks/base/media/jni/android_media_MediaRecorder.cppstatic const JNINativeMethod gMethods[] = { ... {"start" , "()V" , (void *)android_media_MediaRecorder_start}, {"stop" , "()V" , (void *)android_media_MediaRecorder_stop}, {"pause" , "()V" , (void *)android_media_MediaRecorder_pause}, {"resume" , "()V" , (void *)android_media_MediaRecorder_resume}, {"native_reset" , "()V" , (void *)android_media_MediaRecorder_native_reset}, {"release" , "()V" , (void *)android_media_MediaRecorder_release}, {"native_init" , "()V" , (void *)android_media_MediaRecorder_native_init}, ... };
上面定义了一个JNINativeMethod类型的gMethods数组,里面存储的就是MediaRecorder的Native方法与JNI层函数的对应关系,其中注释1处“start()“是Java层的Native方法,它对应的JNI层的函数为android_media_MediaRecorder_start()。”()V“是start()方法的签名信息。只定义JNINativeMethod类型的数组是没有用的,还需要注册它,注册的函数为register_android_media_MediaRecorder(),这个函数在android_media_MediaPlayer的JNI_OnLoad()函数中调用。这个函数会在调用System.loadLibrary()函数后调用,因为多媒体框架中的很多框架都要进行JNINativeMethod数组注册,因此,注册函数就被统一定义在android_media_MediaPlayer.cpp的JNI_OnLoad()函数中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 frameworks/base/media/jni/android_media_MediaPlayer.cpp jint JNI_OnLoad (JavaVM* vm, void * ) { 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_MediaPlayer (env) < 0 ) { ALOGE ("ERROR: MediaPlayer native registration failed\n" ); goto bail; } if (register_android_media_MediaRecorder (env) < 0 ) { ALOGE ("ERROR: MediaRecorder native registration failed\n" ); goto bail; } ... result = JNI_VERSION_1_4; bail: return result; }
在JNI_OnLoad()函数中调用了整个多媒体框架的注册JNINativeMethod数组的函数。
1 2 3 4 5 6 frameworks/base/media/jni/android_media_MediaRecorder.cpp int register_android_media_MediaRecorder (JNIEnv *env) { return AndroidRuntime::registerNativeMethods (env, "android/media/MediaRecorder" , gMethods, NELEM (gMethods)); }
register_android_media_MediaRecorder()中返回了AndroidRuntime的registerNativeMethods()函数。
1 2 3 4 5 6 frameworks/base/core/jni/AndroidRuntime.cpp int AndroidRuntime::registerNativeMethods (JNIEnv* env, const char * className, const JNINativeMethod* gMethods, int numMethods) { return jniRegisterNativeMethods (env, className, gMethods, numMethods); }
registerNativeMethods()函数中又返回了jniRegisterNativeMethods(),它被定义在JNI帮助类JNIHelp.c中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 libnativehelper/JNIHelp.cint jniRegisterNativeMethods (JNIEnv* env, const char * className, const JNINativeMethod* methods, int numMethods) { ALOGV("Registering %s's %d native methods..." , className, numMethods); jclass clazz = (*env)->FindClass(env, className); ALOG_ALWAYS_FATAL_IF(clazz == NULL , "Native registration unable to find class '%s'; aborting..." , className); int result = (*env)->RegisterNatives(env, clazz, methods, numMethods); (*env)->DeleteLocalRef(env, clazz); if (result == 0 ) { return 0 ; } jthrowable thrown = (*env)->ExceptionOccurred(env); if (thrown != NULL ) { struct ExpandableString summary ; ExpandableStringInitialize(&summary); if (GetExceptionSummary(env, thrown, &summary)) { ALOGF("%s" , summary.data); } ExpandableStringRelease(&summary); (*env)->DeleteLocalRef(env, thrown); } ALOGF("RegisterNatives failed for '%s'; aborting..." , className); return result; }
从注释1处可以看出,最终通过调用的JNIEnv的RegisterNatives()函数来完成JNI的注册。
数据类型的转换 JNI数据类型的转换的例子仍旧以MediaRecorder来举例。
1 2 3 4 5 6 7 8 9 10 11 12 frameworks/base/media/jni/android_media_MediaRecorder.cpp static void android_media_MediaRecorder_start (JNIEnv *env, jobject thiz) { ALOGV ("start" ); sp<MediaRecorder> mr = getMediaRecorder (env, thiz); if (mr == NULL ) { jniThrowException (env, "java/lang/IllegalStateException" , NULL ); return ; } process_media_recorder_call (env, mr->start (), "java/lang/RuntimeException" , "start failed." ); }
android_media_MediaRecorder_start()函数的第二个参数为jobject类型,它是JNI层的数据类型,Java的数据类型到了JNI层就需要转换为JNI层的数据类型。Java的数据类型分为基本数据类型和引用数据类型,JNI层对于这两种类型也做了区分。
基本数据类型的转换 除了void,其他的数据类型只需要在前面加上“j”就可以了。
Java
Native
Signature
byte
jbyte
B
char
jchar
C
double
jdouble
D
float
jfloat
F
int
jint
I
short
jshort
S
long
jlong
J
boolean
jboolean
Z
void
void
V
引用数据类型的转换 数组的JNI层数据结构需要以“Array”结尾,签名格式的开头都会有“[”。需要注意有些数据类型的签名以“;”结尾,引用数据类型还具有继承关系。
Java
Native
Signature
所有对象
jobject
L+classname+;
Class
jclass
Ljava/lang/Class;
String
jstring
Ljava/lang/String;
Throwable
jthrowable
Ljava/lang/Throwable;
Object[]
jobjectArray
[L+classname+;
byte[]
jbyteArray
[B
char[]
jcharArray
[C
double[]
jdoubleArray
[D
float[]
jfloatArray
[F
int[]
jshortArray
[I
short[]
jshortArray
[S
long[]
jlongArray
[J
boolean[]
jbooleanArray
[Z
jclass、jstring、jarray和jthrowable都继承自jobject,而jobjectArray、jintArray和jlongArray等类型都继承自jarray。
方法签名 1 2 3 4 5 6 7 8 frameworks/base/media/jni/android_media_MediaRecorder.cppstatic const JNINativeMethod gMethods[] = { ... {"native_init" , "()V" , (void *)android_media_MediaRecorder_native_init}, {"native_setup" , "(Ljava/lang/Object;Ljava/lang/String;Landroid/os/Parcel;)V" , (void *)android_media_MediaRecorder_native_setup}, ... };
gMethods数组中存储的是MediaRecorder的Native方法与JNI层函数的对应关系,其中”()V””和”(Ljava/lang/Object;Ljava/lang/String;Landroid/os/Parcel;)V”就是方法签名。Java是有重载方法的,可以定义方法名相同,参数不同的方法,因此在JNI中仅仅通过方法名是无法找到Java中对应的具体方法的,JNI为了解决这一问题就将参数类型和返回值类型组合在一起作为方法签名。通过方法签名和方法名就可以找到对应的Java方法。JNI的方法签名的格式为:(参数签名格式…)返回值签名格式。
解析JNIEnv JNIEnv是Native世界中Java环境的代表,通过JNIEnv *指针就可以在Native世界中访问Java世界的代码进行操作,它只在创建它的线程中有效,不能跨线程传递,因此不同线程的JNIEnv是彼此独立的,JNIEnv的主要作用有以下两点:
调用Java的方法。
操作Java(操作Java中的变量和对象等)。
先来看JNIEnv的定义,如下所示:
1 2 3 4 5 6 7 8 libnativehelper/include_jni/jni.h#if defined(__cplusplus) typedef _JNIEnv JNIEnv; typedef _JavaVM JavaVM;#else typedef const struct JNINativeInterface * JNIEnv ; typedef const struct JNIInvokeInterface * JavaVM ;#endif
这里使用预定义宏__cplusplus
来区分C和C++两种代码,如果定义了__cplusplus
,就是C++代码中的定义,否则就是C代码中的定义。在这里我们也看到了JavaVM,它是虚拟机在JNI层的代表,在一个虚拟机进程中只有一个JavaVM,因此,该进程的所有线程都可以使用这个JavaVM。通过JavaVM的AttachCurrentThread()函数可以获取这个线程的JNIEnv,这样就可以在不同的线程中调用Java方法了。还要记得在使用AttachCurrentThread()函数的线程退出前,调用DetachCurrentThread()函数来释放资源。
从注释2处可以看出在C中,JNIEnv类型是JNINativeInterface*
,从注释1处可以看出在C++中JNIEnv的类型是_JNIEnv
,_JNIEnv
是如何定义的呢?如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 libnativehelper/include_jni/jni.hstruct _JNIEnv { const struct JNINativeInterface * functions;#if defined(__cplusplus) ... jclass FindClass (const char * name) { return functions->FindClass (this , name); } ... jmethodID GetMethodID (jclass clazz, const char * name, const char * sig) { return functions->GetMethodID (this , clazz, name, sig); } ... jfieldID GetFieldID (jclass clazz, const char * name, const char * sig) { return functions->GetFieldID (this , clazz, name, sig); } ... };
_JNIEnv
是一个结构体,其内部又包含了JNINativeInterface。在_JNIEnv
中又定义了很多函数,这里列举了三个比较常用的函数,FindClass()用来找到Java中指定名称的类,GetMethodID()用来得到Java中的方法,GetFieldID()用来得到Java中的成员变量,这里可以发现这三个函数都调用了JNINativeInterface中定义的函数,因此可以得出结论,无论是C还是C++,JNIEnv的类型都和JNINativeInterface结构有关,JNINativeInterface的定义如下所示:
1 2 3 4 5 6 7 8 9 10 libnativehelper/include_jni/jni.hstruct JNINativeInterface { ... jclass (*FindClass)(JNIEnv*, const char *); ... jmethodID (*GetMethodID)(JNIEnv*, jclass, const char *, const char *); ... jfieldID (*GetFieldID)(JNIEnv*, jclass, const char *, const char *); ... };
在JNINativeInterface结构体中定义了很多和JNIEnv结构体对应的函数指针,这里只给出了上面JNIEnv结构体中对应的三个函数指针定义。通过这些函数指针的定义,就能够定位到虚拟机中的JNI函数表,从而实现了JNI层在虚拟机中的函数调用,这样JNI层就可以调用Java世界的方法了。
jfiledID和jmethodID 在_JNIEnv
结构体中定义了很多函数,这些函数都会有不同的返回值,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 libnativehelper/include_jni/jni.hstruct _JNIEnv { const struct JNINativeInterface * functions;#if defined(__cplusplus) ... jmethodID GetMethodID (jclass clazz, const char * name, const char * sig) { return functions->GetMethodID (this , clazz, name, sig); } ... jfieldID GetFieldID (jclass clazz, const char * name, const char * sig) { return functions->GetFieldID (this , clazz, name, sig); } ... };
这里列举了两个函数,这两个函数的返回值分别为jmethodID和jfieldID。jfieldID和jmethodID分别用来代表Java类中的成员变量和方法。
注释1处jclass代表Java类,name代表成员方法或者成员变量的名字,sig为这个方法和变量的签名。我们来查看MediaRecorder框架的JNI层是如何使用GetMethodID()和GetFieldID()这两个方法的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 frameworks/base/media/jni/android_media_MediaRecorder.cpp static void android_media_MediaRecorder_native_init (JNIEnv *env) { jclass clazz; clazz = env->FindClass ("android/media/MediaRecorder" ); if (clazz == NULL ) { return ; } fields.context = env->GetFieldID (clazz, "mNativeContext" , "J" ); if (fields.context == NULL ) { return ; } fields.surface = env->GetFieldID (clazz, "mSurface" , "Landroid/view/Surface;" ); if (fields.surface == NULL ) { return ; } jclass surface = env->FindClass ("android/view/Surface" ); if (surface == NULL ) { return ; } fields.post_event = env->GetStaticMethodID (clazz, "postEventFromNative" , "(Ljava/lang/Object;IIILjava/lang/Object;)V" ); if (fields.post_event == NULL ) { return ; } clazz = env->FindClass ("java/util/ArrayList" ); if (clazz == NULL ) { return ; } gArrayListFields.add = env->GetMethodID (clazz, "add" , "(Ljava/lang/Object;)Z" ); gArrayListFields.classId = static_cast <jclass>(env->NewGlobalRef (clazz)); }
注释1处,通过FindClass()来找到Java层的MediaRecorder的Class对象,并赋值给jclass类型的clazz,因此,clazz就是Java层的MediaRecorder在JNI层的代表。
注释2、3处的代码分别用来找到Java层的MediaRecorder中名为mNativeContext和mSurface的成员变量,并分别赋值给context和surface。
注释4处获取Java层的MediaRecorder中名为postEventFromNative()的静态方法,并赋值给fields.post_event,其中fields的定义为:
1 2 3 4 5 6 7 8 frameworks/base/media/jni/android_media_MediaRecorder.cppstruct fields_t { jfieldID context; jfieldID surface; jmethodID post_event; };static fields_t fields;
将这些成员变量和方法赋值给jfieldID和jmethodID类型的变量有两个原因:第一是为了效率考虑,如果每次调用相关方法时都要查询方法和变量,显然会效率很低;第二是这些成员变量和方法都是本地引用,在android_media_MediaRecorder_native_init()函数返回时这些本地引用会被自动释放,因此用fields来进行保存,以便后续使用。结合上面两方面原因,在MediaRecorder框架JNI层的初始化方法android_media_MediaRecorder_native_init()中将这些jfieldID和jmethodID类型的变量保存起来,是为了更高效率地供后续使用。
使用jfiledID和jmethodID 保存了jfiledID和jmethodID类型的变量,接着怎么使用它们呢?如下所示:
1 2 3 4 5 6 7 8 frameworks/base/media/jni/android_media_MediaRecorder.cpp void JNIMediaRecorderListener::notify (int msg, int ext1, int ext2) { ALOGV ("JNIMediaRecorderListener::notify" ); JNIEnv *env = AndroidRuntime::getJNIEnv (); env->CallStaticVoidMethod (mClass, fields.post_event, mObject, msg, ext1, ext2, NULL ); }
注释1处调用了JNIEnv的CallStaticVoidMethod()函数,其中就传入了fields.post_event,它保存了Java层MediaRecorder的静态方法postEventFromNative()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 frameworks/base/media/java/android/media/MediaRecorder.javaprivate static void postEventFromNative (Object mediarecorder_ref, int what, int arg1, int arg2, Object obj) { MediaRecorder mr = (MediaRecorder)((WeakReference)mediarecorder_ref).get(); if (mr == null ) { return ; } if (mr.mEventHandler != null ) { Message m = mr.mEventHandler.obtainMessage(what, arg1, arg2, obj); mr.mEventHandler.sendMessage(m); } }
注释1处会创建一个消息,注释2处将这个消息发送给MediaRecoder内部类mEventHandler来处理,这样做的目的是将代码逻辑运行在应用程序的主线程中。JNIEnv的CallStaticVoidMethod()函数会调用Java层MediaRecoder的静态方法postEventFromNative(0,也就是说JNIEnv的CallStaticVoidMethod()函数可以访问Java的静态方法,同理如果想要访问Java的方法则可以使用JNIEnv的CallStaticVoidMethod()函数。上面的例子是使用了jmethodID,接着来查看jfieldID是如何应用的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 frameworks/base/media/jni/android_media_MediaRecorder.cpp static void android_media_MediaRecorder_prepare (JNIEnv *env, jobject thiz) { ALOGV ("prepare" ); sp<MediaRecorder> mr = getMediaRecorder (env, thiz); if (mr == NULL ) { jniThrowException (env, "java/lang/IllegalStateException" , NULL ); return ; } jobject surface = env->GetObjectField (thiz, fields.surface); if (surface != NULL ) { const sp<Surface> native_surface = get_surface (env, surface); ... } process_media_recorder_call (env, mr->prepare (), "java/io/IOException" , "prepare failed." ); }
注释1处调用了JNIEnv的GetObjectField()函数,参数中的fields.surface是jfieldID类型的变量,用来保存Java层MediaRecorder中的成员变量mSurface,mSurface的类型为Surface,这样通过GetObjectField()函数就得到了mSurface在JNI层中对应的jobject类型变量surface。
引用类型 和Java的引用类型一样,JNI也有引用类型,它们分别是本地引用(Local References)、全局引用(Global References)和弱全局引用(Weak Global References)。
本地引用 JNIEnv提供的函数所返回的引用基本上都是本地引用,因此本地引用也是JNI中最常见的引用类型。本地引用的特点主要有以下几点:
当native函数返回时,这个本地引用就会被自动释放。
只在创建它的线程中有效,不能够跨线程使用。
局部引用是JVM负责的引用类型,受JVM管理。
1 2 3 4 5 6 7 8 9 10 11 12 frameworks/base/media/jni/android_media_MediaRecorder.cpp static void android_media_MediaRecorder_native_init (JNIEnv *env) { jclass clazz; clazz = env->FindClass ("android/media/MediaRecorder" ); if (clazz == NULL ) { return ; } ... }
注释1处的FindClass()会返回clazz,这个clazz就是本地引用,它会在android_media_MediaRecorder_native_init()函数调用返回后被自动释放。我们也可以使用JNIEnv的DeleteLocalRef()函数来手动删除本地引用,DeleteLocalRef()函数的使用场景主要是在native函数返回前占用了大量内存,需要调用DeleteLocalRef()函数立即删除本地引用。
全局引用 全局引用和本地引用几乎是相反的,它主要有以下特点:
当native函数返回时不会被自动释放,因此全局引用需要手动来进行释放,并且不会被GC回收。
全局引用是可以跨线程使用的。
全局引用不受到JVM管理。
JNIEnv的NewGlobalRef()函数用来创建全局引用,调用JNIEnv的DeleteGlobalRef()函数来释放全局引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 frameworks/base/media/jni/android_media_MediaRecorder.cpp JNIMediaRecorderListener::JNIMediaRecorderListener (JNIEnv* env, jobject thiz, jobject weak_thiz) { jclass clazz = env->GetObjectClass (thiz); if (clazz == NULL ) { ALOGE ("Can't find android/media/MediaRecorder" ); jniThrowException (env, "java/lang/Exception" , NULL ); return ; } mClass = (jclass)env->NewGlobalRef (clazz); mObject = env->NewGlobalRef (weak_thiz); }
注释1处返回的clazz是本地引用,并传入到注释2处,在注释2处调用JNIEnv的NewGlobalRef()函数将clazz转变为全局引用mClass。那什么时候将全局引用mClass释放呢?我们来查看JNIMediaRecorderListener的析构函数:
1 2 3 4 5 6 7 8 frameworks/base/media/jni/android_media_MediaRecorder.cpp JNIMediaRecorderListener::~JNIMediaRecorderListener () { JNIEnv *env = AndroidRuntime::getJNIEnv (); env->DeleteGlobalRef (mObject); env->DeleteGlobalRef (mClass); }
从上面的析构函数的注释就可以知道,这个析构函数用来释放全局引用,在注释1处释放了全局引用mClass。
弱全局引用 弱全局引用是一种特殊的全局引用,它和全局引用的特点相似,不同的是弱全局引用是可以被GC回收的,弱全局引用被GC回收之后会指向NULL。JNIEnv的NewWeakGlobalRef()函数用来创建弱全局引用,调用JNIEnv的DeleteWeakGlobalRef()函数来释放弱全局引用。由于弱全局引用可能被GC回收,因此在使用它之前要先判断它是否被回收了,方法就是使用JNIEnv的IsSameObject()函数来判断。