Android:JNI
一、分析的文件路径
./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.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; }}
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<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.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层的数据类型的转换关系
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 |
2.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"); }<div class="se-preview-section-delimiter"></div>
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层抛出异常
更多相关文章
- 详解 Android(安卓)的 Activity 组件
- Unity调用Android配置方法
- Android(安卓)Power Management
- Android(安卓)如何获取keyboard和TP消息 分享
- Android应用程序启动过程源代码分析
- 利用HTML5开发Android
- unity与Android相互调用
- android WebView总结
- Android截屏浅析