【Android 系统开发】_“核心技术”篇 -- JNI

开篇

核心源码

关键类 路径
MediaScanner.java frameworks/base/media/java/android/media/MediaScanner.java
android_media_MediaScanner.cpp frameworks/base/media/jni/android_media_MediaScanner.cpp
android_media_MediaPlayer.cpp frameworks/base/media/jni/android_media_MediaPlayer.cpp
AndroidRuntime.cpp frameworks/base/core/jni/AndroidRuntime.cpp

JNI概述

JNI是Java Native Interface的缩写,中文译为“Java本地调用”,通俗地说,JNI是一种技术,经过这种技术能够作到如下两点:html

      ✨ Java程序中的函数能够调用Native语言写的函数,Native通常指的是C/C++编写函数;
      ✨ Native程序中的函数能够调用Java层的函数,也就是说在C/C++程序中能够调用Java的函数;java

在平台无关的Java中,为何要建立一个与Native相关的JNI技术呢?这岂不是破坏了Java的平台无关特性吗?JNI技术的推出主要有如下几个方面的考虑:
      ✨ 承载Java世界的虚拟机是用Native语言写的,而虚拟机又运行在具体的平台上,因此虚拟机自己没法作到平台无关。然而,有了JNI技术后,就能够对Java层屏蔽不一样操做系统平台之间的差别了。这样,就能实现Java自己的平台无关特性。
      ✨ 在Java诞生以前,不少程序都是用Native语言写的,随后Java后来受到追捧,而且迅速发展,可是做为一门高级语言,没法将软件世界完全的改变。那么既然Native模块实现了许多功能,那么在Java中直接经过JNI技术去使用它们不久能够了?android

因此,咱们能够把JNI看做一座将Native世界和Java世界互联起来的一座桥梁(特殊说明:JNI层的代码也是用Native写的哦!)。数据库

原理图以下:数组

流程图.png

一概的讲原理很枯燥,咱们直接以实际的代码做为范例来学习JNI的原理和实际使用!服务器

MediaScanner

若是你是作Android系统开发和维护工做的,那么你确定听过MediaScanner,那咱们就拿它来举例,看看它和JNI之间是如何关联的。函数

(MediaScanner是Android平台中多媒体系统的重要组成部分,它的功能是扫描媒体文件,获得诸如歌曲时长、歌曲做者等媒体信息,并将他们存入到媒体数据库中,拱其余应用程序使用。)工具

MediaScanner和它的JNI:源码分析

流程图.png

咱们简单说明下这个流程图:学习

      ✨ Java世界对应的是MediaScanner,而这个MediaScanner类有一些函数须要由Native层来实现(定义了一些Native函数,具体实现代码在Native层)
      ✨ JNI层对应的是libmedia_jni.so。
             · media_jni是JNI库的名字,其中下划线前的“media”是Native层库的名字,这里就是libmedia库。下划线后的“jni”表示它是一个JNI库。
             · Android平台基本上都采用“lib模块名_jni.so”来命名JNI库。
      ✨ Native层对应的是libmedia.so,这个库完成了实际的功能。
      ✨ MediaScanner将经过JNI库libmedia_jni.so和Native层的libmedia.so交互。

源码分析 - Java层

MediaScanner.java

咱们先来看看MediaScanner在Java层中关于JNI的代码:

package android.media;

public class MediaScanner implements AutoCloseable {
    static {                        // static语句
        // 这个咱们以前说过,media_jni为JNI库的名字,实际加载动态库的时候会将其拓展成libmedia_jni.so
        System.loadLibrary("media_jni");    
        native_init();              // 调用native_init函数
    }
    ... ...

    private native void processFile(String path, String mimeType, MediaScannerClient client);
    ... ...
    
    private static native final void native_init();  // 申明一个native函数。native为Java的关键字,表示它由JNI层实现。

    ... ...
}

OK,以上代码列出了两个重要的要点:(1)加载JNI库;(2)调用Java的native函数

加载JNI库

咱们前面说到过,若是Java要调用native函数,就必须经过一个位于JNI层的动态库来实现。那么这个动态库在何时、什么地方加载?

原则上,在调用native函数以前,咱们能够在任什么时候候、任何地方去加载动态库。但通常通行的作法就是在类的static语句中加载,调用System.loadLibrary方法就能够了。

native函数

咱们发现native_init和processFile函数前面都有Java的关键字native,这个就表示函数将由JNI层来实现。

因此在Java层面去使用JNI只要作两项工做:(1)加载对应的JNI库;(2)申明由关键字native修饰的函数。

源码分析 - JNI层

实现函数

接下来咱们看下Java层中定义的两个native函数在JNI层的实现。

native_init的JNI层实现

static const char* const kClassMediaScanner =
        "android/media/MediaScanner";

static void
android_media_MediaScanner_native_init(JNIEnv *env)
{
    jclass clazz = env->FindClass(kClassMediaScanner);
    if (clazz == NULL) {
        return;
    }

    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
    if (fields.context == NULL) {
        return;
    }
}

processFile的JNI层实现

static void
android_media_MediaScanner_processFile(
        JNIEnv *env, jobject thiz, jstring path,
        jstring mimeType, jobject client)
{
    // Lock already hold by processDirectory
    MediaScanner *mp = getNativeScanner_l(env, thiz);
    ... ...

    const char *pathStr = env->GetStringUTFChars(path, NULL);
    ... ...

    env->ReleaseStringUTFChars(path, pathStr);
    if (mimeType) {
        env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
    }
}

这边咱们来解答一个问题,咱们确实是知道MediaScanner的native函数是JNI层去实现的,可是系统是如何知道Java层的native_init函数对应的就是JNI层的android_media_MediaScanner_native_init函数呢?

注册JNI函数

不知道你有没有注意到native_init函数位于android.media这个包中,它的全路径名应该是android.media.MediaScanner.native_init,而JNI层函数的名字是android_media_MediaScanner_native_init。

是否是很神奇?名字对应着,惟一的区别就是“.”这个符号变成了“_”。由于在Native语言中,符号“.”有着特殊的意义,因此JNI层须要把Java函数名称(包括包名)中的“.”换成“_”。也就是经过这种方式,native_init找到了本身JNI层的本家兄弟android.media.MediaScanner.native_init。

咱们知道了Java层native函数对应JNI层的函数的原理,但有个问题,咱们知道是哪一个函数,可是想要把两个函数关联起来(也就是说去调用它)就涉及到JNI函数注册的问题(不注册,就没有关联,没有关联就没法调用)。

静态方法注册

这种方法很简单,很暴力!直接根据函数名来找对应的JNI函数,它须要Java的工具程序javah参与,总体流程以下:

      ✨ 先编写Java代码,而后编译生成.class文件。
      ✨ 使用Java的工具程序javah,采用命令“javah -o output packagename.classname”,这样它会生成一个叫output.h的JNI层头文件。其中packagename.classname是Java代码编译后的class文件,而在生成的output.h文件里,声明了对应的JNI层函数,只要实现里面的函数便可。

这个头文件的名字通常都会使用packagename_class.h的样式,例如MediaScanner对应的JNI层头文件就是android_media_MediaScanner.h。

/* DO NOT EDIT THIS FILE - it is machine generated*/
  #include <jni.h>        // 必须包含这个头文件,不然编译通不过
/* Header for class android_media_MediaScanner */

#ifndef _Included_android_media_MediaScanner
#define _Included_android_media_MediaScanner
#ifdef _cplusplus
extern "C" {
#endif
... ...     // 略去一部份内容

// processFile对应的JNI函数
JNIEXPORT void JNICALL Java_android_media_MediaScanner_processFile(JNIEnv *, jobject, jstring, jstring, jobject);

... ...     // 略去一部份内容

// native_init对应的JNI函数
JNIEXPORT void JNICALL Java_android_media_MediaScanner_native_linit(JNIEnv *, jclass);

#ifdef _cplusplus
}
#endif
#endif

从上面代码中能够发现,native_init和processFile的JNI层函数被声明成:

// Java 层函数名中若是由一个“_”, 转换成JNI后就变成了“l”
JNIEXPORT void JNICALL Java_android_media_MediaScanner_processFile
JNIEXPORT void JNICALL Java_android_media_MediaScanner_native_linit

Ok,那么静态方法中native函数是如何找到对应的JNI函数的呢?

当Java层调用native_init函数时,它会从对应的JNI库中寻找Java_android_media_MediaScanner_native_init函数,若是没有,就会报错。若是找到,则会为这个native_init和Java_android_media_MediaScanner_native_init创建一个关联关系,其实就是保存JNI层函数的函数指针。之后再调用native_init函数时,直接使用这个函数指针就能够了,固然这项工做是由虚拟机完成的。

从这里能够看出,静态方法就是根据函数名来创建Java函数与JNI函数之间的关联关系的,并且它要求JNI函数的名字必须遵循特定的格式。

这种方法有三个弊端,以下:

      ✨ 须要编译全部声明了native函数的Java类,每一个所生成的class文件都得用javah生成一个头文件;
      ✨ javah生成的JNI层函数名特别长,书写起来很不方便;
      ✨ 初次调用native函数时须要根据函数名称搜索对应的JNI函数来创建关联关系,这样会影响运行效率。

因此咱们是否有办法克服以上三点弊端?咱们知道静态方法是去动态库里找一遍,而后创建关联关系,之后再根据这个函数指针去调用对应的JNI函数,那么若是咱们直接让native函数直接知道JNI层对应函数的函数指针,是不就Ok了?

这就是下面咱们要介绍的第二种方法:动态注册法!

动态方法注册

咱们知道Java native函数和JNI函数是一一对应的,这个就像咱们key-value同样,那么若是有一个结构来保存这种关联关系,那么经过这个结构直接能够找到彼此的关联,是否是就效率就高多了?

答案是确定的,动态注册就是这么干的!在JNI技术中,用来记录这种一一对应关系的,是一个叫 JNINativeMethod 的结构,其定义以下:

typedef struct {
    char *name;                      // Java中native函数的名字,不用携带包的路径,例如:native_init
    char *signature;                 // Java中函数的签名信息,用字符串表示,是参数类型和返回值类型的集合
    void *fnPtr;                     // JNI层对应函数的函数指针,注意它是 void* 类型
}JNINativeMethod;

下面咱们看看如何使用这个结构体,看下MediaScanner JNI层是如何作的。

// 定义一个JNINativeMethod数组,其成员就是MediaScanner中全部native函数的一一对应关系。
static const JNINativeMethod gMethods[] = {
    ... ...

    {
        "processFile",                                    // Java中native函数的函数名
        "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",  // processFile的签名信息
        (void *)android_media_MediaScanner_processFile    // JNI层对应的函数指针
    },
    ... ...

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

是否是很一目了然?定义好了,不能直接用啊,固然须要注册一下。

// This function only registers the native methods, and is called from
// JNI_OnLoad in android_media_MediaPlayer.cpp
// 注册JNINativeMethod数组
int register_android_media_MediaScanner(JNIEnv *env)
{
    // 调用AndroidRuntime的registerNativeMethods函数,第二个参数代表是Java中的哪一个类
    // 咱们在讲解Zygote原理时,聊过建立Java虚拟机,注册JNI函数的内容
    return AndroidRuntime::registerNativeMethods(env,
                kClassMediaScanner, gMethods, NELEM(gMethods));
}

AndroidRunTime类提供了一个registerNativeMethods函数来完成注册的工做,下面来看下registerNativeMethods的实现:

/*
 * Register native methods using JNI.
 */
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
    const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    // 调用jniRegisterNativeMethods函数完成注册
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

其实,jniRegisterNativeMethods是Android平台中为了方便JNI使用而提供的一个帮助函数,其代码以下:

int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    ... ...
    // 其实是调用JNIEnv的RegisterNatives函数完成注册的
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        return -1;
    }
}

我知道你看到这边已经头疼了,调用来调用去,看上去很麻烦,是否是?其实动态注册的工做,只用两个函数就能完成,以下:

(1)jclass clazz = (*env)->FindClass(env, className);
         env指向一个JNIEnv结构体,它很是重要,后面咱们会讨论。classname为对应的Java类名,因为JNINativeMethod中使用的函数名并不是全路径名,因此要指明是哪一个类。
(2)(*env)->RegisterNatives(env, clazz, gMethods, numMethods);
         调用JNIEnv的RegisterNatives函数,注册关联关系。

那么,你如今知道了若是动态注册了,可是有个问题,这些动态注册的函数在何时和什么地方被调用?

当Java层经过System.loadLibrary加载完JNI动态库后,紧接着就会去查找该库中一个叫JNI_OnLoad的函数。若是有,就调用它,而动态注册的工做就是在这里完成的。

JNI_OnLoad

动态库是libmedia_jni.so,那么JNI_OnLoad函数在哪里实现的?若是你看的比较自信的话,我相信以前代码中有段注释你应该注意到了。

// 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)              // 这个代码很熟悉吧?
{
    return AndroidRuntime::registerNativeMethods(env,
                kClassMediaScanner, gMethods, NELEM(gMethods));
}

因为多媒体系统不少地方都使用了JNI,因此JNI_OnLoad被放到了android_media_MediaPlayer.cpp中,咱们看下代码:

jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
    // 该函数的第一个参数类型为JavaVM,这但是虚拟机在JNI层的表明哦,每一个Java进程只有一个这样的JavaVM
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        goto bail;
    }
    ... ...

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

数据类型转换

在Java中调用native函数传递的参数是Java数据类型,那么这些参数类型传递到JNI层会变成什么类型?

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 jdoublt 有符号 64位

引用数据类型的转换

Java引用类型 Native类型 Java引用类型 Native类型
All objects jobject char[] jcharArray
java.lang.Class 实例 jclass short[] jshortArray
java.lang.String 实例 jstring int[] jintArray
Object[] jobjectArray long[] jlongArray
boolean[] jbooleanArray float jfloatArray
byte[] jbyteArray double[] jdoubleArray
java.lang.Throwable 实例 jthrowable

咱们举例说明,看下processFile函数:

private native void processFile                   (                           String  path,  String  mimeType,  MediaScannerClient client);
static void android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path,  jstring mimeType,  jobject            client)

咱们发现:

      ✨ Java的String类型在JNI层对应为jstring类型;
      ✨ Java的MediaScannerClient类型在JNI层对应为jobject。

不知道你有没有注意到一个问题,Java中的processFile中只有三个参数,为何到了JNI层对应的函数却有五个参数?

static void android_media_MediaScanner_processFile(
        JNIEnv *env, jobject thiz, jstring path,
        jstring mimeType, jobject client)

接下来咱们开始重点讨论JNIEnv!!!

JNIEnv

JNIEnv的概念
      是一个与线程相关的表明JNI环境的结构体,该结构体表明了Java在本线程的执行环境。

JNIEnv的做用
      ✨ 调用 Java 函数 : JNIEnv 表明 Java 执行环境, 可以使用 JNIEnv 调用 Java 中的代码
      ✨ 操做 Java 对象 : Java 对象传入 JNI 层就是 Jobject 对象, 需要使用 JNIEnv 来操做这个 Java 对象

咱们来看一个有趣的现象

前面,咱们已经知道 JNIEnv 是一个与线程相关的变量,若是此时线程 A 有一个 JNIEnv 变量, 线程 B 也有一个JNIEnv变量,因为线程相关,因此 A 线程不能使用 B 线程的 JNIEnv 结构体变量。
此时,一个java对象经过JNI调用动态库中的一个send()函数向服务器发送消息,不等服务器消息到来就当即返回,同时把JNI接口的指针JNIEnv *env(虚拟机环境指针),和jobject obj保存在动态库中的变量里。一段时间后,动态库中的消息接收线程接收到服务器发来的消息,并试图经过保存过的env和obj来调用先前的java对象的方法(至关于JAVA回调方法)来处理此消息,此时程序忽然退出(崩溃)

为何?

缘由:前台JAVA线程发送消息,后台线程处理消息,归属于两个不一样的线程,不能使用相同的JNIEnv变量。

怎么解决?

还记得咱们前面介绍的JNI_OnLoad函数吗?它的第一个参数是JavaVM,它是虚拟机在JNI层的表明!!!

// 全进程只有一个JavaVM对象,因此能够在任何地方使用
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)

那么也就是说,不论进程有多少线程(不论有多少JNIEnv),JavaVM倒是独此一份!因此,咱们能够利用一个机制:利用全局的 JavaVM 指针获得当前线程的 JNIEnv 指针。

JavaVM和JNIEnv

      ✨ 调用JavaVM的AttachCurrentThread函数,就可获得这个线程的JNIEnv结构体,这样就能够在后台线程中回调Java函数了。
      ✨ 另外,在后台线程退出前,须要调用JavaVM的DetachCurrentThread函数来释放对应的资源。

经过JNIEnv操做jobject

前面介绍数据类型的时候,咱们知道Java的引用类型除了少数几个外(Class、String和Throwable),最终在JNI层都会用jobject来表示对象的数据类型,那么该如何操做这个jobject呢?

咱们先回顾下Java对象是由什么组成的?固然是它的成员变量和成员函数了!那么同理,操做jobject的本质就应当是操做这些对象的成员变量和成员函数!那么jobject的成员变量和成员函数又是什么?

取出jfieldID和jmethodID

在java中,咱们知道成员变量和成员函数都是由类定义的,他们是类的属性,那么在JNI规则中,也是这么来定义的,用jfieldID定义Java类的成员变量,用jmethodID定义Java类的成员函数。

可经过JNIEnv的下面两个函数获得:

jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)

其中,jclass表明Java类,name表示成员函数或成员变量的名字,sig为这个函数和变量的签名信息(后面会说到)。

咱们来看看在MediaScanner中如何使用它们,直接看代码:android_media_MediaScanner.cpp::MyMediaScannerClient构造函数

class MyMediaScannerClient : public MediaScannerClient
{
public:
    MyMediaScannerClient(JNIEnv *env, jobject client)... ...
    {
        // 先找到android.media.MediaScannerClient类在JNI层中对应的jclass实例
        jclass mediaScannerClientInterface =
                env->FindClass(kClassMediaScannerClient);

        if (mediaScannerClientInterface == NULL) {
            ALOGE("Class %s not found", kClassMediaScannerClient);
        } else {
            // 取出MediaScannerClient类中函数scanFile的jMethodID
            mScanFileMethodID = env->GetMethodID(
                                    mediaScannerClientInterface,
                                    "scanFile",
                                    "(Ljava/lang/String;JJZZ)V");

            // 取出MediaScannerClient类中函数handleStringTag的jMethodID
            mHandleStringTagMethodID = env->GetMethodID(
                                    mediaScannerClientInterface,
                                    "handleStringTag",
                                    "(Ljava/lang/String;Ljava/lang/String;)V");

            ... ...
        }
    }
    ... ...
    jobject mClient;
    jmethodID mScanFileMethodID;
    jmethodID mHandleStringTagMethodID;
    ... ...
}

从上面的代码中,将scanFile和handleStringTag函数的jMethodID保存在MyMediaScannerClient的成员变量中。为何这里要把它们保存起来呢?这个问题涉及到一个关于程序运行效率的知识点:

若是每次操做jobject前都要去查询jmethodID或jfieldID,那么将会影响程序运行的效率,因此咱们在初始化的时候能够取出这些ID并保存起来以供后续使用。

使用jfieldID和jmethodID

咱们来看看android_media_MediaScanner.cpp::MyMediaScannerClient的scanFile函数

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();
            return NO_MEMORY;
        }

        /*
         * 调用JNIEnv的CallVoidMethod函数
         * 注意CallVoidMethod的参数:
         *(1)第一个参数是表明MediaScannerClient的jobject对象
         *(2)第二个参数是函数scanFile的jmethodID,后面是Java中的scanFile的参数
         */
        mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
                fileSize, isDirectory, noMedia);

        mEnv->DeleteLocalRef(pathStr);
        return checkAndClearExceptionFromCallback(mEnv, "scanFile");
    }

经过JNIEnv输出CallVoidMethod,再把jobject、jMethodID和对应的参数传进去,JNI层就可以调用Java对象的函数了!

实际上JNIEnv输出了一系列相似CallVoidMethod的函数,形式以下:

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

其中type对应java函数的返回值类型,例如CallIntMethod、CallVoidMethod等。若是想调用Java中的static函数,则用JNIEnv输出的CallStatic<Type>Method系列函数。

因此,咱们能够看出,虽然jobject是透明的,但有了JNIEnv的帮助,仍是能轻松操做jobject背后的实际对象的。

jstring

这一节咱们单独聊聊String。Java中的String也是引用类型,不过因为它的使用频率很高,因此在JNI规范中单首创建了一个jstring类型来表示Java中的String类型。
虽然jstring是一种独立的数据累心,可是它并无提供成员函数以便操做。而C++中的string类是由本身的成员函数的。那么该如何操做jstring呢?仍是得依靠JNIEnv提供帮助。

先看几个有关jstring的函数:

      ✨ 调用JNIEnv的NewString(const jchar* unicodeChars, jsize len),能够从Native的字符串获得一个jstring对象。
      ✨ 调用JNIEnv的NewStringUTF(const char* bytes)将根据Native的一个UTF-8字符串获得一个jstring对象。
      ✨ 上面两个函数将本地字符串转换成了Java的String对象,JNIEnv还提供了GetStringChars函数和GetStringUTFChars函数,它们能够将Java String对象转换成本地字符串。其中GetStringChars获得一个Unicode字符串,而GetStringUTFChars获得一个UTF-8字符串。
      ✨ 另外,若是在代码中调用了上面几个函数,在作完相关工做后,就都须要调用ReleaseStringChars函数或ReleaseStringUTFChars函数来对应地释放资源,否定会致使JVM内存泄漏。

咱们看段代码加深印象:

static void
android_media_MediaScanner_processFile(
        JNIEnv *env, jobject thiz, jstring path,
        jstring mimeType, jobject client)
{
    // Lock already hold by processDirectory
    MediaScanner *mp = getNativeScanner_l(env, thiz);
    ... ...

    const char *mimeTypeStr =
        (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);
    if (mimeType && mimeTypeStr == NULL) {  // Out of memory
        // ReleaseStringUTFChars can be called with an exception pending.
        env->ReleaseStringUTFChars(path, pathStr);
        return;
    }
    ... ...
}

JNI类型签名

咱们看下动态注册中的一段代码:

static const JNINativeMethod gMethods[] = {
    ... ...

    {
        "processFile",
        // processFile的签名信息,这么长的字符串,是什么意思?
        "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
        (void *)android_media_MediaScanner_processFile
    },
    ... ...
};

上面这段代码咱们以前早就见过了,不过"(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V"是什么意思呢?

咱们前面提到过,这个是Java中对应函数的签名信息,由参数类型和返回值类型共同组成,有人可能有疑问,这东西是干吗的?

咱们都知道,Java支持函数重载,也就是说,能够定义同名但不一样参数的函数。但仅仅根据函数名是无法找到具体函数的。为了解决这个问题,JNI技术中就将参数类型和返回值类型的组合做为一个函数的签名信息,有了签名信息和函数名,就能很顺利地找到Java中的函数了。

JNI规范定义的函数签名信息看起来很别扭,不过习惯就行了。它的格式是:

(参数 1 类型标识参数 2 类型标识 ... 参数 n 类型标识) 返回值类型标识

咱们仍然拿processFile的例子来看下:

{
        "processFile",
        // Java中的函数定义为 private native void processFile(String path, String mimeType, MediaScannerClient client);
        // 对应的JNI函数签名以下:
        "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
        // void类型对应的标示是V
        // 当参数的类型是引用类型时,其格式是“L包名”,其中包名中的“.”换成“/”,Ljava/lang/String表示是一个Java String类型
        (void *)android_media_MediaScanner_processFile
    },

【注意】:引用类型(除基本类型的数组外)的标识最后都有一个“;”。

函数签名不只看起来麻烦,写起来更麻烦,稍微写错一个标点都会致使注册失败,因此在具体编码时,能够定义字符串宏(这边就很少作解释了,能够自行查询了解便可)。

虽然函数签名信息很容易写错,可是Java提供了一个叫javap的工具可以帮助咱们生成函数或变量的签名信息,它的用法以下:

javap -s -p xxx

其中 xxx 为编译后的class文件,s表示输出内部数据类型的签名信息,p表示打印全部函数和成员的签名信息,默认只会打印public成员和函数的签名信息。

垃圾回收及异常处理

这部分我打算单独放在一篇博文中探讨,结果具体错误进行分析。

参考Blog

                  Java Native Interface (JNI)

相关文章
相关标签/搜索