深入理解Android-JNI的理解

理解JNI需要理解以下问题:

  • JNI的认识
  • JNI库的加载、相关native函数分析和总结【借助于MediaScanner】
  • JNI函数注册
  • JNIEnv的认识

JNI的认识

JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。在安卓中,主要做到以下两点:

  • Java程序中函数可以调用Native语言写的函数,Native一般指C/C++编写的函数

  • Native函数也可以反过来调用Java层函数

Java平台中,为什么需要创建一个与Native相关的JNI技术呢?是不是破坏了Java的平台无关特性,其实主要考虑到如下方面:

  • Java世界的虚拟机使用Native语音写的,虚拟机又运行在具体的平台上,所有虚拟机是无法做到平台无关的。但是,JNI技术可以针对Java层屏蔽不同操作系统之间的差异,这样就能够实现平台无关特性。
  • C/C++语音已经有了很多成熟的模块,Java只需要直接调用即可。还有一些追求效率和速度的场合,需要Native语音参与的。
    在Android平台上,JNI就是一座将Native世界和Java世界的天堑变通途的桥。
    深入理解Android-JNI的理解_第1张图片

本文从源码中的MediaScanner中来初步窥视JNI用法
深入理解Android-JNI的理解_第2张图片
上图可以看到Java层 、JNI层、Native层之间的架构,JNI是中间层【这里区分了libmedia_jni.so、libmedia.so:平常开发直接用一个.so 文件。这里区分为了说明JNI层和Native层】

  • Java世界对于的是MediaScanner,他内部的一些函数是需要由Native层来实现的。
  • JNI层对于的是libmedia_jni.so 。Android平台基本上都是采用“lib模块名_jni.so”的命名方式。
  • Native层对应的是libmedia.so 这个库完成了实际的功能。
  • MediaScanner将通过JNI层的libmedia_jni.so和Native层的libmedia.so交互

源码位置:android\frameworks\base\media\java\android\media\MediaScanner.java


public class MediaScanner
{
//1、静态代码块
    static {
        System.loadLibrary("media_jni");      //导入库
        native_init();                        //初始化操作,是一个native方法
    }

   //2、普通方法【非native方法】
   @Override
        public void scanFile(String path, long lastModified, long fileSize,
                boolean isDirectory, boolean noMedia) {
            // This is the callback funtion from native codes.
            // Log.v(TAG, "scanFile: "+path);
            doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia);
        }
//3、native 方法【它的具体实现实在JNI层完成的】
    private native void processDirectory(String path, MediaScannerClient client);
    private native void processFile(String path, String mimeType, MediaScannerClient client);
    public native void setLocale(String locale);

    public native byte[] extractAlbumArt(FileDescriptor fd);

    private static native final void native_init();
    private native final void native_setup();
    private native final void native_finalize();

}

上面可以看到三点:1)加载JNI库 2)初始化Native层 3)声明native方法,具体实现再JNI层实现,应用层只需要声明native方法,程序中直接使用

JNI库的加载

加载jni库其实很简单,如上面MediaScanner源码中,静态代码块中直接调用System.loadLibrary方法就可以了。其参数就是动态库的名字,即media_jni.系统会根据不同的平台拓展成动态库文件,Linux系统会拓展成libmedia_jni.so,而在Windows平台会拓展成media_jni.dll

通过MediaScanner.java 的native函数刨根问底,追一下native_init() 和 processFile(String path, String mimeType, MediaScannerClient client)

源码位置:android\frameworks\base\media\jni\android_media_MediaScanner.cpp


// This function gets a field ID, which in turn causes class initialization.
// It is called from a static block in MediaScanner, which won't run until the
// first time an instance of this class is used.
static void android_media_MediaScanner_native_init(JNIEnv *env)
{
    jclass clazz = env->FindClass(kClassMediaScanner);
    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
    if (fields.context == NULL) {
        return;
    }
}
//native 方法processFile的实现
static void android_media_MediaScanner_processFile(
        JNIEnv *env, jobject thiz, jstring path,
        jstring mimeType, jobject client)
{
   ......
    MediaScanner *mp = getNativeScanner_l(env, thiz);
    const char *pathStr = env->GetStringUTFChars(path, NULL);
    if (pathStr == NULL) {  // Out of memory
        return;
    }
    const char *mimeTypeStr =
        (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);
......
    MyMediaScannerClient myClient(env, client);
    MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient);
    env->ReleaseStringUTFChars(path, pathStr);
    if (mimeType) {
        env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
    }
}

有兴趣好好看一下源码,肯定会有很多疑问,比如:JNIEnv 、FindClass、jclass、ReleaseStringUTFChars 等,都是什么,都在执行什么任务。

不要急,且看如下内容!

JNI函数注册

所谓JNI函数注册就是将JNI层的native层的native函数和JNI层对于的实现函数关联起来,有了这种关联,在调用Java层测native函数时候,就能够顺利到JNI层对应的函数执行了。JNI函数注册有两种,我们主要分析第二种。

  • 静态注册

    静态注册就是直接在Java文件里写个native方法 然后再c/c++文件中实现这个方法就行了!流程如下:
    1)编写 java 代码;
    2)利用 javah 指令生成对应的 .h 文件;
    3)对 .h 中的声明进行实现;
    比如如下实现:

JNIEXPORT jstring JNICALL
Java_com_example_efan_jni_1learn2_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

那么有哪些弊端呢:
1)编写不方便,JNI 方法名字必须遵循规则且名字很长;
2)编写过程步骤多,不方便;
3)程序运行效率低,因为初次调用native函数时需要根据根据函数名在JNI层中搜索对应的本地函数,然后建立对应关系,这个过程比较耗时

  • 动态注册
    原理:
    利用 RegisterNatives 方法来注册 java 方法与 JNI 函数的一一对应关系
    实现流程:
    1) 利用结构体 JNINativeMethod 数组记录 java 方法与 JNI 函数的对应关系;
    2)实现 JNI_OnLoad 方法,在加载动态库后,执行动态注册;
    3)调用 FindClass 方法,获取 java 对象;
    4)调用 RegisterNatives 方法,传入 java 对象,以及 JNINativeMethod 数组,以及注册数目完成注册;
    优点:
    1)流程更加清晰可控;
    2)效率更高;
    静态改动态注册,只需要改JNI层代码:
jstring stringFromJNI(JNIEnv *env, jobject thiz){
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}


static const JNINativeMethod gMethods[] = {
        {"stringFromJNI", "()Ljava/lang/String;", (jstring*)stringFromJNI}
};


JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){

    __android_log_print(ANDROID_LOG_INFO, "native", "Jni_OnLoad");
    JNIEnv* env = NULL;
    if(vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) //从JavaVM获取JNIEnv,一般使用1.4的版本
        return -1;
    jclass clazz = env->FindClass("com/example/efan/jni_learn2/MainActivity");
    if (!clazz){
        __android_log_print(ANDROID_LOG_INFO, "native", "cannot get class: com/example/efan/jni_learn2/MainActivity");
        return -1;
    }
    if(env->RegisterNatives(clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])))
    {
        __android_log_print(ANDROID_LOG_INFO, "native", "register native method failed!\n");
        return -1;
    }
    return JNI_VERSION_1_4;
}

如上是一个简易的动态注册流程。
Java native函数和JNI函数式一一对应的,其实在在动态注册中直接用一个数据结构来保存这种关联关系的,用一个JNINativeMethod 的结构。

typedef struct {
const char* name;         //name是Java中函数的名字
const char* signature;    //signature,用字符串是描述了函数的参数和返回值 
void* fnPtr;              //fnPtr是函数指针,指向C函数。
} JNINativeMethod;

咋们再分析下MediaScanner
源码位置:master\android\frameworks\base\media\jni\android_media_MediaScanner.cpp


static JNINativeMethod gMethods[] = {
    {
        "processDirectory",
        "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
        (void *)android_media_MediaScanner_processDirectory
    },

    {
        "processFile",
        "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
        (void *)android_media_MediaScanner_processFile
    },

    {
        "setLocale",
        "(Ljava/lang/String;)V",
        (void *)android_media_MediaScanner_setLocale
    },

    {
        "extractAlbumArt",
        "(Ljava/io/FileDescriptor;)[B",
        (void *)android_media_MediaScanner_extractAlbumArt
    },

    {
        "native_init",
        "()V",
        (void *)android_media_MediaScanner_native_init
    },

    {
        "native_setup",
        "()V",
        (void *)android_media_MediaScanner_native_setup
    },

    {
        "native_finalize",
        "()V",
        (void *)android_media_MediaScanner_native_finalize
    },
};

其中:processDirectory 、 processFile 、 setLocale 、 extractAlbumArt 、 native_init 、native_setup 、 native_finalize 方法不就是Java中native的放方法名么。

AndroidRunTime 类提供了一个registerNativeMethods函数完成注册工作的,如下源码:
\master\android\frameworks\base\core\jni\AndroidRuntime.cpp


/*
 * Register native methods using JNI.
 */
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
    const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);  //就是这个方法
}

其中的jniRegisterNativeMethods是Android平台中为了方便JNI使用而提供的一个帮助函数。【RndroidRunTime源码可以看一看,有很多看了就非常熟悉的内容】

\master\android\libnativehelper\JNIHelp.cpp


extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    JNIEnv* e = reinterpret_cast(env);
    scoped_local_ref c(env, findClass(env, className));
   ---
   //下面的RegisterNatives 才是重点方法:真正实现注册的地方
   //调用JNIEnv 的RegiisterNatives 函数,注册关联关系
   //e 是env指针   c.get()得到jclass  gMethods是native的函数数组  
    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
      ---
    }
    return 0;
}

上面就完成了动态注册的流程, 还有一个问题需要考虑:这些动态注册的函数,什么时候和什么地方调用呢?
当Java层通过System.loadLibrary加载JNI动态库后,紧接着会查找该库中的一个叫JNI_OnLoad的函数,如果有就调用它,而动态注册的工作就是在这里完成的。

所以,如果想使用动态注册的方法,必须实现JNI_OnLoad函数,只有在这个函数中才有机会去完成动态注册的工作,有一些初始化工作可以在这里做的。
对于libmediia_jni.so的JNI_OnLoad函数实在哪里实现?多媒体系统很多地方都用了JNI,所以源码里面放在了android_media_MediaPlayer.cpp中。
\master\android\frameworks\base\media\jni\android_media_MediaPlayer.cpp



jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
//第一个参数类型是JavaVM,这可是虚拟机在JNI层的代表,每个进程中只有一个这样的JavaVM
    JNIEnv* env = NULL;
    jint result = -1;
    assert(env != NULL);
    //动态注册MediaScanner 的JNII函数
    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;
}

总结流程如下图所示:
深入理解Android-JNI的理解_第3张图片

当Java层通过调用System.loadLibrary加载完JNI动态库后,紧接着会查找该库中一个叫JNI_OnLoad的函数。如果有,就调用他,而动态注册的工作就是在这里完成的。
所以,如果想使用动态注册方法,必须实现JNI_OnLoad函数,只有这个函数才有机会完成动态注册的工作,静态注册则没有这个要求。

JNIEnv的认识

你会发现在JNI世界里,你是离不开JNIEnv,它就是一个与线程相关的代表JNI环境的结构体
深入理解Android-JNI的理解_第4张图片

上图可以看到:JNIEnv提供了一些JNI系统函数,通过这些函数可以

  • 调用Java函数【jni层调用java层】
  • 操作jobject对象等很多事情【jni层调用native】

前面提到过JNI_OnLoad函数,第一个参数是JavaVM,它是虚拟机在JNI层的代表。

JNI_OnLoad(JavaVM* vm, void* reserved)

不论检查中多少个线程,JavaVM独此一份,在任意地方都可以使用它。
JavaVM和JNIEnv关系:

  • JavaVM的AttachCurrentThread函数可以得到这个线程的JNIEnv结构体,这样后台就可以毁掉Java函数
  • 后台线程推出前,需要调用JvavVM的DetachCurrentThread函数来释放对应的资源

总结:看看JNI底层实现,分析源码,初步了解JNI。具体实践还需要了解JNI层是怎么操作Java层函数和调用JNI层函数的。

参考文章

  • 《深入理解Android卷I》
  • JNI完全指南-JavaVM与JNIEnv
  • JNI与JVM
  • -

你可能感兴趣的:(深入理解Android)