JNI : Java Native Interface 即JAVA本地调用,为何需要这种技术呢?原因有二:
1、运行JAVA程序的虚拟机是用Native语言编写的,而虚拟机运行在具体的平台上,所以虚拟机本身无法做到平台无关,而利用JNI技术即可对JAVA层屏蔽不同操作系统平台之间的差异,如file,socket等
2、在JAVA语言诞生前,很多程序使用Native语言编写,JAVA直接利用JNI使用,避免造重复轮子的坏名声。而且JNI的运行效率和速度会更高

第一大部分: JAVA如何调用Native函数

通过MediaScanner进行完整解释其工作原理:

调用层次关系,图示:

android --- 深入理解 JNI_JAVA



JAVA层代码分析:MediaScanner.java
 public class MediaScanner
 {
     static {  //static 语句,前面的 JAVA学习系列(一) 有说明过这个问题
     //加载对应用JNI库,linux上即调用libmedai_jni.so,而windows平台上调用libmedia_jni.dll
         System.loadLibrary("media_jni");
         native_init(); //调用native函数
     //非native函数
 public void scanDirectories(String[] directories, String volumeName) //声明native函数,native为JAVA语言的关键字
 private static native final void native_init();
     private native final void native_setup();
     private native final void native_finalize();
     private native void processDirectory(String path, String extensions, MediaScannerClient client);
 }


总结: 一个是加载jni动态库,二是声明java的native函数

JNI层 android_media_MediaScanner.cpp 分析:

首先来找下java层的native_init函数对应jni层的函数?
首先native_init函数位于android.media包中,它的全路径:android.media.MediaScanner.native_init,由于
在native语言中,"."有特殊的意义,所以用"_"来替换:即上面路径这为:android_media_MediaScanner_native_init
ok,函数名就如此关联起来了。而JNI函数注册有两种方式:
1、静态注册方法
a、编写java代码,然后编译生成.class文件
b、使用javah -o output packagename.classname生成jni层头文件
调用时先加载动态库,然后查找native_init函数的jni函数:android_media_MediaScanner_native_init
如果找到则建立这两个函数的关联关系,即保存jni层函数的函数指针,这个由虚拟机完成。
缺点:javah生成的函数名特别长,不利于书写,且第一次调用时需要根据函数名字搜索建立关联关系。

2、动态注册方法
   利用JNINativeMethod结构保存其关系

typedef struct
    {
     //JAVA中native函数名字
     const char *name;
     //签名信息,用字符串表示,参数类型及返回值类型的组合
     const char *signature;
     ///JNI层函数函数指针,转换为void*类型
     void *fnPtr;
    };
   
 static JNINativeMethod gMethods[] = {
     {"processDirectory",  "(Ljava/lang/String;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},
 };

这里说明一下签名信息:
因为JAVA支持函数重载,可以定义同名但不同参数的函数,但直接根据函数名是没法找到具体函数的,因此利用参数类型及返回类型
组成签名信息。

常用类型标识符:
类型标识   JAVA类型    字长

Z        boolean      8位
   B        byte         8位
   C        char         16位 -- 注意哟
   S        short        16位
   I        int          32位
   J        long         64位
   F        float        32位
   D        double       64位
   L/java/languageString  String
   [I       int[]        int数组
   [L/java/lang/object    // This function only registers the native methods, and is called from
 // JNI_OnLoad in android_media_MediaPlayer.cpp
 int register_android_media_MediaScanner(JNIEnv *env)
 {
 //利用registerNativeMethods注册JNI函数
     return AndroidRuntime::registerNativeMethods(env,
                 "android/media/MediaScanner", gMethods, NELEM(gMethods));
 } // 加载jni库,查找该库中的JNI_OnLoad函数完成动态注册工作
 jint JNI_OnLoad(JavaVM* vm, void* reserved)
 {
 ...
 if (register_android_media_MediaScanner(env) < 0) {
         LOGE("ERROR: MediaScanner native registration failed\n");
         goto bail;
     }
 } static void
 android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client)
 {
     MyMediaScannerClient myClient(env, client);
     mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env);
 ...
 } NATIVE库实现:MediaScanner.cpp (编译成libmedia.so库)
 status_t MediaScanner::processDirectory(
         const char *path, const char *extensions,
         MediaScannerClient &client,
         ExceptionCheck exceptionCheck, void *exceptionEnv) {
     ...
         status_t result =
         doProcessDirectory(
                 pathBuffer, pathRemaining, extensions, client,
                     free(pathBuffer);
     ...
 }


如此整个流程调用逻辑就讲完了。

第二大部分:JNIEnv 介绍

JNIEnv 是一个与线程相关的变量,由于线程相关,所以线程B中不能使用线程A中的JNIEnv函数。

那个多个线程由谁来保存并保证每个线程的JNIEnv结构体正确呢?

android --- 深入理解 JNI_JAVA



 

jint JNI_OnLoad(JavaVM* vm, void* reserved)
全进程只有一个JavaVM对象,可以保存并在任何地方使用没有问题,独此一份。
利用JavaVM中的 AttachCurrentThread函数,就可以得到这个线程的 JNIEnv结构体,利用用DetachCurrnetThread释放相应资源

a、通过 JNIEnv 操作 jobject

jfieldID   操作成员变量

jmethodID 
 static jfieldID GetFieldID(JNIEnv* env, jclass jclazz,
     static jmethodID GetMethodID(JNIEnv* env, jclass jclazz, const char* name,
     const char* sig)


   
具体实现参考:dalvik\vm\jni.c的实现:重要的函数还有 JNI_CreateJavaVM [Create a new VM instance]

举例说明JNI如何调用JAVA层函数:

JNIEnv *mEnv;
 jmethodID mScanFileMethodID;
 jmethodID mHandleStringTagMethodID; MyMediaScannerClient(JNIEnv *env, jobject client)
     :   mEnv(env),
         mClient(env->NewGlobalRef(client)),
         mScanFileMethodID(0),
         mHandleStringTagMethodID(0),
         mSetMimeTypeMethodID(0)
 {
 ...// 找到 android.media.MediaScannerClient类在JNI层对应用用jclass实例
 jclass mediaScannerClientInterface = env->FindClass("android/media/MediaScannerClient");//依此取出 MediaScannerClient 类中的函数 scanFile/handleStringTag 的 jMethodID
     mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile",
                                              "(Ljava/lang/String;JJ)V");
     mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface, "handleStringTag",
                                              }
 // returns true if it succeeded, false if an exception occured in the Java code
 virtual bool scanFile(const char* path, long long lastModified, long long fileSize)
 {
 // 调用JNIEnv的CallVoidMethod函数:
 // 第一个参数代表 MediaScannerClient 的 jobject 对象,第二个参数代表 scanFile的 jMethodID
 // 后面的是JAVA层 ScanFile 函数参数
     mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);
 }

实现上JNIEnv提供了一系列类似的函数调用JAVA函数:

NativeType Call<type>Method(JNIEnv *env, jobject obj,jmethodID methodID,...)

同时也可以设定或获取JAVA层的成员变量值,定义如下:
NativeType Get<type>Field(JNIEnv *env,jobject obj,jfieldID fieldID);
NativeTYpe Set<type>Field(JNIEnv *env,jobject obj,jfieldID fieldID,NativeType value);