JNI开发之JNI原理

JNI开发之JNI原理_第1张图片

  在上一篇文章中对JNI简单介绍了,在这篇文章中将对JNI原理进行介绍。本篇文章将以JNI执行环境、JNI数据类型、JNI注册方式、JNI引用、JNI变量共享以及JNI调用方式来介绍JNI原理。

 一、执行环境(Runtime)

  在计算机中,每种编程语言都有一个执行环境(Runtime),执行环境用来解释执行语言的语句。在JNI开发中有两个比较重要与执行环境Runtime相关的变量: JavaVMJNIEnv

  • JavaVM
  Java语言的执行环境是Java虚拟机(JVM),JVM其实是主机环境中的一个进程,每个JVM虚拟机进程在本地环境中都有一个JavaVM结构体,该结构体在创建Java虚拟机的时候被返回的,在JNI中创建JVM函数为JNI_CreateJavaVM,在jni.h头文件中有定义。
jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*);
  在jni.h文件中也定义了JavaVM的数据结构,可以看到JavaVM结构中封装了一些函数指针,这些函数指针主要是对JVM操作的接口,定义如下:
#if defined(__cplusplus)
typedef _JavaVM JavaVM; //C++的JavaVM定义
#else
typedef const struct JNIInvokeInterface* JavaVM; //C的JavaVM定义
#endif

/*
 * JNI invocation interface.
 */
struct JNIInvokeInterface {
    void*       reserved0;//保留
    void*       reserved1;//保留
    void*       reserved2;//保留

    jint        (*DestroyJavaVM)(JavaVM*); // 销毁Java虚拟机并回收资源
    jint        (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);//链接到当前Java线程
    jint        (*DetachCurrentThread)(JavaVM*); //从当前Java线程中分离
    jint        (*GetEnv)(JavaVM*, void**, jint);// 获得当前线程的Java运行环境
    jint        (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*); // 将当前线程作为守护线程
};

/*
 * C++ version.
 */
struct _JavaVM {
    const struct JNIInvokeInterface* functions;

#if defined(__cplusplus)
    jint DestroyJavaVM()
    { return functions->DestroyJavaVM(this); }
    jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThread(this, p_env, thr_args); }
    jint DetachCurrentThread()
    { return functions->DetachCurrentThread(this); }
    jint GetEnv(void** env, jint version)
    { return functions->GetEnv(this, env, version); }
    jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};
  从代码可以看到,JNIInvokeInterface结构封装了几个和JVM相关的函数。另外在C和C++中,JavaVM的定义是不相同的,在C中的JavaVM是JNIInvokeInterface类型指针,而在C++语言中,JavaVM是在JNIInvokeInterface指针上进行了一次封装,函数调用时少了一个参数。
   JavaVM是JVM在JNI层的代表,在JNI层中有且仅有一个JavaVM。JavaVM是进程相关的,一个进程只有一个JavaVM。

  • JNIEnv
  JNIEnv是当前线程的执行环境,一个JVM对应一个JavaVM结构,而在一个JVM中可能创建多个Java线程,每个线程都有一个JNIEnv结构,这些JNIEnv结构保存在线程的本地存储中(Thread Local Storage)。JNIEnv是线程相关的,即在每个线程中都有一个JNIEnv指针,每个JNIEnv都是线程专有的,其他线程不能使用本线程中的JNIEnv。
   JNIEnv不能跨线程,只在当前线程中有效。JNIEnv不能在线程之间进行传递,在同一个线程中,多次调用JNI层方法,传入的JNIEnv是相同的。但是一个本地方法可以被不同的Java线程调用, 因此本地方法可以接受不同的JNIEnv。
  JNIEnv有两个作用:一个是调用Java函数,JNIEnv代表当前Java线程的运行环境,通过JNIEnv可以调用Java中的代码;另一个是操作Java对象,Java对象传入JNI层就是jobject对象,需要使用JNIEnv来操作这个Java对象。
  JNIEnv的数据结构是在jni.h文件中定义的,可以看到JNIEnv数据结构也是一个函数表,在本地代码中通过JNIEnv的函数表来操作Java数据或调用Java方法。也就是说,只要在本地代码中拿到了JNIEnv结构,就可以在本地代码中调用Java代码。
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;//C++定义
#else
typedef const struct JNINativeInterface* JNIEnv;//C定义
#endif
/*
 * Table of interface function pointers.
 */
//C JNIEnv定义
struct JNINativeInterface {
    void*       reserved0;
    void*       reserved1;
    void*       reserved2;
    void*       reserved3;

    jclass      (*FindClass)(JNIEnv*, const char*);
    jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
    jfieldID    (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
    jmethodID   (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);
    jfieldID    (*GetStaticFieldID)(JNIEnv*, jclass, const char*,const char*);

   .......
    jint        (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*, jint);    //注册本地方法
    jint        (*UnregisterNatives)(JNIEnv*, jclass); //反注册本地方法
    jint        (*GetJavaVM)(JNIEnv*, JavaVM**); //获取对应的JavaVM对象
    ......
}

/*
 * C++ object wrapper. 
 *
 * This is usually overlaid on a C struct whose first element is a
 * JNINativeInterface*.  We rely somewhat on compiler behavior.
 */
//C++的JNIEnv定义
struct _JNIEnv {
    const struct JNINativeInterface* functions;

#if defined(__cplusplus)
  jclass FindClass(const char* name)
    { return functions->FindClass(this, name); }
    .......
}

  从代码可知,JNIEnv和JavaVM类似,也是定义了一些函数指针,通过这些函数可以操作Java对象。JNIEnv在C和C++中定义的方式也不相同,C++对JNINativeInterface指针进行了一次封装,调用时更加方便。
  总的来说, JNI其实就是定义了Java语言和本地语言之间的一种沟通方式,这种沟通方式依赖于JavaVM和JNIEnv结构中定义的函数表,这些函数将Java中的方法调用转换为本地语言的函数调用。
  JNI是JVM实现的一部分,JavaVM是JVM在JNI层的代表,每个Java线程都有一个JNIEnv,代表当前线程的执行环境,他们之间的关系如下图所示:
   JNI开发之JNI原理_第2张图片



二、JNI数据类型


  当Java与Native语言相互调用时,肯定会涉及到数据的传递。这两者属于不同的编程语言,在数据类型上有很多差异的。例如,在C语言中,int类型的长度取决于平台,char类型为1个字节长度,而在Java语言中,int类型固定为4个字节,而char类型为2个字节。为了使Java语言数据类型与Native语言数据类型匹配,需要借助JNI的数据类型来保证它们两者之间的数据类型和数据空间大小匹配。JNI中定义的一些数据类型有基本数据类型和引用数据类型。

    2.1 JNI基本类型

Java类型
Native类型
JNI类型
描述
boolean
unsigned char
jboolean
无符号8比特
byte
signed char
jbyte
有符号8比特
char
unsigned short
jchar
无符号16比特
short
short
jshort
有符号16比特
int
int
jint
有符号32比特
long
long long
jlong
有符号64比特
float
float
jfloat
32比特
double
double
jdouble
64比特
void
void
void
N/A
  Java类型和JNI类型名称具有一致性,JNI类型的名称在只是在Java类型的基础上添加了一个j,例如,Java的int类型对应的JNI类型为jint,Java的long类型,对应的JNI类型为jlong。JNI的基本类型在jni文件中定义了:

typedef unsigned char   jboolean;       /* unsigned 8 bits */
typedef signed char     jbyte;          /* signed 8 bits */
typedef unsigned short  jchar;          /* unsigned 16 bits */
typedef short           jshort;         /* signed 16 bits */
typedef int             jint;           /* signed 32 bits */
typedef long long       jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */
typedef jint            jsize;

  需要注意的是jchar代表的是Java的char类型,对应于C/C++中的却是unsigned short类型,因为Java中的char类型是两个字节,jchar相当于C/C++的宽字符。如果需要在本地方法中定义一个jchar类型的数据,规范的写法应该是jchar = L'C'。

  实际上,所有带j的JNI类型,都是JNI对应的Java类型,并且JNI的类型接口与本地代码在类型的空间大小上是完全匹配的,而在语言层次上却不一定相同。在本地方法中与JNI接口调用时,要在内部进行转换,必须小心处理。
   

2.2 JNI引用类型

    
  在本地代码中为了访问Java运行环境中的引用类型,在JNI中也定义了一套对应的引用类型,他们的对应关系如下:
                           JNI开发之JNI原理_第3张图片
  所有的引用类型对应于jobject,java.lang.Class类型对应于jclass,数组类型对应于jarray,java.lang.Throwable类型对应于jthrowable,Object数组对应于jobjectArray。
  JNI引用类型都是以j开头的,与Java中所有类的父类都是Object一样,所有的JNI引用类型都是jobject的子类。JNI的引用类型在jni.h文件中有定义:
#ifdef __cplusplus
/*
 * Reference types, in C++
 */
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};

//在C++中定义的引用类型
typedef _jobject*       jobject;
typedef _jclass*        jclass;
typedef _jstring*       jstring;
typedef _jarray*        jarray;
typedef _jobjectArray*  jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray*    jbyteArray;
typedef _jcharArray*    jcharArray;
typedef _jshortArray*   jshortArray;
typedef _jintArray*     jintArray;
typedef _jlongArray*    jlongArray;
typedef _jfloatArray*   jfloatArray;
typedef _jdoubleArray*  jdoubleArray;
typedef _jthrowable*    jthrowable;
typedef _jobject*       jweak;
#else /* not __cplusplus */

//在C语言中定义的JNI引用类型
/*
 * Reference types, in C.
 */
typedef void*           jobject;
typedef jobject         jclass;
typedef jobject         jstring;
typedef jobject         jarray;
typedef jarray          jobjectArray;
typedef jarray          jbooleanArray;
typedef jarray          jbyteArray;
typedef jarray          jcharArray;
typedef jarray          jshortArray;
typedef jarray          jintArray;
typedef jarray          jlongArray;
typedef jarray          jfloatArray;
typedef jarray          jdoubleArray;
typedef jobject         jthrowable;
typedef jobject         jweak;
#endif /* not __cplusplus */

2.3 JNI类型签名


为什么需要JNI类型签名?
  Java语言是面向对象的语言,支持方法的重载。即允许多个方法调用拥有相同的名字,通过方法的参数和返回值来确定调用的是哪一个函数。为了让JNI能调用Java对象中的方法,仅仅通过方法名是不够的,还需要指定方法的签名,即:参数列表和返回值类型。
  Java类型对应的JNI类型签名如下表所示:
Java类型
JNI类型签名
boolean
Z
btye
B
char
C
short
S
int
I
long
J
float
F
double
D
Class类
L
void
V
数组[]
[
boolean[] [Z
byte[] [B
char[]
[C
short[]
[S
int[]
[I
long[]
[J
float[]
[F
double[]
[D

 基本类型 
 以特定的单个字母表示,例如int用I表示。

 Java类型(L +类名 + ; )
 Java类型以L开头,以"/"分割包名,在类名的后面加上分号“;”分隔符。例如String的签名为:Ljava/lang/String;

 Java数组 ([ + 数据元素签名)
 Java中数组是引用类型,数组是“[”开头,后面跟上数组元素的签名。例如int[]的签名为:[I,Object[]的签名为:[Ljava/lang/Object;。

  JNI对于方法签名有特定的格式要求,参数列表签名在前,返回值签名在后,如下所示:
       (参数类型签名列表)返回值类型签名
 需要注意的是:
  • 在方法签名中没有体现方法名;
  • 括号内表示参数列表,参数列表紧密相挨,中间没有逗号和空格;
  • 返回值出现在括号后面;
  • 如果函数没有返回值,也要加上V类型。
  例如:
 void setName(String name);对应的JNI方法签名是(Ljava/lang/String;)V
 char fun(int n,String s,int[] value);对应的JNI方法签名是(ILjava/lang/String;[I)C
 可以通过javap -s  类名   生成该类中函数方法的类型签名
 在jni.h文件中,定义了JNI本地方法的数据结构,里面就有JNI的方法签名,signature就是JNI方法的签名。
typedef struct {
    const char* name;//方法名字
    const char* signature;//方法签名
    void*       fnPtr;//本地方法名字
} JNINativeMethod;

三、JNI方法注册方式


  JNI方法注册的方式有两种:一种是在系统启动的时候注册,另外一种是通过System.loadLibrary来加载so库文件注册。

3.1系统启动时注册

  在Android系统中,所有的应用程序进程以及系统服务进程SystemServer都是由Zygote进程孕育而来(fork)而来。我们知道,Android系统是基于Linux内核的,而在Linux内核中,所有的进程都是由init进程直接或间接fork出来的。Zygote进程也不例外,它是在系统启动的过程中,由init进程创建的。在启动Zygote进程的过程中,通过调用AndroidRuntime.cpp 的startVm方法创建虚拟机VM,VM创建完成后,紧接着调用startReg注册JNI方法,最后调用com.android.internal.os.ZygoteInit类main函数来启动Zygote进程。
   在系统启动脚本system/core/rootdir/init.rc文件中,可以看到启动Zygote进程的脚本命令:
  //引用ro.zygote.rc文件
   import  / init . $ { ro . zygote }. rc
  在rootdir目录下init.zygote32.rc、 init.zygote32_64.rc、 init.zygote64.rc、 init.zygote64_32.rc四个文件,代表32位和64位平台的Zygote脚本,以32位的Zygote脚本为例,如下:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    writepid /dev/cpuset/foreground/tasks
  前面的关键字service告诉init进程创建一个名为“Zygote”的进程,这个Zygote进程需要执行的程序是/system/bin/app_process,后面是要传给app_process的参数。
   socket关键字表示这个Zygote进程需要一个名为“Zygote”的socket资源,这样系统启动后,我们就可以在/dev/socket目录下看到有一个名为Zygote的文件。这里定义的socket的类型是unix domain socket,它是用来作为本地进程间通信用的。
  最后一系列onrestart关键字表示这个Zygote进程重启时需要执行的命令。
  由前面知道,Zygote进程需要执行app_process程序,app_prcess程序是system/bin/app_process,它的源码位于frameworks/base/cmds/app_process/app_main.cpp文件中,入口函数是main。
int main(int argc, char* const argv[])
{
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));//创建AppRuntime
    .........
    bool zygote = false;
    bool startSystemServer = false;
    ...........

    ++i;  
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {
            zygote = true;//启动Zygote进程
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;//启动SystemServer进程
        } 
        ........
    }

    Vector args;
    if (!className.isEmpty()) {
       .........
    } else {
        if (startSystemServer) {
            args.add(String8("start-system-server"));//添加启动SystemServer的参数
        }
        ............
    }


    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);//启动Zygote进程
    } 
    ............
}
  
  这个函数的主要作用是创建一个AppRuntime变量,然后调用它的成员函数start来启动ZygoteInit。AppRuntime类继承自AndroidRuntime类,同样定义在app_main.cpp文件中,如下所示:  
class AppRuntime : public AndroidRuntime
{
public:
    AppRuntime(char* argBlockStart, const size_t argBlockLength)
        : AndroidRuntime(argBlockStart, argBlockLength)
        , mClass(NULL)
    { }
} 
     而AndroidRuntime类定义在AndroidRuntime.cpp文件中,代码路径在frameworks/base/core/jni/AndroidRuntime.cpp路径下。
AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength) :
        mExitWithoutCleanup(false),
        mArgBlockStart(argBlockStart),
        mArgBlockLength(argBlockLength)
{
    SkGraphics::Init();
    mOptions.setCapacity(20);
    assert(gCurRuntime == NULL);        // one per process
    gCurRuntime = this; //将Runtime保存到一个全局变量中
}

  在创建AppRuntime对象的时候,也会调用其父类AndroidRuntime的构造函数。在AndroidRuntime构造函数中会将this保存到一个全局静态变量变量 gCurRuntime中,可以通过getRuntime()函数来获取该静态对象。
static AndroidRuntime* gCurRuntime = NULL;
AndroidRuntime* AndroidRuntime::getRuntime()
{
    return gCurRuntime;
}
  在调用AppRuntime的start方法时,最终调用的是其父类AndroidRuntime的start方法,代码如下:
frameworks/base/core/jni/AndroidRuntime.cpp
/*
 * Start the Android runtime.  This involves starting the virtual machine
 * and calling the "static void main(String[] args)" method in the class
 * named by "className".
 *
 * Passes the main function two arguments, the class name and the specified
 * options string.
 */
void AndroidRuntime::start(const char* className, const Vector& options, bool zygote)
{
     .......
    static const String8 startSystemServer("start-system-server");

    ........
    // 1.启动虚拟机VM
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    if (startVm(&mJavaVM, &env, zygote) != 0) {
        return;
    }
    onVmCreated(env);

    //2.注册JNI函数
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }

   //准备启动Zygote进程的参数
    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;

    stringClass = env->FindClass("java/lang/String");
    assert(stringClass != NULL);
    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
    assert(strArray != NULL);
    classNameStr = env->NewStringUTF(className);
    assert(classNameStr != NULL);
    env->SetObjectArrayElement(strArray, 0, classNameStr);

    for (size_t i = 0; i < options.size(); ++i) {
        jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
        assert(optionsStr != NULL);
        env->SetObjectArrayElement(strArray, i + 1, optionsStr);
    }


    // 3.启动Zygote进程,调用ZygoteInit.main()方法。
    /*
     * Start VM.  This thread becomes the main thread of the VM, and will
     * not return until the VM exits.
     */
    char* slashClassName = toSlashClassName(className);
    //通过JNI调用java方法,具体是通过找到ZygoteInit类,以及ZygoteInit类的静态方法main,
    //最后通过CallStaticVoidMethod调用ZygoteInit类的main方法。
    // "com.android.internal.os.ZygoteInit"是通过app_main中main方法传递过来的参数。
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray);//通过JNI调用ZygoteInit的main方法
        }
    }
    free(slashClassName);

    // 虚拟机退出了才会执行到这里
    ALOGD("Shutting down VM\n");
    if (mJavaVM->DetachCurrentThread() != JNI_OK)
        ALOGW("Warning: unable to detach main thread\n");
    if (mJavaVM->DestroyJavaVM() != 0)
        ALOGW("Warning: VM did not shut down cleanly\n");
}
  在AndroidRuntime的start方法中,主要干了三件事情,一是启动VM;二是注册JNI函数,最后一件是启动ZygoteInit类的main函数。
   startVm函数是启动Dalvik虚拟机,该方法定义在frameworks/base/core/jni/AndroidRuntime.cpp中实现了,主要是定义一些启动Dalvik虚拟机参数以及初始化JavaVM和JNIEnv变量。
/*
 * Start the Dalvik Virtual Machine.
 *
 * Various arguments, most determined by system properties, are passed in.
 * The "mOptions" vector is updated.
 *
 * Returns 0 on success.
 */
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
{
    JavaVMInitArgs initArgs;
    //准备创建JVM的参数
    .........
    // 初始化VM,
     /*
     * Initialize the VM.
     *
     * The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.
     * If this call succeeds, the VM is ready, and we can start issuing
     * JNI calls.
     */
    if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
        ALOGE("JNI_CreateJavaVM failed\n");
        return -1;
    }

    return 0;
}
  当调用JNI_CreateJavaVM成功后,VM也将准备好了可以被使用,可以调用JNI方法。调用JNI_CreateJavaVM后,将初始化一个JavaVM和JNIEnv变量,每个进程都对应一个JavaVM,每个线程对应于一个JNIEnv,即JavaVM是进程相关的,而JNIEnv是线程相关的。
  startReg函数是将JNI方法注册到VM中,startReg函数也是在AndroidRuntime.cpp文件中实现,代码如下:
/*
 * Register android native functions with the VM.
 */
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
    ATRACE_NAME("RegisterAndroidNatives");
  // 设置创建线程的方法为javaCreateThreadEtc,该方法定义在AndroidRuntime.h中
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
    ......
    
    env->PushLocalFrame(200);
   //注册gRegJNI数组中的函数
    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
        env->PopLocalFrame(NULL);
        return -1;
    }
    env->PopLocalFrame(NULL);
    return 0;
}
  可以看到startReg函数最后调用的是 register_jni_procs函数将gRegJNI数组中的函数注册到VM中。gRegJNI是一个包含JNI函数的数组,定义如下:
/*
 * Register android native functions with the VM.
 */
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
    ATRACE_NAME("RegisterAndroidNatives");
  // 设置创建线程的方法为javaCreateThreadEtc,该方法定义在AndroidRuntime.h中
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
    ......
    
    env->PushLocalFrame(200);
   //注册gRegJNI数组中的函数
    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
        env->PopLocalFrame(NULL);
        return -1;
    }
    env->PopLocalFrame(NULL);
    return 0;
}
    可以看到startReg函数最后调用的是register_jni_procs函数将gRegJNI数组中的函数注册到VM中。gRegJNI是一个包含JNI函数的数组,定义如下:
//一个包含JNI函数的数组
static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_com_android_internal_os_RuntimeInit),
    REG_JNI(register_com_android_internal_os_ZygoteInit),
    REG_JNI(register_android_os_SystemClock),
    REG_JNI(register_android_util_EventLog),
    REG_JNI(register_android_util_Log),
    REG_JNI(register_android_util_MemoryIntArray),
    ....
}

#define REG_JNI(name)      { name }
struct RegJNIRec {
        int (*mProc)(JNIEnv*);
 };

 //循环调用gRegJNI数组中的JNI函数,每一个方法都对应于一个类的jni映射。
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
    for (size_t i = 0; i < count; i++) {
        if (array[i].mProc(env) < 0) {
            return -1;
        }
    }
    return 0;
}
  
  在register_jni_procs()函数中,循环调用 gRegJNI数组中定义的JNI函数。这样就完成了JNI函数的注册。举个例子,在RegJNIRec数组中有一个 register_com_android_internal_os_RuntimeInit函数指针,当调用register_jni_procs()方法时,会调用register_com_android_internal_os_RuntimeInit()方法,将会注册JNI本地方法。
int register_com_android_internal_os_RuntimeInit(JNIEnv* env)
{
    const JNINativeMethod methods[] = {
        { "nativeFinishInit", "()V",
            (void*) com_android_internal_os_RuntimeInit_nativeFinishInit },
        { "nativeSetExitWithoutCleanup", "(Z)V",
            (void*) com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup },
    };
    return jniRegisterNativeMethods(env, "com/android/internal/os/RuntimeInit",
        methods, NELEM(methods));
}
     jniRegisterNativeMethods方法的作用是将本地方法注册到JNI中,JNINativeMethod是定义本地方法的一个数据结构在jni.h文件中定义。
   typedef struct {
    const char* name;//Java方法名字
    const char* signature;//方法签名
    void*       fnPtr;//Java方法对应的本地函数指针
} JNINativeMethod; 
  至此,介绍完了JNI方法在系统启动过程中的注册流程。其流程如下图所示:
             JNI开发之JNI原理_第4张图片



3.2 通过loadLibrary方法注册

  除了在系统启动的时候注册JNI函数,还有一种JNI注册方式是通过loadLibrary实现的。loadLibrary方法是System.java中的静态方法,先来看实现:
  在java/lang/System.java类中

/*
     * 加载libname指定的本地库
     */
    @CallerSensitive
public static void loadLibrary(String libname) {
        Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
  在  java/lang/Runtime.java类中
 /*
    * 通过给定的ClassLoader搜索和加载指定的共享库文件
    */
    void loadLibrary(String libraryName, ClassLoader loader) {
        //loader不为空,进入该分支处理
        if (loader != null) {
            //查找库所在路径
            String filename = loader.findLibrary(libraryName);
            if (filename == null) {
                throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
                                               System.mapLibraryName(libraryName) + "\"");
            }
            //加载库文件
            String error = doLoad(filename, loader);
            if (error != null) {
                throw new UnsatisfiedLinkError(error);
            }
            return;
        }

        //loader为空,则进入该分支处理
        // 返回平台相关库文件名字,在Android中,如果共享库为MyLibrary,则返回的共享库名字为“libMyLibrary.so”。
        String filename = System.mapLibraryName(libraryName);
        List candidates = new ArrayList();
        String lastError = null;
         //在/system/lib/和/vendor/lib/下查找指定的filename文件
        for (String directory : mLibPaths) {
            String candidate = directory + filename;
            candidates.add(candidate);

            if (IoUtils.canOpenReadOnly(candidate)) {
                // 找了对应的库文件,加载库文件
                String error = doLoad(candidate, loader);
                if (error == null) {
                    return; // We successfully loaded the library. Job done.
                }
                lastError = error;
            }
        }

        if (lastError != null) {
            throw new UnsatisfiedLinkError(lastError);//没有找到满足条件的,报错
        }
        throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
    }

// mLibPaths是保存库文件路径,用来查找native库文件
private final String[] mLibPaths = initLibPaths();
   /* 
     *搜索JNI库文件的路径,通过"java.library.path"读取出来的属性值为/vendor/lib:/system/lib/。
     *其中/system/lib/路径存放的是系统应用使用的so库文件,/vendor/lib/路径存放的是第三方应用的so库文件。*/

 private static String[] initLibPaths() {
        String javaLibraryPath = System.getProperty("java.library.path");
        if (javaLibraryPath == null) {
            return EmptyArray.STRING;
        }
        String[] paths = javaLibraryPath.split(":");
        // Add a '/' to the end of each directory so we don't have to do it every time.
        for (int i = 0; i < paths.length; ++i) {
            if (!paths[i].endsWith("/")) {
                paths[i] += "/";
            }
        }
        return paths;
    }

 private String doLoad(String name, ClassLoader loader) {
        String ldLibraryPath = null;
        if (loader != null && loader instanceof BaseDexClassLoader) {
            ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();
        }
        synchronized (this) {
            //最后调用nativeLoad方法加载库文件
            return nativeLoad(name, loader, ldLibraryPath);
        }
    }
  可以看到,System.loadLibrary()方法会先去/system/lib/和/vendor/lib/目录下去查找指定名字的so库文件,然后调用nativeLoad方法加载库文件。nativeLoad()方法在java_lang_Runtime.cc文件中实现,该文件在art/runtime/native/java_lang_Runtime.cc路径下。这里再深入下去,就是虚拟机内部的实现了,这里暂不描述。可以说一下接来下大致的处理流程:
  • 调用dlopen()函数,打开一个so文件并创建一个handle;
  • 调用dlsym()函数,查看相应so文件的JNI_OnLoad()函数指针,并执行相应的函数。

  总之,System.loadLibrary()的作用就是调用相应库中的JNI_OnLoad()方法。接下来看JNI_OnLoad函数。

3.2.1JNI_OnLoad方法

  本地代码最终编译成动态库,在Java代码中通过System.loadLibrary()方法来加载本地代码库,当本地代码动态库被JVM加载时,JVM会自动调用本地代码中的JNI_OnLoad函数。
  JNI_OnLoad函数的在jni.h文件中定义:
 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);
   其中vm参数代表JVM实例,主要是包含一些与JVM相关的操作函数; reversed保留
   JNI_OnLoad函数的工作流程一般如下:
  1. 通过JavaVM获取JNIEnv,即通过getEnv函数,获取JNIEnv,JNIEnv代表Java线程执行环境;
  2. 通过RegisterNative函数注册本地方法;
  3. 返回JNI版本号;
  接下来看一个实例,在framework/media/jni/android_media_MediaPlayer.cpp文件中,实现了JNI_OnLoad()方法:
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
    JNIEnv* env = NULL;
    jint result = -1;
    // 1.通过JavaVM获取JNIEnv,指定JNI的版本为1.4。
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        ALOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);

    ...
    // 2.注册JNI方法
    if (register_android_media_MediaPlayer(env) < 0) {
        ALOGE("ERROR: MediaPlayer native registration failed\n");
        goto bail;
    }

    ...
    /* success -- return valid version number */
    // 3.返回JNI版本号
    result = JNI_VERSION_1_4;

bail:
    return result;
}

// 调用registerNativeMethods方法注册本地方法
static int register_android_media_MediaPlayer(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env,
                "android/media/MediaPlayer", gMethods, NELEM(gMethods));
}
static const JNINativeMethod gMethods[] = {
    ...
    {"_prepare",            "()V",                              (void *)android_media_MediaPlayer_prepare},
    {"_start",              "()V",                              (void *)android_media_MediaPlayer_start},
    {"_stop",               "()V",                              (void *)android_media_MediaPlayer_stop},
    {"seekTo",              "(I)V",                             (void *)android_media_MediaPlayer_seekTo},
    {"_pause",              "()V",                              (void *)android_media_MediaPlayer_pause},
    {"isPlaying",           "()Z",                              (void *)android_media_MediaPlayer_isPlaying},
    {"getCurrentPosition",  "()I",                              (void *)android_media_MediaPlayer_getCurrentPosition},
    {"getDuration",         "()I",                              (void *)android_media_MediaPlayer_getDuration},
    {"_release",            "()V",                              (void *)android_media_MediaPlayer_release},
    {"_reset",              "()V",                              (void *)android_media_MediaPlayer_reset},
    {"setParameter",        "(ILandroid/os/Parcel;)Z",          (void *)android_media_MediaPlayer_setParameter},
    {"native_setup",        "(Ljava/lang/Object;)V",            (void *)android_media_MediaPlayer_native_setup},
    ....
};
  在framework/core/jni/AndroidRuntime.cpp文件中实现了registerNativeMethods方法,并最终调用jniRegisterNativeMethods方法。
/*
 * 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);
}
  registerNativeMethods方法中的className代表的是需要注册方法的类, JNINativeMethod结构体保存的是映射关系,将Java方法映射到本地函数指针。gMethods是一个JNINativeMethods数组,因为一个Java类中可能定义多个本地方法,所以需要一个数组来保存这些本地方法的映射关系。
  在/libnativehelper/JNIHelp.cpp文件中实现了jniRegisterNativeMethods方法
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()方法完成注册
    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
       ...
    }
    return 0;
}
  RegisterNatives方法在jni.h文件中定义。
struct _JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */
    const struct JNINativeInterface* functions;
        ...
    jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods){
        return functions->RegisterNatives(this, clazz, methods, nMethods); 
    }
    ....
}
  functions是一个JNINativeInterface的指针,也将调用RegisterNatives()方法,再往下面就是虚拟机的内部实现了,在此不再详述。
struct JNINativeInterface {
    void*       reserved0;
    void*       reserved1;
    void*       reserved2;
    void*       reserved3;
    ....
    jint  (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,jint);
    ....
}
  当不需要这些映射关系时,或者需要更新映射关系时,则需要调用 UnregisterNatives函数,来删除这些映射关系。
jint        (*UnregisterNatives)(JNIEnv*, jclass);
  至此,介绍了通过System.loadLibrary()方法注册JNI函数的过程。整体的流程如下图所示:
JNI开发之JNI原理_第5张图片


3.3如何查找native方法


  当查看framework层的代码时,经常会遇到一些native方法,这些native方法是定义在哪些文件中呢,该如何查找对应的文件呢?接下来说明一些查找native方法的一些途径。
  首先获取native方法定义所在的包名和类名,然后得出本地方法名称。
  [包名] _ [类名] _ nativemethod ()。
  例如:MessageQueue.java中有一个nativeInit()方法,所以得出本地方法名称为 android_os_ MessageQueue_ nativeInit()。
  获取到本地方法名称后,如果是在系统启动时候注册的JNI函数,则可以先到framework/base/core/jni/目录下查找对应的.cpp和.h文件。cpp和h文件的命名方式一般为:
   [包名]_[类名].cpp
   [包名]_[类名] .h
  MessageQueue.java对应的native文件为os_android_MessageQueue.cpp文件
  如果是通过System.loadLibrary()注册的JNI函数,则先找到生成.so库文件的Android.mk文件,然后在Android.mk文件下的查找包含本地方法名称的.cpp和.h文件,一般的命名规则和前面一样:
  [包名]_[类名].cpp
   [包名]_[类名] .h
  如果通过上诉两种方法还未找到,则通过全局搜索包含本地方法名称的Native文件了。

四、JNI引用

  在JNI提供了三种Reference类型,Global Reference(全局引用)、Local Reference(本地引用)和Weak Global Reference(全局弱引用)。其中Global Reference如果不主动释放,则一直占用。
  Java代码调用本地方法时,涉及到参数的传递以及返回值的复制。对于int、char等基本类型,是以传值的方式进行,可以直接拷贝;而对于Java对象类型,是通过传递引用来实现的。JVM保证了所有的Java对象正确的传递给本地代码,并且维持这些引用。如果本地代码不释放这些引用,则Java对象不能被Java的垃圾回收器GC回收。
那么,有什么办法可以通知垃圾回收器GC回收这些Java对象?
  JNI将在本地方法引用的对象分为三大类:局部引用、全局引用和全局弱引用三大类。
  局部引用 :只在本地方法中有效,当本地方法返回时,该局部引用自动回收。
  全局引用 :只有显式通知垃圾回收器GC回收,才会被回收,否则会一直有效。

局部引用
全局引用
作用范围
本地方法内
全局范围
回收时机
本地方法返回时,自动回收
显式通知GC回收
适用范围
只在当前线程中有效
可以跨越多个线程
创建引用函数
jobject    (*NewLocalRef)(JNIEnv*, jobject)
jobject    (*NewGlobalRef)(JNIEnv*, jobject)
销毁引用函数 void    (*DeleteLocalRef)(JNIEnv*, jobject)
void    (*DeleteGlobalRef)(JNIEnv*, jobject)

  创建引用和删除引用的函数在jni.h中定义了:
jobject     (*NewLocalRef)(JNIEnv*, jobject);
void        (*DeleteLocalRef)(JNIEnv*, jobject);

jobject     (*NewGlobalRef)(JNIEnv*, jobject);
void        (*DeleteGlobalRef)(JNIEnv*, jobject);

jweak       (*NewWeakGlobalRef)(JNIEnv*, jobject);
void        (*DeleteWeakGlobalRef)(JNIEnv*, jweak);

4.1局部引用

  局部引用的创建和销毁函数如下所示:
jobject     (*NewLocalRef)(JNIEnv*, jobject);
void        (*DeleteGlobalRef)(JNIEnv*, jobject);
  默认的,Java代码传递给本地方法的参数引用是局部引用,所有本地方法的返回值引用也是局部引用。
  当本地方法返回时,这些局部引用都会自动被回收
jstring Jstring2CStr(JNIEnv*   env,   jstring   jstr)
{
     ....
     jclass   clsstring   =   (*env)->FindClass(env,"java/lang/String");//局部变量
     jstring native_desc = env->NewStringUTF(" I am Native");//局部变量
     ...
}
  虽然局部引用在本地方法返回时,会自动被回收,但还是存在一些情况需要手动释放局部引用的情况:
  1. 在本地方法中引用了一个很大的Java对象,在使用完该Java对象后,还需要继续执行一些耗时操作,如果不主动释放该局部引用对象的话,则需要等到本地方法执行完成才能释放该局部引用对象。对于内存比较紧张的情况,可能会由于局部引用对象没有及时回收而导致内存不足的问题。
  2. 在本地方法中创建了大量的局部对象,可能会导致JNI局部引用表溢出,此时需要手动释放这些不再使用的局部引用对象。例如,在本地代码中创建一个很大的对象数组。
  3. 不返回的本地函数。例如:在一个本地函数中循环处理消息,如果不释放循环中使用的局部引用,则会无限地累积,进而导致内存泄漏。此时在循环体中需要手动释放局部引用。
  局部引用只在创建它们的线程中有效,不能传递到其他的线程。

 4.2全局引用

  当一个本地方法需要被多个线程调用,并且希望本地方法中的引用在多个线程间可以共享使用时,那么可以使用全局引用。全局引用可以跨越多个线程,全局引用需要手动释放,显式通知垃圾回收器GC回收它,否则会一直存在。
  全局引用的使用示例如下:
JNIMediaPlayerListener::JNIMediaPlayerListener(JNIEnv* env, jobject thiz, jobject weak_thiz)
{   ....
    jclass clazz = env->GetObjectClass(thiz);//局部引用
    ....
    mClass = (jclass)env->NewGlobalRef(clazz);//创建全局引用并指向局部引用
    ...
    mObject  = env->NewGlobalRef(weak_thiz);//创建全局引用
}

JNIMediaPlayerListener::~JNIMediaPlayerListener()
{
    // remove global references
    JNIEnv *env = AndroidRuntime::getJNIEnv();
    env->DeleteGlobalRef(mObject);//移除全局引用
    env->DeleteGlobalRef(mClass);//移除全局引用
}
  当本地方法不再需要使用全局引用时,应该通过 DeleteGlobalRef方法来释放全局引用。否则的话,JVM不会回收被全局引用的对象。

  4.3 JNI变量共享

  当需要在本地代码中共享变量时,首先想到的方法是利用全局引用共享。除此之外,还有一种方法是在Java代码中,保存需要在本地代码中共享的对象。如下图所示,在Native方法1中,生成了一个本地对象m,该对象是局部引用,不能被其他的本地方法访问。通过将本地对象m保存到Java代码中的相应对象n,当Native方法2需要利用Native方法1中的本地对象m时,可以通过访问Java代码中对象n来获取本地对象m的值。Java代码的作用可以理解为共享区域,一个本地方法往其中写入值,另一个本地方法从中读取值出来。
            JNI开发之JNI原理_第6张图片
 
static void android_media_MediaPlayer_native_init(JNIEnv *env,jobject obj)
{   ....
    jclass clazz;
    clazz = env->FindClass("android/media/MediaPlayer");//获取MediaPlayer类
    jfieldID name = env->GetFieldID(clazz,"name","Ljava/lang/String;");//获取MediaPlayer类的域name的ID
    jstring newName = env->NewStringUTF("media player");//创建一个新的字符串
    env->setObjectField(obj,name,newName);//将MediaPlayer类中的name属性更新
    ....
}
static void android_media_MediaPlayer_start(JNIEnv *env, jobject obj)
{
    ....
    jclass clazz;
    clazz = env->FindClass("android/media/MediaPlayer");//获取MediaPlayer类
    jfieldID name = env->GetFieldID(clazz,"name","Ljava/lang/String;");//获取MediaPlayer类的域name的ID
    jstring mediaName = (jstring)env->getObjectField(obj,name);//读取在android_media_MediaPlayer_native_init方法中更新的name对象值
    ...
}

  在上面的例子中,首先在 android_media_MediaPlayer_native_init方法中更新name属性到Java代码对应的域中,然后在android_media_MediaPlayer_start()方法中,通过读取Java域中对应的字段的值,这样就实现了将android_media_MediaPlayer_native_init()方法中的对象,传递到android_media_MediaPlayer_start()方法中。

五、调用方式

  在JNI开发中,有两种调用方式:一种是Java代码调用本地方法;另一种是本地代码访问Java的成员。

 5.1Java访问本地方法

  在某些情况下,一些功能需要本地代码来实现,例如对运算速度有要求的代码需要本地代码来执行。这时Java代码需要能访问本地方法。在访问本地方法前,首先要保证本地代码被加载到Java执行环境中,并与Java代码链接到一起了。这样当Java代码调用本地方法时,能保证找到对应的本地方法实现。Java访问本地方法的大致步骤如下:
  1. 在java代码中,声明native方法;
  2. 编译生成本地代码库.so文件;
  3. 在java代码中,加载本地代码库.so文件;
  4. 在java代码中,调用本地native方法;
  例如:
public class JavaToNative{
    static{
        System.loadLibrary(“java_to_native”);  // 通过System.loadLibrary()来加载本地代码库
        }
        private static native String hello();    // 声明native方法
        public static void main(String args[]){
            System.out.println(JavaToNative.hello()); // 调用native方法
        }
}
  需要注意的是加载库文件的代码System.loadLibrary()是在静态代码块中,因为静态代码块在加载Java类的时候被调用,而且只会被调用一次。

 5.2本地方法访问Java成员

  从Android的系统架构来看,JVM和Native系统库位于内核之上,构成了Android Runtime。更多的系统功能则是通过在其之上的Framework和Java API提供的。因此,如果希望在Native库中调用某些系统功能,就需要通过JNI来访问Framework以及Java API提供的功能。
  Java中的类封装了属性和方法,要想访问Java中的属性和方法,首先要获取Java类或Java对象,然后再访问属性和方法。在Java中,类成员与对象成员是不同的。类成员是指静态属性和静态方法,而对象成员是指非静态的属性和非静态的方法,他们属于某一个具体的对象。因此,在本地代码中对类成员与对象成员的访问是不相同的。
 本地方法访问Java成员的大致有以下几个步骤:
  1. 获取Java运行环境中的类对象class;
  2. 获取Java类或Java对象的属性ID和方法ID;
  3. 通过属性ID和方法ID访问Java类或Java对象的属性和方法;
 获取类对象class
  在JNI中通过FindClass函数来获取Java运行环境中的类对象class,FindClass函数的定义如下:
jclass FindClass(const char* name);
  name为类的全名,包含了类的完整路径,即包名+类名,以"/"代替"."分割符。例如:
jclass  newStr = env->FindClass("java/lang/String");
  还有一种方法是通过Object对象来获取其对应的类class对象,所调用的方法为  GetObjectClass,定义如下:
  jclass GetObjectClass(jobject obj);
  obj代表Java传递过来的对象,通过该方法可以获取该对象的类类型,其功能如同Object.getClass()方法。

 获取Java属性ID和方法ID
  为了在C/C++中表示Java的属性和方法,JNI在jni.h文件中,定义了jfieldID和jmethodID类型,分别用来代表Java的属性和方法。当需要访问Java的属性和方法,需要首先获取属性ID和方法ID,然后通过属性ID和方法ID来访问对应的属性和方法。属性ID和方法ID定义如下:
struct _jfieldID;                       /* opaque structure */
typedef struct _jfieldID* jfieldID;     /* field IDs */
struct _jmethodID;                      /* opaque structure */
typedef struct _jmethodID* jmethodID;   /* method IDs */
  在jni.h文件中,定义了获取属性ID和方法ID的方法:
//获取Java对象的属性ID
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig);
//获取Java类的静态属性ID
jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)

//获取Java对象的方法ID
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
//获取Java类的静态方法ID
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig);
  其中参数 class为类对象,是通过前面的FindClass方法或者getObjectClass方法返回的class对象;
  参数name为属性或方法的名字;
  参数sig为JNI类型签名,在前面有介绍;
  举个例子来说:
jclass clazz = env->FindClass("android/media/MediaPlayer");//获取MediaPlayer类class
jfieldID name = env->GetFieldID(clazz,"name","Ljava/lang/String;");//获取MediaPlayer对象的属性name的ID
jmethodID getHost = env->GetMethodID(clazz, "getHost", "(V)Ljava/lang/String;");//获取MediaPlayer对象方法getHost()的ID

jfieldID port =  env->GetStaticFieldID(clazz,"port","I");//获取MediaPlayer类的静态属性port的ID
jmethodID getPort = env->GetStaticMethodID(clazz,"getPort","(V)I");//获取MediaPlayer类的静态方法getPort()的ID

 JNI操作Java属性和方法
  •  操作Java属性
  当通过GetFieldID方法获取到Java对象的属性ID和通过GetStaticMethodID方法获取到Java类的静态属性ID后,就可以使用JNIEnv中提供的方法来访问Java对象的属性和Java类中的静态属性。在jni.h文件中定义了访问Java对象属性和Java类的静态属性的方法:
jobject GetObjectField(jobject obj, jfieldID fieldID);
jboolean GetBooleanField(jobject obj, jfieldID fieldID);
jint GetIntField(jobject obj, jfieldID fieldID);
...

void SetObjectField(jobject obj, jfieldID fieldID, jobject value);
void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value);
void SetIntField(jobject obj, jfieldID fieldID, jint value);
...
 Java对象获取属性的方法和设置属性的方法为:
  j<类型> Get<类型>Field(jobject obj,jfieldID fieldID);
  void Set<类型>Field(jobject obj,jfieldID fieldID,j<类型> value);
  其中类型表示Java中的基本类型,obj表示需要获取的Java对象,fieldID表示需要获取Java对象的属性ID,value表示要设置的属性值;
jobject GetStaticObjectField(jclass clazz, jfieldID fieldID);
jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID);
jint GetStaticIntField(jclass clazz, jfieldID fieldID);
...
void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value);
void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value);
void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value);
....

  Java类静态属性的获取方法和设置方法为:
  j<类型> GetStatic<类型>Field(jclass clazz,jfieldID fieldID); 
  void SetStatic<类型>Field(jclass clazz,jfieldID fieldID,j<类型> value);
  其中类型为Java中的基本类型,clazz表示需要Java类对象,filedID表示类的静态属性ID,value表示要设置的静态属性值。
  举个例子来说:
 static void android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
    ...
jclass clazz = env->GetObjectClass(thiz);//获取MediaPlayer类class
//获取和设置Java对象的属性
jfieldID name = env->GetFieldID(clazz,"name","Ljava/lang/String;");//获取MediaPlayer对象的属性name的ID
jstring newStr = env->NewStringUTF("local media player");
env->SetObjectField(thiz,name,newStr);//设置MediaPlayer对象的属性name的值

//获取和设置类的静态属性
jfieldID port =  env->GetStaticFieldID(clazz,"port","I");//获取MediaPlayer类的静态属性port的ID
jint portNum = env->GetStaticIntField(claszz,port);//获取MediaPlayer类静态属性port的值
env->SetStaticIntField(clazz,port,portNum + 100);//设置MediaPlayer类静态属性port的值
    ...
}

  • 操作Java方法
   当通过GetMethodID方法获取到Java对象的方法ID和通过GetStaticMethodID方法获取到Java类的静态方法ID后,就可以使用JNIEnv中提供的方法来调用Java对象的方法和Java类中的静态方法。在jni.h文件中定义了访问Java对象方法和Java类的静态方法的方法:

  调用Java对象的成员方法:
  Call<类型>Method(jobject obj,jmethodID methodID,...);
  Call<类型>MethodV(jobject obj, jmethodID methodID, va_list args);
  Call<类型>MethodA(jobject obj, jmethodID methodID, jvalue* args);

  调用Java类的静态方法:
  CallStatic<类型>Method(jobject obj,jmethodID methodID,...);
  CallStatic<类型>MethodV(jobject obj, jmethodID methodID, va_list args);
  CallStatic<类型>MethodA(jobject obj, jmethodID methodID, jvalue* args);
  其中类型为Java的基本类型,例如int、char等,代表方法的返回值类型; obj为成员方法所属的对象; methodID为通过GetMethodID方法获取到的方法ID; 最后一项参数表示方法的参数列表,...表示的是变长参数,以“V”结束的方法名表示以向量形式提供参数列表,以“A”结尾的方法名表示以jvalue数组形式提供参数列表,这两种方式调用比较少。
  例如调用返回值为Void类型的Java对象的成员方法
void CallVoidMethod(jobject obj, jmethodID methodID, ...);
void CallVoidMethodV(jobject obj, jmethodID methodID, va_list args);
void CallVoidMethodA(jobject obj, jmethodID methodID, jvalue* args);

  调用返回值为Void类型的Java类的静态方法
void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...);
void CallStaticVoidMethodV(jclass clazz, jmethodID methodID, va_list args);
void CallStaticVoidMethodA(jclass clazz, jmethodID methodID, jvalue* args);
  
  举个例子来说:
static void android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
    ...
jclass clazz = env->FindClass("android/media/MediaPlayer");//获取MediaPlayer类class
jmethodID getHost = env->GetMethodID(clazz, "getHost", "(V)Ljava/lang/String;");//获取MediaPlayer对象方法getHost()的ID
jmethodID getPort = env->GetStaticMethodID(clazz,"getPort","(V)I");//获取MediaPlayer类的静态方法getPort()的ID
env->CallObjectMethod(this,getHost);//调用MediaPlayer的成员方法getHost()
env->CallStaticIntMethod(clazz,getPort);//调用MediaPlayer类的静态方法getPort()
    ...
}


 在本地代码中创建Java对象
  • 在本地代码中创建Java对象
  在JNIEnv的函数表中有几个方法被用来创建一个Java对象:
jobject NewObject(jclass clazz, jmethodID methodID, ...);
jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args);
jobject NewObjectA(jclass clazz, jmethodID methodID, jvalue* args);
  和Java方法调用一样,创建对象也有三种形式。 其中clazz参数代表需要创建对象的class类; methodID表示创建对象的构造方法ID;
  最后一项参数表示方法的参数列表,...表示的是变长参数,以“V”结束的方法名表示以向量形式提供参数列表,以“A”结尾的方法名表示以jvalue数组形式提供参数列表。
  由于构造方法比较特别,与类名一样,并且没有返回值,所以通过GetMethodID来获取构造方法的ID时,第二个参数name固定为类名或者用""代替类名,第三个参数sig与构造函数有关,默认的构造函数是没有参数的。
  例如,调用MediaPlayer的默认构造函数,方法如下:
jclass clazz = env->FindClass("android/media/MediaPlayer");//获取MediaPlayer类class
jmethodID ctor = env->GetMethodID(clazz,"","(V)V");//获取默认构造方法ID
jobject mediaPlayer = env->NewObject(clazz,ctor);//调用构造函数创建MediaPlayer对象

  • 在本地代码中创建String对象
  在Java中,字符串String对象是Unicode(UTF-16)编码,每个字符不论是中文还是英文,一个字符都是占用两个字节。而在C/C++中一个字符是占用一个字节的,宽字符是占用两个字节的。
  如果在C/C++代码中需要创建一个Java端的String对象,则需要创建一个宽字符串或者是一个UTF-8编码的字符串来与Java端的String对象匹配。
  根据传入的宽字符串创建一个Java String对象的方法如下:
  jstring NewString(const jchar* unicodeChars, jsize len);
  
  根据传入的UTF-8字符串创建一个Java对象的方法如下:
  jstring NewStringUTF(const char* bytes);
  从Java属性中获取的String属性,在本地方法中对应的都是jstring类型。jstring不是C/C++中的字符串,所以需要通过JNIEnv中的函数来将Java的String类型转换为C/C++中的字符串类型。
  将一个jstring对象转换为(UTF-8)编码的字符串(char*);
const char* GetStringUTFChars(jstring string, jboolean* isCopy);
  
  将一个jstring对象转换为(UTF-16)编码的宽字符串(jchar*);
const jchar* GetStringChars(jstring string, jboolean* isCopy);
typedef unsigned short  jchar;          /* unsigned 16 bits */

  这两个函数中的参数,第一个参数传入的是一个指向Java String对象的引用;第二个参数传入的是一个jboolean类型的指针,其值可以为NULL、JNI_TRUE、JNI_FLASE。 如果jboolean为JNI_TRUE,则表示在本地开辟内存,然后把Java中的String拷贝到这个内存中,然后返回指向这个内存地址的指针。 如果jboolean为JNI_FALSE,则直接返回指向Java中String的指针。Java中String对象和本地方法C/C++字符串将共用同一个内存地址的指针,在本地方法中修改指针指向的内容,将修改Java中String对象的内容,这将破坏String在Java中始终是常量的原则。 如果是NULL,则表示不关心是否拷贝字符串。
  通过上述两个方法获取Java String对象的字符串,在使用完字符串后,要释放字符串所占用的内存,可以使用下列的两个函数来释放字符串占用的内存。

void ReleaseStringChars(jstring string, const jchar* chars);
void ReleaseStringUTFChars(jstring string, const char* utf);
  第一个参数string代表指向的Java中String对象; 第二个参数代表需要释放的本地字符串;
  例如:
const char *tmp = env->GetStringUTFChars(path, NULL);
env->ReleaseStringUTFChars(path, tmp);

  • 在本地代码中处理Java数组
  在本地代码中处理Java数组的一个大致流程如下:
  1. 通过GetFieldID方法获取Java数组变量的ID;
  2. 通过GetObjectField方法获取Java数组变量的值,保存到jobject中。
  3. 强制将jobject对象转换为j<类型>Array类型;
  4. 将j<类型>Array类型转换为C/C++中的数组。
  j<类型>Array类型是JNI定义的一个对象类型,并不是C/C++中的数组,例如int[]数组、double[]数组,因此要借助JNIEnv中定义了一系列方法来把一个j<类型>Array类型转换为C/C++数组。
jsize GetArrayLength(jarray array);//获取数组的长度
  
  //Object数组的操作
jobjectArray NewObjectArray(jsize length, jclass elementClass,jobject initialElement);//创建Object对象数组
  length:代表创建对象数组的长度;
  elementClas:代表对象数组的元素类型;
  initialElement:代表对象数组元素的初始值;

jobject GetObjectArrayElement(jobjectArray array, jsize index);//相当于array[index]
  array:代表要操作的数组
  index:代表要操作数组元素的下标

void SetObjectArrayElement(jobjectArray array, jsize index, jobject value);//相当于arry[index] = value
  array:代表要操作的数组
  参数:代表需要操作数组元素的下标
  参数:代表需要设置的数组元素的值

  注意在JNI中,没有提供直接将Java的对象数组Object[]直转换为C++中的Object[]数组的方法,只能通过GetObjectArrayElement和SetObjectArrayElement方法来对Java的Object数组进行操作。

 基本类型数组的操作
  j<类型>  New<类型>Array(jsize length);//创建基本类型的数组
  类型为基本类型,例如int、char等; 参数length表示需要创建数组的长度;

  j<类型>* Get<类型>ArrayElements(j<类型>Array,jboolean* isCopy);//获取基本类型的数组
  array:表示需要转换的Java的基本数组类型;
  isCopy:表示是否需要拷贝到本地。isCopy的取值有JNI_TRUE和JNI_FALSE和NULL。当isCopy为JNI_TRUE,则表示需要在本地拷贝一份,并返回一个指向本地拷贝的指针;当isCopy为JNI_FALSE时,则把指向Java数组的指针直接返回给本地代码,isCopy的取值为NULL则不关心拷贝情况。
当处理完本地代码中的数组之后,需要调用Release<类型>ArrayElements方法来释放数组所占用的资源。

  void Release<类型>ArrayElements(j<类型>Array array, j<类型>* elems,jint mode);//释放基本数组所占用的内存资源
  array:代表Java中基本数组,
  elems:代表需要释放的C/C++中的基本数组
  mode:mode的取值可以为0、JNI_COMMIT、JNI_ABORT。当mode为0时,表示对Java数组进行更新并释放C/C++数组。当mode为JNI_COMMIT时,表示对Java数组进行更新,但是不释放C/C++的数组。当mode为JNI_ABORT时,表示对Java数组不进行更新,释放C/C++数组。
上面的这些基本数组的操作,可以完成把Java基本类型的数组转换到C/C++中的数组。
  例如,类型为char时,创建、获取和释放char的数组方法如下:
jcharArray NewCharArray(jsize length);
jchar* GetCharArrayElements(jcharArray array, jboolean* isCopy);
void ReleaseCharArrayElements(jcharArray array, jchar* elems,jint mode);
  具体的例如如下:
static void android_media_MediaPlayer_release(JNIEnv *env, jobject thiz)
{
     jclass clazz = env->FindClass("android/media/MediaPlayer");//获取MediaPlayer类class
     jfieldID arrayID = env->GetFieldID(clazz,"arrays", "[I");
     jintArray array = (jintArray)(env->GetObjectField(thiz, arrayID));
     jint*  int_array = env->GetIntArrayElements(arr, NULL);
     jsize  len = env->GetArrayLength(array);
     env->ReleaseIntArrayElements(array, int_array, JNI_ABORT);
}

你可能感兴趣的:(NDK,JNI)