一、分析的文件路径

./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层抛出异常

更多相关文章

  1. C语言函数以及函数的使用
  2. Android 的相关文件类型
  3. 下载Android SDK tools完成Android SDK 安装、配置环境变量
  4. android sqlite 数据类型
  5. 在Android程序中使用全局变量
  6. 设置提示信息,输入值类型,输入框引入图片,设置输入框的形状
  7. Android的xml文件中引用类型
  8. Android新增一个音频类型及双音频输出的实现

随机推荐

  1. Android(安卓)开发中踩过的坑之八:多进程
  2. 一文带你了解Android(安卓)Jetpack
  3. Android(安卓)添加底层核心服务
  4. Android界面设计专业术语:xxxhdpi和4K分辨
  5. android 简单实现指纹识别功能
  6. Android接口回调,最简单的理解方式
  7. 关于Activity的启动模式
  8. 【BUG修补匠】 Android微信支付返回码-1
  9. Android(安卓)为点击事件添加震动效果
  10. 在Android(安卓)中Fragment 中嵌套使用Fr