Android NDK开发(五):JNI基础

1 JNI简介

        概念:JNI(Java Native Interface)java本地化接口,狭义上是SUN定义的一套标准接口,广义上是标准接口、结构体、符号常量等的集合。

        作用:实现 java代码 和 本地代码(以C/C++为主)的交互,包括数据类型的相互转换、互调彼此的方法。

        实现形式:JNI的具体实现是由本地语言编写的数据类型、常量、函数、类、结构体等,是JDK中的一揽子本地文件,其中比较有代表性的jni.h及jni.cpp在OpenJDK的\openjdk\hotspot\src\share\vm\prims路径下,java程序启动时,JNI的代码被加载到java虚拟机中。

        使用位置:在本地代码中使用,通过引用jni.h即可使用JNI定义的数据类型、方法、结构体等;利用JNI中定义的JNIEnv结构体,可通过函数指针调用JNI的函数。
(注意:JNI是java的,NDK是Android的)

        效率:假设java调用java方法时间是1,java通过JNI调用本地方法时间约为3,本地通过JNI调用Java方法的时间大约是10。

        层次结构:为便于理解,用三层来进一步说明JNI的作用,其中本地层指C/C++的源码或库,java本身无法调用本地层代码,需要在本地层外面用C/C++语言并按照JNI标准,封装一层JNI,虽然JNI使用C/C++编写,但JNI函数不同于本地层函数,具体不同在于方法参数(JNI函数必须含有JNIEnv*和jobject/jclass两个类型的形参)、数据类型、命名上,后面会详细说明,这些不同使得java层可以和JNI层相互调用,而由于JNI是用C/C++实现的,故JNI层可以与本地层相互调用,这就间接实现了java与本地层的相互调用。其中java层调用JNI层,需要先建立映射再函数查找,JNI层调用java层直接通过反射机制。

Android NDK开发(五):JNI基础_第1张图片

2 JNI定义的数据类型、引用的种类及类型描述符

(1)JNI定义的与java数据类型对应的数据类型

        JNI定义了java的数据类型与C/C++数据类型的对应关系,基本类型定义如下:

java基本数据类型 JNI定义的在C/C++中对应的基本类型 JNI起的别名(typedef)
short short(有符号16位) jshort
int int(有符号32位) jint
long long long(有符号64位) jlong
float float(有符号32位) jfloat
double double(有符号64位) jdouble
char unsigned short(无符号16位) jchar
boolean unsigned char(无符号8位) jboolean
byte signed char(有符号8位) jbyte

        注意:
        (1)java层的 true 和 false 转化为本地层后,会用1和0表示;而本地层的 非零值 和 0 转化到本地层后,会用java层的true和fasle表示。
        (2)JNI层的 jboolean 实际上是本地层的unsigned char,故可对jboolean 类型的变量赋予整形常量(不超过255)
        (3)本地层C++也有true和false关键字,不同于java的true和false,前者实际上代表数值1和0,可直接赋值给C++的其他基本类型变量,如int类型变量;而对于java的true和false,不是数值,只能赋值给boolean类型变量。

        由上表可知,JNI用C/C++的部分基本类型来对应java的基本类型,那么JNI把java的引用类型对应成C/C++的啥类型呢?请看下表:

java的引用类型 JNI定义的在C++中对应的指针类型 JNI起的别名(typedef)

Object类及其子类

class _jobject {};

_jobject*

jobject、jweak(JNI多定义的弱全局引用)
Class类

class _jclass : public _jobject {};

_jclass*

jclass
String类

class _jstring : public _jobject {};

_jstring*

jstring
class _jarray : public _jobject {}; jarray(JNI数组类型的父类)
Object[ ]

class _jobjectArray : public _jarray {};

_jobjectArray*

jobjectArray
boolean[ ]

class _jbooleanArray : public _jarray {};

_jbooleanArray*

jbooleanArray
byte[ ]

class _jbyteArray : public _jarray {};

_jbyteArray*

jbyteArray
chart[ ]

class _jcharArray : public _jarray {};

_jcharArray*

jcharArray
short[ ]

class _jshortArray : public _jarray {};

_jshortArray*

jshortArray
int[ ]

class _jintArray : public _jarray {};

_jintArray*

jintArray
long[ ]

class _jlongArray : public _jarray {};

_jlongArray*

jlongArray
float[ ]

class _jfloatArray : public _jarray {};

_jfloatArray*

jfloatArray
double[ ]

class _jdoubleArray : public _jarray {};

_jdoubleArray*

jdoubleArray
Throwable[ ]

class _jthrowable : public _jobject {};

_jthrowable*

jthrowable

        由上表可知,JNI用C++的指针类型来对应java的引用类型,JNI定义了C++的类型来对应Java中比较常用的类,并用C++相应类的指针类型,对应java常用类的引用类型;值得注意的是,JNI将java中的数组引用类型也对应成C++中的指针类型。

(2)JNI引用的种类

        JNI定义的引用类型其本质是C++中的指针类型,这里我们称之为JNI中的引用类型,那么由JNI引用类型定义的变量的我们称之为JNI引用变量,JNI引用变量的值我们称之为JNI引用,JNI引用的种类如下:

JNI引用种类 生命周期 创建 释放 特点
局部引用(LocalRef) 仅在当前线程当前方法中有效 在本地方法中,若无特殊处理,创建的引用都是局部引用 本地方法结束后,JavaVM自动释放;也可通过JNI方法DeleteLocalRef手动释放

所引用的对象不会被GC;

全局引用(GlobalRef) 多线程、多方法中有效 需要通过JNI方法NewGlobalRef并基于局部引用手动创建 需要通过JNI方法DeleteGlobalRef手动释放 所引用的对象不会被GC;
弱全局引用(GlobalWeakRef) 多线程、多方法中有效 需要通过JNI方法NewGlobalWeakRef并基于局部引用手动创建 需要通过JNI方法DeleteGlobalWeakRef手动释放 所引用的对象不保证不被GC;

(注意:对于LocalRef,每个JavaVM有一个LocalRefTable(局部引用表),最大可存储512个局部引用,如果局部引用超过512将报错,导致程序崩溃,如果本地方法在结束前会创建大量局部引用,一定要手动释放!!)
(注意:局部引用、全局引用和局部引用变量、全局引用变量得定义方式是不同的,局部引用变量、全局引用变量是看变量是定义在函数内还是函数外)
(注意:一般不使用GlobalWeakRef!)
(注意:不要试图将局部引用赋值给全局引用变量,即不要缓存局部引用,若需缓存,请将局部引用转为全局引用并赋值给全局引用变量;也不要将全局引用赋值给局部引用变量,这没有意义。)
(注意:一般不使用GlobalWeakRef!)

(3)JNI定义的其他数据类型

        JNI除了定义了与java数据类型对应的数据类型之外,还定义了一些自己的数据类型,都定义在jni.h头文件中,具体如下:

1)jfieldID及jmethodID      

        定义:
                struct _jfieldID;                       /* opaque structure */
                typedef struct _jfieldID* jfieldID;     /* field IDs */
                struct _jmethodID;                      /* opaque structure */
                typedef struct _jmethodID* jmethodID;   /* method IDs */

        说明:_jfieldID、_jmethodID是结构体类型, jfieldID、jmethodID是指向相应结构体的指针变量,通常只会用到jfieldID、jmethodID,jni.h中隐藏了_jfieldID、_jmethodID的实现细节。

        作用:jfieldID用于本地调用Java类或对象的属性时,jmethodID用于本地调用Java类或对象的方法时。

2)JNINativeInterface、_JNIEnv、JNIEnv

        定义:
​​​​​                
#if defined(__cplusplus)
                typedef _JNIEnv JNIEnv;
                typedef _JavaVM JavaVM;
                #else
                typedef const struct JNINativeInterface* JNIEnv;
                typedef const struct JNIInvokeInterface* JavaVM;
                #endif

                struct JNINativeInterface{
                    jint  (*GetVersion)(JNIEnv *);
                    ........
                }

                struct _JNIEnv {
                    const struct JNINativeInterface* functions;
                    jint GetVersion() { return functions->GetVersion(this); 
                    .......
                }

        说明:JNINativeInterface 结构体中定义了JNI标准的全部方法,_JNIEnv 结构体采用修饰模式,内部持有JNINativeInterface结构体实例的指针,包装了JNINativeInterface结构体的方法。在C++中JNIEnv是_JNIEnv 结构体类型的别名,在C中JNIEnv是 JNINativeInterface* 指针类型的别名,一般我们直接使用JNIEnv,后面我们统一用JNIEnv描述。

        作用:JNI的大部分方法都需通过JNIEnv实例调用。
(注意:JNIEnv的实例是与线程相关的,每个线程都有一个,jni可能有多个JNIEnv,不同线程的JNIEnv实例彼此独立,线程间不能共享JNIEnv实例,所以不要尝试将JNIEnv实例缓存为全局的)
(注意:在C++与C中使用JNIEnv指针变量调用JNI方法时,使用方式稍有差别,后面会详细说)

3)JavaVM、JNIInvokeInterface

        定义:
​​​​​                
#if defined(__cplusplus)
                typedef _JNIEnv JNIEnv;
                typedef _JavaVM JavaVM;
                #else
                typedef const struct JNINativeInterface* JNIEnv;
                typedef const struct JNIInvokeInterface* JavaVM;
                #endif

                struct JNIInvokeInterface {
                    void*       reserved0;
                    void*       reserved1;
                    void*       reserved2;
                    jint        (*DestroyJavaVM)(JavaVM*);
                    jint        (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
                    jint        (*DetachCurrentThread)(JavaVM*);
                    jint        (*GetEnv)(JavaVM*, void**, jint);
                    jint        (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
                };

                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 结构体中定义了本地代码操作JavaVM及获取JNIEnv的方法,_JavaVM 结构体采用修饰模式,内部持有JNIInvokeInterface 结构体实例的指针,包装了JNIInvokeInterface 结构体的方法。在C++中JavaVM是_JavaVM 结构体类型的别名,在C中JavaVM是 JNIInvokeInterface* 指针类型的别名,一般我们直接使用JavaVM,后面我们统一用JavaVM描述。

        作用:用于获取JavaVM实例,通过JavaVM实例可获取跟本地线程相关的JNIEnv实例,从而让在本地开启的线程在没有java native方法传递来的JNIEnv实例的情况下 能获取JNIEnv实例调用java方法。
(注意:JavaVM是java虚拟机在JNI层的代表,在一个java进程中只有一个JavaVM实例,因此该进程的所有线程都可以使用这个JavaVM实例,故可考虑将JavaVM缓存为全局的)。
(注意:JavaVM结构体的方法指针指向java invocation API,Invocation API用于把JVM嵌入到本地程序中)

4JNINativeMethod

        定义:
                
typedef struct {
                    const char* name;
                    const char* signature;
                    void*       fnPtr;
                } JNINativeMethod;

        说明:变量name是Java中函数的名字;变量signature是Java方法通过描述符表示的方法签名
;变量fnPtr是函数指针,指向本地函数,使用时前面都要接加(void *),其name与fnPtr是对应的,一个是java层方法名,一个是native方法名。

        作用:用于JNI动态注册(后面会说),每个JNINativeMethod实例都表示一个本地方法与java方法的映射关系。

(4)JNI定义的描述符

        JNI定义描述符是为了让本地代码通过JNI调用java时,准确找到Java的类或方法,JNI描述符如下:

java元素 JNI描述符
int I
long J
byte B
short S
char C
float F
double D
boolean Z
void V
引用类型 L+全类名;
类型[] [类型
方法 (形参类型)返回值类型

例子:

String[] [Ljava/lang/String;
int[][] [[I
long f (int n, String s, int[] arr); (ILjava/lang/String;[I)J

(注意:引用类型是L+全类名;,并将全类名中的“.” 换成“/”,在最后加上;)

(5)JNI定义的宏

1)JNIEXPORT

        定义:
                
#define JNIEXPORT  __attribute__ ((visibility ("default")))

        作用:用于表明这个函数是一个可导出函数。每一个C/C++库都有一个导出函数列表,只有在这个列表里面的函数才可以被外部直接调用,类似Java的public函数和private函数的区别。定义JNI函数时可以不加,默认可以导出。
(注意:也可在JNI函数前直接加__attribute__ ((visibility ("default"))),若将“default”改成“hidden”,则外界无法找到该函数)

2)JNICALL

        定义:
                
#define JNICALL

        作用:只用于说明说明这个函数是一个JNI函数,用来和普通的C/C++函数进行区别,定义JNI函数时可以不加。

3)JNI_FALSE 及 JNI_TRUE

        定义:
                #define JNI_FALSE   0
                #define JNI_TRUE    1

        作用:JNI定义的字符常量,用来在本地代码中表示真假。

3 JNI定义的接口简介

       JNI定义的接口全在jni.h中,根据接口的实现方式不同可分为JNI自己实现的、间接调用java invocation API的、.间接调用so的,java invocation API及so为JNI暴露一些有用的方法。以下为方法简介,后续会结合使用详细说明:

接口 位置 实现 作用
JNI定义的接口 JNINativeInterface结构体中的接口 JNI自己 本地代码与java代码交互
JNIInvokeInterface结构体中的接口及JNI_CreateJavaVM、JNI_GetCreatedJavaVMs、JNI_GetDefaultJavaVMInitArgs 调用java invocation API相关的方法 用于将JVM嵌入到本地程序中
JNI_OnLoad
JNI_OnUnload
调用.so库相关方法 java加载、卸载so库时,会在JNI层回调,可用于JNI动态注册、持久化JavaVM等

 好了分享就到这里,如有问题请告知,希望大家点个赞支持一下!!!

你可能感兴趣的:(android,ndk,android,java)