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层直接通过反射机制。

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

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

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

java基本数据类型JNI定义的在C/C++中对应的基本类型JNI起的别名(typedef)
shortshort(有符号16位)jshort
intint(有符号32位)jint
longlong long(有符号64位)jlong
floatfloat(有符号32位)jfloat
doubledouble(有符号64位)jdouble
charunsigned short(无符号16位)jchar
booleanunsigned char(无符号8位)jboolean
bytesigned 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描述符
intI
longJ
byteB
shortS
charC
floatF
doubleD
booleanZ
voidV
引用类型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等

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

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值