Android: JNI

    xiaoxiao2021-03-26  9

    一、分析的文件路径

    ./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.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。

    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.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; }

    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实例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 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); }

    2.3.2 JNI类型签名介绍

    2.3.3 垃圾回收

    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的引用计数 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层抛出异常
    转载请注明原文地址: https://ju.6miu.com/read-650136.html

    最新回复(0)