引言
上一篇讲解了一些关于JNI和NDK的必知必会的理论知识和机制,由于篇幅问题把关于JNI的重要的函数放到这篇,具体使用留到下一篇,此系列文章基链接:
- Android NDK——必知必会之配置Windows与Linux共享及 Linux NDK 交叉编译环境配置(一)
- Android NDK——必知必会之JNI和NDK基础全面详解(二)
- Android NDK——必知必会之JNI的C++操作函数详解和小结(三)
一、JNI中的函数概述
在JNI层我们基本上都是通过env指针来调用jni.h头文件里定义的函数,JNI的函数语法与Java 有些异同,建议对比着学习,效果会更好。
二、操作对象的相关函数
函数名称(env->函数名) | 功能说明 |
---|---|
jobject AllocObject( jclass clazz) | 不通过jclass对应类的构造方法并返回其引用或者null |
jobject NewObject(jclass clazz, jmethodID methodID, …) | 通过反射构造方法创建对象,通过不定参数传入对应的实参 |
jobject NewObjectA(jclass clazz, jmethodID methodID, jvalue *args) | 通过反射构造方法创建对象,通过数组接收传入对应的实参 |
jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args) | 通过反射构造方法创建对象,通过va_list传入对应的实参 |
jclass GetObjectClass( jobject obj); | 反射获取obj对应的jclass 字节码对象 |
jclass GetSuperclass(jclass clazz) | 获取父类或者说超类 。 如果 clazz 代表类class而非类 object,则该函数返回由 clazz 所指定的类的超类。 如果 clazz指定类 object 或代表某个接口,则该函数返回NULL |
jclass FindClass(const char* name) | 通过全限定类名(把**.** 换成 /)获取对应的jclass,该函数用于加载本地定义的类。它将搜索由CLASSPATH 环境变量为具有指定名称的类所指定的目录和 zip文件 ,和GetObjectClass 一样 |
jboolean IsSameObject(jobject ref1, jobject ref2) | 判断两个引用是否指向同一jobeect |
jboolean IsAssignableFrom(jclass clazz1, jclass clazz2) | clazz1对象所表示的类或接口与指定的 clazz2 所表示的类或接口是否相同,或是否是其超类或超接口,可用于判断确定 clazz1 的对象是否可安全地强制转换为clazz2 |
jboolean IsInstanceOf( jobject obj, jclass clazz) | 判断jobject是否是jclass类型的 |
1、非静态成员Field操作的相关函数
1.1、GetFieldID获取jfieldID
操作成员属性前需要首先来获取对应的jfieldID,但该方法不能用于获取数组的长度。
//参数clazz为传入的jobject,name为Field名成,sig为对应的类型签名
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
1.2、Get< type >Field获取Field的值
通过以下通用形式:**NativeType Get< type >Field(jobject obj, jfieldID fieldID)**来获取Field的值。
参数obj为jobject对象(非空),fieldID为GetFieldID返回的值
Get< type >Field Routine Name | Native Type |
---|---|
GetObjectField(jobject obj, jfieldID fieldID) | jobject |
GetBooleanField(jobject obj, jfieldID fieldID) | jboolean |
GetByteField(jobject obj, jfieldID fieldID) | jbyte |
GetCharField(jobject obj, jfieldID fieldID) | jchar |
GetShortField(jobject obj, jfieldID fieldID) | jshort |
GetIntField(jobject obj, jfieldID fieldID) | jint |
GetLongField(jobject obj, jfieldID fieldID) | jlong |
GetFloatField(jobject obj, jfieldID fieldID) | jfloat |
GetDoubleField(jobject obj, jfieldID fieldID) | jdouble |
1.3、设置Field的值
通过以下通用形式:**void Set< type >Field( jobject obj, jfieldID fieldID,NativeType value)**来设置Field
其中参obj为jobject对象(非空),fieldID为属性ID,value为Field的参数类型的值。
Set< type >Field Routine | Native Type |
---|---|
SetObjectField(jobject obj, jfieldID fieldID, jobject value) | jobject |
SetBooleanField(jobject obj, jfieldID fieldID, jboolean value) | jboolean |
SetByteField(jobject obj, jfieldID fieldID, jbyte value) | jbyte |
SetCharField(jobject obj, jfieldID fieldID, jchar value) | jchar |
SetShortField(jobject obj, jfieldID fieldID, jshort value) | jshort |
SetIntField(jobject obj, jfieldID fieldID, jint value) | jint |
SetLongField(jobject obj, jfieldID fieldID, jlong value) | jlong |
SetFloatField(jobject obj, jfieldID fieldID, jfloat value) | jfloat |
SetDoubleField(jobject obj, jfieldID fieldID, jdouble value) | jdouble |
2、非静态函数调用相关的函数
2.1、通过GetMethodID获取methodID
调用函数前需要首先获取methodID。
//其中参数clazz为传入的jobject,name为函数名,sig为对应的函数签名
jmethodID GetMethodID( jclass clazz, const char *name, const char *sig )
2.2、调用非静态方法
使用env来调用对应的Call< Native Type>Method(jobject obj, jmethodID methodID, …) 、Call< Native Type>MethodA(jobject obj, jmethodID methodID, jvalue args)、Call< Native Type>MethodV(jobject obj, jmethodID methodID, va_list args)和CallNonvirtual< Native Type>Method(jobject obj, jmethodID methodID, …) 、CallNonvirtual< Native Type>MethodA(jobject obj, jmethodID methodID, jvalue args)、**CallNonvirtual< Native Type>MethodV(jobject obj, jmethodID methodID, va_list args)**系函数。
1、obj传入的是对象实例,尤其是JNI层不要与jclass 搞混了
2、其中< Native Type>代表函数返回值的类型,如上表所示,比如CallVoidMethod() 代表调用的是无返回值类型的函数。
2、函数名后的是否有A、V是代表接收参数的形式,都没有则代表通过不定参数的形式传入实参,有A则代表通过数组形式传入实参,有V则代表通过va_list(指向变参列表的指针)形式传入实参。
C++让一个函数具有多态属性需要显式的声明virtual关键字,如果一个方法被定义在父类中且在子类中被覆盖,你也可以调用这个实例方法,JNI提供了一系列完成这些功能的函数:CallNonvirtual< Native Type>Method(jobject obj, jmethodID methodID, …) 、CallNonvirtual< Native Type>MethodA(jobject obj, jmethodID methodID, jvalue args)*、CallNonvirtual< Native Type>MethodV(jobject obj, jmethodID methodID, va_list args)。为了调用一个定义在父类中的实例方法(其中CallNonvirtualVoidMethod也可以被用来调用父类的构造函数。JNI中构造函数可以和实例方法一样被调用,调用方式也相似,只要传入“< init >”作为方法名,“V”作为函数签名),通常包含以下步骤:
- 使用GetMethodID从一个指向父类的引用当中获取方法ID
- 传入对应的对象、父类、方法ID和参数,并调用CallNonvirtual< Native Type>Method、CallNonvirtual< Native Type>MethodA、CallNonvirtual< Native Type>MethodV系中的一个
当然也可以指定调用子类的函数,至于调用哪个函数,是取决于CallNonvirtual< Native Type>Method、CallNonvirtual< Native Type>MethodA、CallNonvirtual< Native Type>MethodV的第二个参数决定的。
3、静态Field成员操作
3.1、GetStaticFieldID获取静态Field的jfieldID
//参数clazz为传入的jobject,name为Field名成,sig为对应的类型签名
jfieldID GetStaticFieldID( jclass clazz,const char *name, const char *sig)
3.2、GetStatic< type >Field获取Field的值
通过以下通用形式:**NativeType GetStaticField(JNIEnv *env, jclass clazz,jfieldID fieldID)**来获取Field的值。
参数obj为jobject对象(非空),fieldID为GetFieldID返回的值
3.3、SetStatic< type >Field设置Field的值
通过以下通用形式:**void SetStatic< type >Field( jobject obj, jfieldID fieldID,NativeType value)**来设置Field
其中参obj为jobject对象(非空),fieldID为属性ID,value为Field的参数类型的值。
4、静态函数操作
4.1、 GetStaticMethodID获取静态方法ID
//其中参数clazz为传入的jobject,name为函数名,sig为对应的函数签名
jmethodID GetStaticMethodID(jclass clazz,const char *name, const char *sig);
4.2、调用静态方法
使用env来调用对应的Call< Native Type>Method(jclass clazz, jmethodID methodID, …) 、Call< Native Type>MethodA(jclass clazz, jmethodID methodID, jvalue args)、Call< Native Type>MethodV(jclass clazz, jmethodID methodID, va_list args)和CallNonvirtual< Native Type>Method(jclass clazz, jmethodID methodID, …) 、CallNonvirtual< Native Type>MethodA(jclass clazz, jmethodID methodID, jvalue args)、**CallNonvirtual< Native Type>MethodV(jclass clazz, jmethodID methodID, va_list args)**系函数,与调用非静态函数对比除了字面上多了个Static之外,第一个参数也有所不同,在调用非静态函数时第一个参数传入的是jobject,而调用静态函数时传递的是jclass。
1、obj传入的是jclass字节码,尤其是JNI层不要与jobect对象实例搞混
5、字符串操作的相关函数
函数名称(env->函数名) | 功能说明 | 需要配套使用的函数 |
---|---|---|
jstring NewString(const jchar *unicodeChars, jsize len) | 从Unicode字符数组构造一个新的java.lang.String对象 | ReleaseStringChars(jstring string, const jchar* chars) |
jsize GetStringLength( jstring string) | 返回字符串的长度(Unicode 字符数) | |
const jchar * GetStringChars(jstring string, jboolean *isCopy) | 返回指向字符串的Unicode字符数组的指针( 在调用ReleaseStringchars函数之前,此指针有效) | ReleaseStringChars(jstring string, const jchar* chars) |
void ReleaseStringChars(jstring string, const jchar *chars) | 回收chars的内存空间 | |
void ReleaseStringUTFChars(jstring string, const char* utf) | 回收uft的内存空间 | |
jstring NewStringUTF(const char *bytes) | 从修改后的UTF-8编码中的字符数组构造一个新字符串对象 | void ReleaseStringUTFChars(jstring string, const char* utf) |
jsize GetStringUTFLength(jstring string) | 以字节为单位返回字符串的 UTF-8 长度 | |
const jbyte GetStringUTFChars(jstring* string, jboolean *isCopy); | 返回指向字节数组的指针,该字节数组表示修改后的UTF-8编码中的字符串。 在ReleaseStringUTFChars()释放之前有效,参见下文对isCopy的说明 | |
void GetStringRegion(jstring str, jsize start, jsize len, jchar *buf) | 从Java字符串中截取一段字符,其中str为字符串对象,start为起始位置,len为截取长度,buf为保存截取结果的缓冲区 | |
void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf) | 从UTF-8字符串中截取一段字符,其中str为字符串对象,start为起始位置,len为截取长度,buf为保存截取结果的缓冲区 | |
const jchar * GetStringCritical(jstring string, jboolean *isCopy) | 当我们需要获取字符数组时,使用上面的函数都有可能或得到原始字符串的拷贝,这会运行效率有些影响。如果我们想获得指向原始字符串指针就可以极大地优化运行效率。这两个函数Get/ReleaseStringCritical来操作原始字符串的直接指针。但是在这两个函数之间不能存在任何会让线程阻塞的操作。 | void ReleaseStringCritical(jstring string, const jchar* carray) |
6、数组操作的相关函数
6.1、创建数组
JNI为不同的类型定义了创建数组的方法,对于已知具体类型(非Object)的调用形如jArrayType NewArray(jsize length) 的函数会返回对应的数组
- PrimitiveType 为Java中对应的原始类型,ArrayType 为JNI中数组的类型
函数 | ArrayType 数组类型 | 数组元素类型 | 说明 |
---|---|---|---|
jbooleanArray NewBooleanArray(jsize length) | jbooleanArray | jboolean | 返回的对应的数组对象 |
jbyteArray NewByteArray(jsize length) | jbyteArray | jbyte | 同上 |
jcharArray NewCharArray(jsize length) | jcharArray | jchar | 同上 |
jshortArray NewShortArray(jsize length) | jshortArray | jshort | 同上 |
jintArray NewIntArray(jsize length) | jintArray | jint | 同上 |
jlongArray NewLongArray(jsize length) | jlongArray | jlong | 同上 |
jfloatArray NewFloatArray(jsize length) | jfloatArray | jfloat | 同上 |
jdoubleArray NewDoubleArray(jsize length) | jdoubleArray | jdouble | 同上 |
创建对象数组的话就是通过函数**jobjectArray NewObjectArray(jsize length, jclass elementClass, jobject initialElement)**构造新的数组,它将保存类 elementClass 中的对象且所有元素初始值均设为 initialElement。
6.2、访问数组和释放缓存
同样地JNI为不同的类型定义了对应访问数组的方法,对于已知具体类型(非Object)的调用形如NativeType* Get< PrimitiveType >ArrayElements(ArrayType array, jboolean *isCopy)的函数会返回指向原始类型数组的指针
- PrimitiveType 为Java中对应的原始类型,ArrayType 为JNI中数组的类型
- isCopy这个参数很重要,上文中的也都一样,它是一个指向Java布尔类型的指针。在函数返回之后应当检查这个参数的值,如果值为JNI_TRUE表示返回的字符是Java字符串的拷贝,则可以对其中的值进行任意修改。反之为JNI_FALSE,表示这个字符指针指向原始Java字符串的内存,这时候对字符数组的任何修改都将会原始字符串的内容。但如果无需考虑字符数组的来源或者操作不会对字符数组进行任何修改,可以传入NULL。
简单来说就是true则是把Java字符串的内容拷贝到新内存中来,false则是直接指向Java中原始的内存地址。
函数 | ArrayType 数组类型 | 数组元素类型 | 说明 | 配套使用 |
---|---|---|---|---|
jboolean* GetBooleanArrayElements(jbooleanArray array, jboolean* isCopy) | jbooleanArray | jboolean | 返回的是指向数组首元素的指针 | ReleaseBooleanArrayElements 释放 |
jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy) | jbyteArray | jbyte | 同上 | ReleaseByteArrayElements 释放 |
jchar* GetCharArrayElements(jcharArray array, jboolean* isCopy) | jcharArray | jchar | 同上 | ReleaseShortArrayElements 释放 |
jshort* GetShortArrayElements(jshortArray array, jboolean* isCopy) | jshortArray | jshort | 同上 | ReleaseBooleanArrayElements 释放 |
jint* GetIntArrayElements(jintArray array, jboolean* isCopy) | jintArray | jint | 同上 | ReleaseIntArrayElements 释放 |
jlong* GetLongArrayElements(jlongArray array, jboolean* isCopy) | jlongArray | jlong | 同上 | ReleaseLongArrayElements 释放 |
jfloat* GetFloatArrayElements(jfloatArray array, jboolean* isCopy) | jfloatArray | jfloat | 同上 | ReleaseFloatArrayElements 释放 |
jdouble* GetDoubleArrayElements(jdoubleArray array, jboolean* isCopy) | jdoubleArray | jdouble | 同上 | ReleaseDoubleArrayElements 释放 |
jobject GetObjectArrayElement(jobjectArray array, jsize index) | jobject | jobject | 返回对象数组的元素 |
而且由于访问数组的过程中会产生局部引用,为了避免内存泄漏,应该配套调用释放内存的方法
void Release< PrimitiveType >ArrayElements (ArrayType array, jboolean elems,jint mode)*
mode取值 | 说明 |
---|---|
0 | 拷贝内容并释放elems的缓存即刷新java数组并释放c/c++数组(copy back the content and free the elems buffer) |
JNI_COMMIT | 拷贝内容并但不释放elems的缓存即只刷新java数组(copy back the content but do not free the elems buffer) |
JNI_ABORT | 直接释放elems的缓存(free the buffer without copying back the possible changes) |
6.3、设置对象数组的元素
//如果要对java对象数组的对象进行操作,你必须使用GetObjectArrayElement函数以jobject形式返回数组的元素,然后再操作jobject
你也可以用SetObjectArrayElement函数把jobject放进java对象数组
void SetObjectArrayElement(jobjectArray array, jsize index, jobject value);
6.4、截取和设置非Object数组
数组的截取操作即获取数组的子集,仅仅是针对具体类型(非Object)的,只要调用
**void Get< PrimitiveType >ArrayRegion( ArrayType array,jsize start, jsize len, NativeType *buf)**传入对应的参数即可。
- 表示把数组array从第start个元素开始的len个元素拷贝到buf地址所指向的内存区域
范围设置数组也是仅针对具体类型的,通过调用
*Set< PrimitiveType >ArrayRegion(ArrayType array,jsize start, jsize len, const NativeType buf)
- 表示把数组array从第start个元素开始的len个元素设置到buf地址所指向的内存区域
6.5、获取操作基本数据类型数组的直接指针
与前文中关于操作原始字符串类似,在某些情况下,当我们需要原始数据指针来进行一些操作,可以调用
void GetPrimitiveArrayCritical(jarray array, jboolean isCopy)函数来获取一个指向原始数据的指针,同样地存在一些限制,在调用void ReleasePrimitiveArrayCritical(jarray array, void* carray, jint mode)**函数之前,两个函数之间不能存在任何可能导致线程阻塞的操作,而且GC的运行会打断线程,因此期间任何调用GC的线程都会被阻塞。
7、动态注册和反注册JNI函数
//向 clazz 参数指定的类注册本地方法。methods 参数将指定 JNINativeMethod 结构的数组,其中包含本地方法的名称、签名和函数指针。nMethods 参数将指定数组中的本地方法数。
jint RegisterNatives(jclass clazz, const JNINativeMethod *methods, jint nMethods)
typedef struct {
char *name;
char *signature;
void *fnPtr;
} JNINativeMethod;
反注册JNI函数
//取消注册类的本地方法。类将返回到链接或注册了本地方法函数前的状态。该函数不应在常规平台相关代码中使用。相反,它可以为某些程序提供一种重新加载和重新链接本地库的途径。
jint UnregisterNatives( jclass clazz)
8、其他函数
函数 | 说明 | |
---|---|---|
Throw | ||
ThrowNew | ||
ExceptionOccurred | ||
ExceptionDescribe | ||
ExceptionClear | ||
FatalError | ||
NewGlobalRef | ||
DeleteGlobalRef | ||
DeleteLocalRef |
未完待续…