概述
JNI学习总结
- JNI简介
- JavaVM和JNIEnv
- JNI Types and Data Structures
- Primitive Types
- Reference Types
- Field and Method IDs
- The Value Type
- JNI注册
- JNI静态注册
- 静态注册的示例代码
- JNI静态注册的优缺点
- JNI动态注册
- Type Signatures
- 动态注册的示例代码
- 在代码中学习jni functions
- 字符串操作
- 基本类型数组操作
- int数组
- byte数组
- primitiveArrayCritical
- 访问对象的域和方法
- 获取jclass
- 创建一个jobject
- 字段ID和方法ID
- 操作对象数组
- 静态成员操作
- CallNonvirtualMethod
JNI简介
做过C和Java开发的都知道,如果需要C/C++代码和Java代码交互,比如应用层Java代码调用C的动态库,需要用到JNI。在某些情况下,纯Java开发可能并不是一个最优的选择,比如已经有了相关功能的C库,通过JNI调用C库显然比再用Java重新实现更能降低开发成本,提高开发效率;另外,java的反编译特性决定了用java开发有些关键功能并不是一个安全方案,这时候用C/C++开发关键模块,再通过JNI调用显然更好。
那么JNI是什么呢?JNI是Java Native Interface的缩写,即Java本地接口。 它定义了一种虚拟机中的Java代码与用C/C++编写的本地代码交互的方式。支持从动态库中加载代码,虽然有时麻烦,但效率相当高。熟悉JNI相关语法是JNI开发的关键。
乍看起来,JNI主要是通过java来调用c本地方法,其实不仅如此。
通过JNI可以使用本地方法来操作Java,比如:
创建,检查和更新Java对象(包括数组和字符串)。
调用Java方法。
捕捉并抛出异常。
加载类并获取类信息。
执行运行时类型检查。
下面进入JNI正题。
JavaVM和JNIEnv
jni代码示例:
//这是调用native方法java的类路径
static const char * const kClassName = "com/milanac007/jnistudydemo/JavaLayer";
jint JNI_OnLoad(JavaVM *vm, void *reserved){
JNIEnv *env = NULL;
jint result = JNI_FALSE ;
if(vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) {
logI(TAG, "GetEnv failed.");
return result;
}
jclass myClass = env->FindClass(kClassName);
if(myClass == NULL){
logI(TAG, "can't get class %sn", kClassName);
jthrowable ex = env->ExceptionOccurred();
if(ex) {
env->ExceptionDescribe();
env->ExceptionClear();
}
return result;
}
if(env->RegisterNatives(myClass, gMethods, sizeof(gMethods)/ sizeof(gMethods[0])) <0) {
logI(TAG, "regeister native method failedn");
return result;
}
logI(TAG, "---JNI_OnLoad---");
return JNI_VERSION_1_4;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_milanac007_jnistudydemo_JavaLayer_getNativeInitStr(JNIEnv *env, jobject instance) {
return env->NewStringUTF("Welcome to JNI.");//根据utf-编码的字符串构造一个新的java.lang.String对象。
}
以上代码分别为jni的动态注册和静态注册方法,在jni开发中很常见。在参数中有JavaVM和JNIEnv,它们是什么呢?用什么用呢?
本质上它们都是指向函数表指针的指针(二级指针)。
JavaVM是指向Invocation API函数表的指针。它允许创建和销毁一个JavaVM。从理论上讲,每个进程可以有多个JavaVM,但Android只允许一个进程有一个。下列代码展示了这个函数表。
typedef const struct JNIInvokeInterface *JavaVM;
const struct JNIInvokeInterface ... = {
NULL,
NULL,
NULL,
DestroyJavaVM,
AttachCurrentThread,
DetachCurrentThread,
GetEnv,
AttachCurrentThreadAsDaemon
};
请注意,创建VM函数JNI_CreateJavaVM()并不在JavaVM函数表中,它是可以直接调用的,保证了在没有JavaVM结构的情况下来创建一个JavaVM。
在Android中,所有线程都是Linux线程,由内核调度。它们通常由java代码Thread.start()启动,默认启动后已经连接了Java虚拟机JavaVM,并且可以使用该线程的JNIEnv来进行JNI调用。同时,如果本地代码层创建的线程(调用pthread_create())也可以创建JavaVM,并使用AttachCurrentThread或AttachCurrentThreadAsDaemon函数连接到JavaVM,并使用GetEnv来获取该线程的JNIEnv来进行JNI调用。在线程被连接到JavaVM之前,它没有JNIEnv,是不能进行JNI调用的。
再来看JNIEnv。
JNIEnv类型是指向存储所有JNI函数指针的函数表的指针。可通过JNIEnv参数以固定的偏移量来访问每个JNI函数。 定义如下:
typedef const struct JNINativeInterface* JNIEnv;
const struct JNINativeInterface ... = {
GetVersion,
DefineClass,
FindClass,
...
Throw,
ThrowNew,
ExceptionOccurred,
ExceptionDescribe,
ExceptionClear,
FatalError,
...
NewGlobalRef,
DeleteGlobalRef,
DeleteLocalRef,
IsSameObject,
NewLocalRef,
AllocObject,
NewObject,
...
GetMethodID,
CallObjectMethod,
...
};
JNI函数可通过JNI接口指针使用。 该指针指向一个指针数组,其中每个指针指向一个接口函数。 每个接口函数都在数组内的预定义偏移处。 图2-1展示了接口指针的组织结构。
图 2-1 Interface Pointer
本地方法接收JNI接口指针作为参数。当从同一Java线程对本地方法进行多次调用时, JavaVM保证将相同的接口指针传递给本地方法。
实现JNI的VM可以在JNI接口指针所指向的区域中分配和存储线程局部数据,即JNIEnv用于线程本地存储。出于这个原因,不能在线程之间共享一个JNIEnv。如果一段代码没有其他方式来获得它所在线程的JNIEnv,可以通过共享JavaVM,并使用GetEnv来获取该线程的JNIEnv。(前提是该线程已连接到JavaVM。)
JNI Types and Data Structures
下面讨论JNI如何将Java类型映射到本地C类型。
Primitive Types
Table 3-1 描述Java基本类型及其与机器相关的本地代码的等价类型。
Table 3-1 Primitive Types and Native Equivalents
其中,Java Type为Java代码层的对象类型,Native Type为Native层接收、操作的java对象的对应类型。
Reference Types
JNI包含一系列的对应于不同种类的Java对象的引用类型。 JNI引用类型按照Figure 3-1.所示的层次结构进行组织。
Figure 3-1 Reference Type Hierarchy
可以看到:
-
jobject
- 对应于Java的java.lang.Object jstring
- 对应于Java的java.lang.String jarray及派生类
- 对应于Java的数组arrays jclass
- 对应于Java的java.lang.Class
在C语言中,所有其他JNI引用类型的定义与jobject的定义都相同。 例如jclass:
typedef jobject jclass;
以下摘自jni.h中,C部分定义:
/*
* 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;
在C ++中,JNI引入了一组伪类来加强子类型关系。 例如:
class _jobject {};
class _jclass : public _jobject {};
...
typedef _jobject *jobject;
typedef _jclass *jclass;
以下摘自jni.h中,C++部分定义:
/*
* 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 {};
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;
Field and Method IDs
方法和字段ID是常规的C指针类型:
struct _jfieldID; /* opaque structure */
typedef struct _jfieldID* jfieldID; /* field IDs */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID* jmethodID; /* method IDs */
The Value Type
jvalue联合类型被用作参数数组中的元素类型。 声明如下:
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
JNI注册
当Java代码去执行一个native 方法时,虚拟机是怎么知道该调用 so 中的哪个方法呢?答案就是注册,通过注册,将指定的 native方法和 so 中对应的本地方法进行绑定,建立起函数映射表,从函数映射表中找到相应的本地方法。注册分为 静态注册 和 动态注册 两种。默认的实现方式即静态注册。
JNI静态注册
虚拟机VM加载so时,动态链接器根据名称来进行解析。通过名称对应规则,匹配并链接java中对应的native方法。规则如下:
Java_包名_类名_方法名
- 以Java开头,每部分用下划线连接
- 包名也使用下划线隔开
- 如果名称中本来就包含下划线,将使用下划线加数字替换
- 对于重载的本地方法,两个下划线(“ __”)后跟参数签名(针对c++,c中没有重载)
静态注册的示例代码
java代码:
package com.milanac007.jnistudydemo;
public class JavaLayer {
static {
System.loadLibrary("native-lib");
}
public static native String getNativeInitStr();
public static native int java_add(int a, int b);
public static native int java_sub(int a, int b);
}
对应的cpp代码:
extern "C"
JNIEXPORT jstring JNICALL
Java_com_milanac007_jnistudydemo_JavaLayer_getNativeInitStr(JNIEnv *env, jobject instance) {
return env->NewStringUTF("Welcome to JNI.");//创建一个utf-8编码的java.lang.String对象。
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_milanac007_jnistudydemo_JavaLayer_java_1add(JNIEnv *env, jobject instance, //TOTO: "_" : _1
jint a, jint b) {
return a+b;
}
extern "C"
jint Java_com_milanac007_jnistudydemo_JavaLayer_java_1sub(JNIEnv *env, jobject instance, jint a, jint b){
return a-b;
}
注:
extern “C”
是告诉C++编译器以C Linkage方式编译,按C的规则去翻译相应的函数名而不是C++的,也就是抑制C++的name mangling机制。例如:
void Test(void);
根据重载/namespace等机制,C++编译器可能实际把它改名为vTest_v,
extern “C” void Test(void)
则和C编译器一样为_Test。
此外,当extern不与"C"在一起修饰变量或函数时,如在头文件中: extern int g_Int; 它的作用就是声明 函数或全局变量 ,也就是可以使用在其他模块中定义的函数或变量。
注:由程序自动生成的native代码都带JNIEXPORT 和 JNICALL 两个宏定义,标识了该方法是被java native方法调用的。我们自己写的native代码可以不带这连个宏,添加这两个宏有助于提高VM查找方法的效率。
VM检查与本地库中驻留的方法相匹配的方法名称。 VM首先寻找简称;即没有参数签名的名称。然后,它将查找长名称,即带有参数签名的名称。仅当本地方法被另一个本地方法重载时,程序员才需要使用长名称。无论如何,如果本地方法与java方法具有相同的名称,则这不是问题。非本地方法(Java方法)不驻留在本地库中。
在下面的示例中,不必使用长名称链接本驻留方法g,因为另一个方法g不是本地方法,因此不在本地库中。
class Cls1 {
int g(int i);
native int g(double d);
}
同时,我们注意到对于java native 方法 java_add,对应的本地方法名称为
Java_com_milanac007_jnistudydemo_JavaLayer_java_1add。可见方法名称中的下划线被转成了"_1"。这是什么规则呢?
由于名称或类型描述符从不以数字开头,因此可以将_0,…,_ 9用于转义序列,如表2-1所示,同时可见,数组类型符号"[“用”_3"表示。
JNI静态注册的优缺点
-
优点
- 简单明了 缺点
-
必须遵循注册规则
名字过长
运行时查找效率低
JNI动态注册
原理:通过 RegisterNatives 方法手动完成 native 方法和 so 中本地方法的绑定,这样VM就可以通过这个函数映射表直接找到相应的方法了。
jint RegisterNatives(JNIEnv *env, jclass clazz,const JNINativeMethod *methods,
jint nMethods);
PARAMETERS:
env: the JNI interface pointer.
clazz: a Java class object.
methods: the native methods in the class.
nMethods: the number of native methods in the class.
RETURNS:
Returns “0” on success; returns a negative value on failure.
THROWS:
NoSuchMethodError: if a specified method cannot be found or if the method is not native.
用参数clazz指定的类注册本地方法。 methods参数指定一个JNINativeMethod结构数组,其中包含java native方法的名称,签名和函数指针。 JNINativeMethod结构的名称和签名字段是指向UTF-8字符串的指针。 nMethods参数指定数组中本地方法的数量。 JNINativeMethod结构定义如下:
typedef struct {
char *name; //java native方法名称
char *signature; //方法签名
void *fnPtr; //对应的本地函数指针
} JNINativeMethod;
fnPtr的调用如下:
ReturnType (*fnPtr)(JNIEnv *env, jobject objectOrClass, ...);
Type Signatures
JNI使用Java VM的类型签名表示。 Table 3-2显示了这些类型签名。
Table 3-2 Java VM Type Signatures
从上述表格可以总结各种类型的类型签名:
- 对于基本类型,类型签名就是它们的首字母大写;特殊情况:boolean用Z表示;long用J表示;
- java的类:使用“L”开头、完整的类全名(“/”代替“.”)、“;”结尾表示;
- 数组:使用 “[”表示数组,“[”的数量代表数组的维数;其后跟类型;如:“[I”表示int [], “[[F”表示float[][]。
- 方法:(参数类型)返回值类型
举例,java方法
long f (int n, String s, int[] arr);
有以下类型签名:
(ILjava/lang/String;[I)J
动态注册的示例代码
java代码如下:
package com.milanac007.jnistudydemo;
public class JavaLayer {
static {
System.loadLibrary("native-lib");
}
...
public static native int java_div(int a, int b);
}
对应的本地代码:
jint native_div(JNIEnv *env, jobject obj, jint a, jint b) {
return a/b;
}
static JNINativeMethod gMethods[] = {
{"java_div", "(II)I", (void *)native_div},
};
//这是调用本地方法的java native方法所在的类路径
static const char * const kClassName = "com/milanac007/jnistudydemo/JavaLayer";
jint JNI_OnLoad(JavaVM *vm, void *reserved){
JNIEnv *env = NULL;
jint result = JNI_FALSE ;
if(vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) {
logI(TAG, "GetEnv failed.");
return result;
}
jclass myClass = env->FindClass(kClassName);
if(myClass == NULL){
logI(TAG, "can't get class %sn", kClassName);
jthrowable ex = env->ExceptionOccurred();
if(ex) {
env->ExceptionDescribe();
env->ExceptionClear();
}
return result;
}
if(env->RegisterNatives(myClass, gMethods, sizeof(gMethods)/ sizeof(gMethods[0])) <0) {
logI(TAG, "regeister native method failedn");
return result;
}
logI(TAG, "---JNI_OnLoad---");
return JNI_VERSION_1_4;
}
加载本地库时(例如,通过System.loadLibrary),VM会调用JNI_OnLoad。 JNI_OnLoad必须返回本地库所需的JNI版本。
代码中的“JNI_VERSION_1_4”代表为了要使用J2SE 1.4版中引入的JNI函数,除了1.1、1.2版中可用的那些函数外,本地库还必须导出返回JNI_VERSION_1_4的JNI_OnLoad函数。
如果本地库未导出JNI_OnLoad函数,则VM假定该库仅需要JNI版本JNI_VERSION_1_1。 如果VM无法识别JNI_OnLoad返回的版本号,则无法加载本地库。
在代码中学习jni functions
下面的章节,我们将结合代码来学习典型的jni functions。
jni functions的第一个参数都为JNIEnv *, 第二个参数为调用的Java类的实例或 Class 对象,如果是实例方法,则该参数是 jobject,如果是静态方法,则是 jclass。
字符串操作
java代码:
public static native String java_str(String value);
本地代码:
jstring native_str(JNIEnv *env, jobject obj, jstring value){
jboolean isCopy = JNI_FALSE;
jsize len = env->GetStringUTFLength(value);
// const char * utf = env->GetStringUTFChars(value, &isCopy);
char * utf = (char *)env->GetStringUTFChars(value, &isCopy);
logI(TAG, "native_str() isCopy: %s", isCopy?"true":"false");
if(utf == NULL) {
return NULL;
}
if(isCopy == JNI_TRUE) {
logI(TAG, utf);
env->ReleaseStringUTFChars(value, utf);//不释放会导致GC不会回收value,导致内存泄露
logI(TAG, "strcpy(utf, "12345")");
strcpy(utf, "12345");
logI(TAG, utf);
jstring demo = env->NewStringUTF("hello world");
jsize len = env->GetStringUTFLength(demo);
logI(TAG, "hello world's length: %d", len);
env->GetStringUTFRegion(demo, 0, len, utf);//注:如果len>utf指向的内存长度,数组越界,导致异常
logI(TAG, "exec GetStringUTFRegion()");
logI(TAG, utf);
env->DeleteLocalRef(demo);
} else{
//TODO :不能操作指针修改字符串的内容,因为JVM中的原始字符串也会被更改,这会打破Java中字符串不可变的原则,导致崩溃。
}
return env->NewStringUTF(utf);
}
static JNINativeMethod gMethods[] = {
{"java_str", "(Ljava/lang/String;)Ljava/lang/String;", (void *)native_str},
};
测试代码:
Log.i(TAG, "JavaLayer.java_str("abcde abcde"): " + JavaLayer.java_str("abcde abcde"));
log:
10-28 16:54:55.436 28935-28935/com.milanac007.jnistudydemo I/JniLayer: native_str() isCopy: true
10-28 16:54:55.436 28935-28935/com.milanac007.jnistudydemo I/JniLayer: abcde abcde
10-28 16:54:55.436 28935-28935/com.milanac007.jnistudydemo I/JniLayer: strcpy(utf, "12345")
10-28 16:54:55.436 28935-28935/com.milanac007.jnistudydemo I/JniLayer: 12345
10-28 16:54:55.436 28935-28935/com.milanac007.jnistudydemo I/JniLayer: hello world's length: 11
10-28 16:54:55.436 28935-28935/com.milanac007.jnistudydemo I/JniLayer: exec GetStringUTFRegion()
10-28 16:54:55.436 28935-28935/com.milanac007.jnistudydemo I/JniLayer: hello world
10-28 16:54:55.436 28935-28935/com.milanac007.jnistudydemo I/MainActivity: JavaLayer.java_str("abcde abcde"): hello world
⚠️:
- GetStringUTFLength:获取utf-8编码的字符串的长度;
- GetStringUTFChars :
const char * GetStringUTFChars(JNIEnv *env, jstring string,
jboolean *isCopy);
返回一个UTF-8编码的字节数组的指针。该数组在被ReleaseStringUTFChars()释放之前,一直有效。
该指针可能是原始java字符串string的指针,也可能是string拷贝的指针。
如果不关心是否发生了拷贝,则可将isCopy设为NULL或0。
如果isCopy不为NULL,则如果发生了拷贝,则* isCopy被设置为JNI_TRUE; 没发生拷贝,将其被设置为JNI_FALSE,即返回的是java字符串string的原始指针。
- ReleaseStringUTFChars:
void ReleaseStringUTFChars(JNIEnv *env, jstring string,
const char *utf);
通知VM本地代码不再需要访问字符串string的指针utf。 因为GetStringUTFChars会给java对象string添加一个引用计数,相应的ReleaseStringUTFChars会减少一个引用计数。如果不调用ReleaseStringUTFChars,那么VM不会回收string,造成内存泄漏。
当isCopy为JNI_TRUE时,utf为java字符串string的拷贝的指针。通过对GetStringUTFChars方法的结果强转为char *,我们就可以改变utf指向的地址。 比如先 strcpy(utf, “12345”);使字符串"12345"写入utf指的内存。然后调用
GetStringUTFRegion,将新的字符串demo的内容写入utf指向的内存。
- GetStringUTFRegion
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf);
将从偏移量start开始的len个Unicode字符转换为UTF-8编码,并将结果放入给定的缓冲区buf中。
注:如果len>utf指向的内存长度,数组越界,导致异常
env->GetStringUTFRegion(demo, 0, len, utf);
最后,为保险起见,手动将本地代码中的创建的jobject及其子类对象的本地引用删除。
env->DeleteLocalRef(demo);
- Global and Local References
JNI将本地代码使用的对象引用分为两类:局部引用和全局引用。 局部引用在本地方法调用期间有效,并在本地方法返回后自动释放。 全局引用在显式释放之前一直保持有效。
对象以局部引用的形式传递给本地方法。 JNI函数返回的所有Java对象都是局部引用。 JNI允许程序员从局部引用创建全局引用。 期望Java对象的JNI函数接受全局和局部引用。 本地方法可能会返回VM的局部或全局引用作为其结果。
在大多数情况下,程序员应在本地方法返回后依靠VM回收所有局部引用。 但是,有时程序员应该显式释放局部引用。 例如,考虑以下情况:
本地方法访问一个大的Java对象,并创建该Java对象的局部引用。 然后,本地方法将执行其他计算,然后再返回到调用方。 即使在其余的计算中不再使用该对象,该Java对象的局部引用也防止了GC对该对象进行垃圾回收。
本地方法创建了大量局部引用,尽管并非所有的局部引用都会被使用。 由于VM需要一定的空间来跟踪局部引用,因此创建太多局部引用可能会导致系统内存不足。 例如,本地方法遍历一个大容量的对象数组,检索元素作为局部引用,并在每次迭代时对一个元素进行操作。 每次迭代后,程序员不再需要这个数组元素的局部引用。
JNI允许程序员在本地方法中的任何时候手动删除局部引用。 为了确保程序员可以手动释放局部引用,不允许JNI函数创建额外的局部引用,除非它们会作为结果返回引用。
局部引用仅在创建它们的线程中有效。 本地代码不得将局部引用从一个线程传递到另一个线程。
Java VM为从Java到本地方法的每次控制转换创建一个注册表。 注册表将不可删除的局部引用映射到Java对象,并防止垃圾回收该对象。 传递给本地方法的所有Java对象(包括那些作为JNI函数调用结果返回的Java对象)都将自动添加到注册表中。 本地方法返回后,注册表将被删除,注册表中的所有子项都允许被GC垃圾回收。
有多种实现注册表的方法,例如使用表,链表或哈希表。 尽管可以使用引用计数来避免注册表中出现重复项,但是JNI没有义务检测并删除重复项。
请注意,不能通过保守地扫描本地堆栈来实现局部引用。 本地代码可以将局部引用存储到全局或堆数据结构中。
关于本地引用LocalRef、全局引用GlobalRef的相关介绍可以参考
JNI Functions
本文就不介绍了。
基本类型数组操作
int数组
java代码:
public static native void java_addOne(int[] values);//每个数组元素加一
本地代码:
void native_addOne(JNIEnv *env, jobject obj, jintArray values){
jboolean isCopy = JNI_FALSE;
jint * pointer = env->GetIntArrayElements(values, &isCopy);
logI(TAG, "native_addOne() isCopy: %s", isCopy?"true":"false");
jsize size = env->GetArrayLength(values);
for(int i=0; i<size; i++){
pointer[i] += 1;
}
env->ReleaseIntArrayElements(values, pointer, 0);
// env->ReleaseIntArrayElements(values, pointer, JNI_COMMIT);
// env->ReleaseIntArrayElements(values, pointer, JNI_ABORT);
}
static JNINativeMethod gMethods[] = {
{"java_addOne", "([I)V", (void *)native_addOne},
};
测试代码:
int[] origArray = {1, 2, 3, 4, 5};
Log.i(TAG, "java_addOne执行前: " + Arrays.toString(origArray));
JavaLayer.java_addOne(origArray);
Log.i(TAG, "java_addOne执行后: " + Arrays.toString(origArray));
log:
0-28 16:54:55.435 28935-28935/com.milanac007.jnistudydemo I/MainActivity: java_addOne执行前: [1, 2, 3, 4, 5]
10-28 16:54:55.435 28935-28935/com.milanac007.jnistudydemo I/JniLayer: native_addOne() isCopy: true
10-28 16:54:55.435 28935-28935/com.milanac007.jnistudydemo I/MainActivity: java_addOne执行后: [2, 3, 4, 5, 6]
⚠️:
这个本地方法的作用是将给定的int数组的每个元素加1。
- GetArrayLength:返回给定的java数组中元素的个数。
jsize GetArrayLength(JNIEnv *env, jarray array);
- GetArrayElements Routines:
NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env,
ArrayType array, jboolean *isCopy);
执行成功返回基本类型数组的指针,失败返回NULL。 该结果在调用相应的Release ArrayElements()函数之前一直有效。 由于返回的数组可能是原Java数组的拷贝,因此在调用Release ArrayElements()之前,对返回的数组所做的更改不一定会反映在原Java数组中。
如果不关心是否发生了拷贝,则可将isCopy设为NULL或0。
如果isCopy不为NULL,则如果发生了拷贝,则* isCopy被设置为JNI_TRUE; 没发生拷贝,将其被设置为JNI_FALSE,即返回的是java数组array的原始指针。
NativeType代表返回值类型,具体的基本类型函数、参数array的类型ArrayType、返回值类型NativeType对应如下表:
不管Java VM中如何表示布尔数组,GetBooleanArrayElements()始终返回指向jboolean的指针,每个字节表示一个元素
(注: typedef uint8_t jboolean; /* unsigned 8 bits */) 。
其他类型的所有数组都保证在内存中是连续的。
- ReleaseArrayElements Routines
与GetArrayElements 相对应的是ReleaseArrayElements 。
void Release<PrimitiveType>ArrayElements(JNIEnv *env,
ArrayType array, NativeType *elems, jint mode);
当本地代码不再需要访问使用相应的Get ArrayElements()函数获取的数组指针elems时,通知VM回收资源。 如有必要,此函数将对elems指向的数组所做的所有更改复制回原数组。
mode参数表明应如何释放数组缓冲区。 如果elems指向的数组不是数组array中的拷贝,则mode无效。 否则,mode会产生以下影响,如下表所示:
mode=0,表示将对副本数组的更改拷贝会原数组,并释放副本buffer。所以这里对数组元素加1后,原数组的每个元素也都加一了。
感兴趣的话可以试试另外两个参数。它们可以更好地控制内存管理,但使用应格外小心。
byte数组
java代码:
public static native byte[] java_byteArray(byte[] src);
本地代码:
jbyteArray native_byteArray(JNIEnv *env, jobject obj, jbyteArray src) {
jboolean isCopy = JNI_FALSE;
jbyte * pointer = env->GetByteArrayElements(src, &isCopy);
// typedef int8_t jbyte; /* signed 8 bits , defined from jni.h */
// typedef __int8_t int8_t;/*defined from stdint.h */
// typedef signed char __int8_t;
// 故: jbyte * <==> signed char *
jsize len = env->GetArrayLength(src);
logI(TAG, "native_byteArray() isCopy: %s", isCopy?"true":"false");
if(pointer == NULL) {
return NULL;
}
jbyteArray dst = env->NewByteArray(10);
env->SetByteArrayRegion(dst, 0, len>10?10:len, pointer);
env->ReleaseByteArrayElements(src, pointer, 0);
return dst;
}
static JNINativeMethod gMethods[] = {
{"java_byteArray", "([B)[B", (void *)native_byteArray},
};
测试代码:
String hex = "0a0b0c";
byte[] src = JavaLayer.toByteArray(hex);
Log.i(TAG, "java_byteArray执行前: " + Arrays.toString(src));
byte[] dst = JavaLayer.java_byteArray(src);
Log.i(TAG, "java_byteArray执行后: " + Arrays.toString(dst));
Log.i(TAG, "=========================================");
hex = "000102030405060708090a0b0c0d0e10";
src = JavaLayer.toByteArray(hex);
Log.i(TAG, "java_byteArray执行前: " + Arrays.toString(src));
dst = JavaLayer.java_byteArray(src);
Log.i(TAG, "java_byteArray执行后: " + Arrays.toString(dst));
log:
10-28 16:54:55.436 28935-28935/com.milanac007.jnistudydemo I/MainActivity: java_byteArray执行前: [10, 11, 12]
10-28 16:54:55.436 28935-28935/com.milanac007.jnistudydemo I/JniLayer: native_byteArray() isCopy: true
10-28 16:54:55.436 28935-28935/com.milanac007.jnistudydemo I/MainActivity: java_byteArray执行后: [10, 11, 12, 0, 0, 0, 0, 0, 0, 0]
10-28 16:54:55.436 28935-28935/com.milanac007.jnistudydemo I/MainActivity: =========================================
10-28 16:54:55.436 28935-28935/com.milanac007.jnistudydemo I/MainActivity: java_byteArray执行前: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16]
10-28 16:54:55.436 28935-28935/com.milanac007.jnistudydemo I/JniLayer: native_byteArray() isCopy: true
10-28 16:54:55.436 28935-28935/com.milanac007.jnistudydemo I/MainActivity: java_byteArray执行后: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
⚠️:
- jbyte 与signed char * 是可以直接转化的。原因如下:
typedef int8_t jbyte; / signed 8 bits , defined from jni.h */
typedef __int8_t int8_t;/*defined from stdint.h */
typedef signed char __int8_t; - NewByteArray: 原型:
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
构建并返回指定长度、指定基本类型的java数组对象,失败返回NULL。
具体的函数分类可参见下表:
3. SetByteArrayRegion: 函数原型:
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array,
jsize start, jsize len, const NativeType *buf);
将缓冲区buf的内容复制基本数组array。
array: a Java array.
start: the starting index.
len: the number of elements to be copied.
buf: the source buffer.
具体的函数分类可参见下表:
与之相对应的是
void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array,
jsize start, jsize len, NativeType *buf);
将基本数组array的一个区域(起始坐标start、长度len)复制到缓冲区buf。
具体的函数分类可参见下表:
primitiveArrayCritical
java代码:
public static native void java_primitiveArrayCritical(int[] src);
本地代码:
void native_primitiveArrayCritical(JNIEnv *env, jobject obj, jintArray src) {
jboolean isCopy = JNI_FALSE;
int *pointer = (int *)env->GetPrimitiveArrayCritical(src, &isCopy);
logI(TAG, "GetPrimitiveArrayCritical(), isCopy=%s", isCopy?"true":"false");
jsize len = env->GetArrayLength(src);
for(int i=0;i<len; i++){
pointer[i] += 1;
}
// env->ReleasePrimitiveArrayCritical(src, pointer, 0);
// env->ReleasePrimitiveArrayCritical(src, pointer, JNI_COMMIT);
env->ReleasePrimitiveArrayCritical(src, pointer, JNI_ABORT);
}
static JNINativeMethod gMethods[] = {
{"java_primitiveArrayCritical", "([I)V", (void *)native_primitiveArrayCritical},
};
测试代码:
int[] srcArray = {1, 2, 3, 4, 5};
Log.i(TAG, "java_primitiveArrayCritical执行前,srcArray= " + Arrays.toString(srcArray));
JavaLayer.java_primitiveArrayCritical(srcArray);
Log.i(TAG, "java_primitiveArrayCritical执行后,srcArray= " + Arrays.toString(srcArray));
log:
10-28 16:54:55.437 28935-28935/com.milanac007.jnistudydemo I/MainActivity: java_primitiveArrayCritical执行前,srcArray= [1, 2, 3, 4, 5]
10-28 16:54:55.437 28935-28935/com.milanac007.jnistudydemo I/JniLayer: GetPrimitiveArrayCritical(), isCpoy=false
10-28 16:54:55.437 28935-28935/com.milanac007.jnistudydemo I/MainActivity: java_primitiveArrayCritical执行后,srcArray= [2, 3, 4, 5, 6]
⚠️:
- GetPrimitiveArrayCritical和ReleasePrimitiveArrayCritical
void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);
GetPrimitiveArrayCritical: 如果可能,VM返回指向基元数组的指针;否则返回的是拷贝。当本地代码持有通过GetPrimitiveArrayCritical获得的原数组的指针时,VM可暂时禁用垃圾收集。
该函数为我们提供了获取并操作原数组的方式,但需要注意的是:调用GetPrimitiveArrayCritical之后,本地代码在调用ReleasePrimitiveArrayCritical之前不应运行很长时间。在这个关键区域内,本地代码不得调用其他JNI函数或任何可能导致当前线程阻塞并等待另一个Java线程的系统调用。(例如,当前线程不得在被另一个Java线程正在写入的流上调用read。)
从log中看到 :isCpoy=false,即代表没有拷贝发生,从而获取的就是原数组的指针。前面提到,ReleaseXXX方法的mode参数在isCopy为true时才有效,所以这里的ReleasePrimitiveArrayCritical的mode为三值(0,JNI_COMMIT,JNI_ABORT)中的任意一个,都不会影响最终结果。
访问对象的域和方法
Java和本地代码之间可以互相拷贝基本类型变量,例如整型,字符型等。 另一方面,Java对象通过引用传递。 VM必须追踪已传递给本地代码的所有java对象,以使垃圾回收器不会回收这些对象。 反过来,本地代码不再需要java对象时,必须有方法来通知VM。 另外,垃圾收集器必须能够移除被本地代码引用的对象。
JNI允许本地代码访问Java对象的字段并调用它的方法。 JNI通过它们的符号名和类型签名来标识方法和字段。从名称和签名中确定字段或方法分为两步。首先获取方法或字段ID,然后使用方法或字段ID来调用相应的方法或字段。字段ID或方法ID不会阻止VM将获取ID的类卸载。卸载类后,方法ID或字段ID就失效了。
获取jclass
获取jclass,以下三个方法较常用:
FindClass
jclass FindClass(JNIEnv *env, const char *name);
name参数是完全限定的类名或数组的类型签名。
根据类型签名返回一个类型为jclass的java类对象,没找到则返回NULL。
GetSuperclass
jclass GetSuperclass(JNIEnv *env, jclass clazz);
如果clazz表示除Object类之外的任何类,则此函数返回表示clazz指定的类的超类。
如果clazz表示Object类,或者clazz表示接口,则此函数返回NULL。
GetObjectClass
jclass GetObjectClass(JNIEnv *env, jobject obj);
根据传入的非NULL的java对象,输出一个该对象的类型为jclass的类对象。
创建一个jobject
创建一个jobject主要有以下两个方法:
AllocObject
jobject AllocObject(JNIEnv *env, jclass clazz);
不显式调用构造函数、创建一个java对象,返回该对象的引用。clazz不能是数组类型的引用。
NewObject
jobject NewObject(JNIEnv *env, jclass clazz,
jmethodID methodID, ...);
根据指定构造方法的methodID及参数,调用该构造方法,构造一个Java对象。这个ID必须通过调用GetMethodID()(使用作为方法名参数、void(V)作为返回值类型参数))来获取。
clazz不能是数组类型的引用。
字段ID和方法ID
GetFieldID
jfieldID GetFieldID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);
返回类的实例(非静态)字段的ID。 该字段由其名称和签名指定。Get Field和Set Field系列的访问器函数使用字段ID检索对象字段。调用该方法会导致一个未初始化的类被初始化。
参数中,clazz是通过前面的获取jclass小节获取的。
name和sig:UTF-8编码的字符串,分别为字段名和前面类型。
成功获取字段ID,失败返回NULL。
GetField Routines
NativeType Get<type>Field(JNIEnv *env, jobject obj,
jfieldID fieldID);
返回一个java类的实例的指定字段的值。该字段是由调用GetField()获取的。具体的类型参考下表:
SetField Routines
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID,
NativeType value);
设置java类的实例obj的指定ID的字段值。具体的类型参考下表:
GetMethodID
jmethodID GetMethodID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);
返回类或接口的实例(非静态)方法的方法ID。 该方法可以在clazz的超类之一中定义,并由clazz继承。 该方法由其名称和签名确定。
调用该方法会导致一个未初始化的类被初始化。
参数中,clazz是通过前面的获取jclass小节获取的。
name和sig:UTF-8编码的字符串,分别为字段名和前面类型。
成功获取方法ID,失败返回NULL。
为了得到构造函数的方法ID,需要使用作为方法名、void(V)作为返回类型。
CallMethod Routines
NativeType Call<type>Method(JNIEnv *env, jobject obj,
jmethodID methodID, ...);
调用Java实例方法。 methodID参数必须通过调用GetMethodID()获得。
具体类型参考下表:
CallMethod Routine Name | Native Type |
---|---|
CallVoidMethod() | void |
CallObjectMethod() | jobject |
CallBooleanMethod() | jboolean |
CallByteMethod() | jbyte |
CallCharMethod() | jchar |
CallShortMethod() | jshort |
CallIntMethod() | jint |
CallLongMethod() | jlong |
CallFloatMethod() | jfloat |
CallDoubleMethod() | jdouble |
下面通过demo实践一下。
java代码:
public static native Student java_newjobject();
返回自定义类型Student,它继承自Person,Person有name和sex两个字段,和构造函数、设置sex方法及toString方法。Student还有id字段,同时覆写了toString方法。它们的代码如下:
public class Person {
enum SexType{
male,
female,
unkown
}
protected String name;
protected SexType sex;
Person(){
}
Person(String name, int sex){
this.name = name;
setSex(sex);
}
public void setSex(int sex){
if(sex == 0){
this.sex = SexType.male;
}else if(sex == 1){
this.sex = SexType.female;
}else {
this.sex = SexType.unkown;
}
}
@Override
public String toString() {
return "Person{"+
"name=" + name
+",sex=" + sex.name()
+"}";
}
}
public class Student extends Person{
private int id;
Student(int id, String name, int sex){
super(name, sex);
this.id = id;
}
@Override
public String toString() {
return "Student{"+
"id=" + id
+",name=" + name
+",sex=" + sex.name()
+"}";
}
}
本地方法的作用是创建一个新的Student对象并返回。
本地代码:
jobject native_newjobject(JNIEnv *env, jobject obj){
jclass cls = env->FindClass("com/milanac007/jnistudydemo/Student");
if(env->ExceptionOccurred()){
env->ExceptionDescribe();
env->ExceptionClear();
}
if(cls == NULL){
return NULL;
}
//创建方式一
jobject student = env->AllocObject(cls);
jfieldID idID = env->GetFieldID(cls, "id", "I");
env->SetIntField(student, idID, 1);
jobject name = env->NewStringUTF("李雷");
jfieldID nameID = env->GetFieldID(cls, "name", "Ljava/lang/String;");
env->SetObjectField(student, nameID, name);
jmethodID sexID = env->GetMethodID(cls, "setSex", "(I)V");
if(sexID != NULL) {
env->CallVoidMethod(student, sexID, 0);
if(env->ExceptionOccurred()){
env->ExceptionDescribe();
env->ExceptionClear();
}
}
//创建方式二
// jmethodID constructID = env->GetMethodID(cls, "<init>", "(ILjava/lang/String;I)V");
// if(env->ExceptionOccurred()){
// env->ExceptionDescribe();
// env->ExceptionClear();
// return NULL;
// }
// jstring name = env->NewStringUTF("韩美美");
// jobject student = env->NewObject(cls, constructID, 2, name, 1);
env->DeleteLocalRef(name);
jmethodID toStringID = env->GetMethodID(cls, "toString", "()Ljava/lang/String;");
jstring toStr = (jstring)env->CallObjectMethod(student, toStringID);
if(env->ExceptionOccurred()){
env->ExceptionDescribe();
env->ExceptionClear();
env->DeleteLocalRef(toStr);
} else{
const char *p = env->GetStringUTFChars(toStr, NULL);
logI(TAG, p);
env->ReleaseStringUTFChars(toStr, p);
env->DeleteLocalRef(toStr);
}
return student;
}
static JNINativeMethod gMethods[] = {
{"java_newjobject", "()Lcom/milanac007/jnistudydemo/Student;", (void *)native_newjobject},
};
测试代码:
Student stu = JavaLayer.java_newjobject();
Log.i(TAG, "java_newjobject执行后: stu= " + stu);
log:
10-29 18:36:22.005 4115-4115/? I/JniLayer: Student{id=1,name=李雷,sex=male}
10-29 18:36:22.005 4115-4115/? I/MainActivity: java_newjobject执行后: stu= Student{id=1,name=李雷,sex=male}
⚠️:
- 这里本地方法分别使用两种方式创建Student对象并赋值。方式一使用AllocObject创建java类的实例,再调用SetObjectField给字段赋值,并使用CallVoidMethod调用类方法。方式二是直接使用NewObject调用特定的构造方法创建实例并赋值。
- 在本地方法的实现内创建的jobject及其子类,最后使用完后主动调用DeleteLocalRef以通知VM回收。
- 在本地代码中调用java类的实例方法依然会抛异常。如果本地不做处理,同时java层也不处理,会导致java层崩溃。所以好的习惯是在本地代码调用java方法的地方处理异常。
if(env->ExceptionOccurred()){//如果有异常发生
env->ExceptionDescribe();//打印异常
env->ExceptionClear();//清除异常
env->DeleteLocalRef(toStr);
}else{
//TODO 正常处理流程
}
操作对象数组
java代码:
public static native Student[] java_newjobjectArray();
本地代码:
jobjectArray native_newjobjectArray(JNIEnv *env, jobject obj) {
jclass cls = env->FindClass("com/milanac007/jnistudydemo/Student");
if(env->ExceptionOccurred()){
env->ExceptionDescribe();
env->ExceptionClear();
}
if(cls == NULL){
return NULL;
}
jmethodID initID = env->GetMethodID(cls, "<init>", "(ILjava/lang/String;I)V");
jstring default_name = env->NewStringUTF("未知");
jobject stu = env->NewObject(cls, initID, -1, default_name, -1);
jobjectArray stuList = env->NewObjectArray(5, cls, stu);
jstring name1 = env->NewStringUTF("李明");
jobject stu1 = env->NewObject(cls, initID, 1, name1, 0);
jstring name2 = env->NewStringUTF("王小虎");
jobject stu2 = env->NewObject(cls, initID, 2, name2, 0);
jstring name3 = env->NewStringUTF("王娇");
jobject stu3 = env->NewObject(cls, initID, 3, name3, 1);
env->SetObjectArrayElement(stuList, 0, stu1);
env->SetObjectArrayElement(stuList, 1, stu2);
env->SetObjectArrayElement(stuList, 2, stu3);
return stuList;
}
static JNINativeMethod gMethods[] = {
{"java_newjobjectArray", "()[Lcom/milanac007/jnistudydemo/Student;", (void *)native_newjobjectArray},
};
测试代码:
Student[] students = JavaLayer.java_newjobjectArray();
Log.i(TAG, "java_newjobjectArray执行后: students= " + Arrays.toString(students));
log:
10-30 18:20:35.996 18182-18182/com.milanac007.jnistudydemo I/MainActivity: java_newjobjectArray执行后: students= [Student{id=1,name=李明,sex=male}, Student{id=2,name=王小虎,sex=male}, Student{id=3,name=王娇,sex=female}, Student{id=-1,name=未知,sex=unkown}, Student{id=-1,name=未知,sex=unkown}]
⚠️:
这个例子的本地代码首先创建了一个size=5的java对象数组,并用NewObject创建的默认对象填充数组;然后调用SetObjectArrayElement()修改指定index的数组成员对象。
NewObjectArray
jobjectArray NewObjectArray(JNIEnv *env, jsize length,
jclass elementClass, jobject initialElement);
构造一个新数组,其中包含类elementClass中的对象。 所有元素都初始化为initialElement。
SetObjectArrayElement
void SetObjectArrayElement(JNIEnv *env, jobjectArray array,
jsize index, jobject value);
将java对象value设置为对象数组array中索引为index上的元素;
与之相对应的为GetObjectArrayElement
jobject GetObjectArrayElement(JNIEnv *env,
jobjectArray array, jsize index);
返回java对象数组array中索引为index的元素;
静态成员操作
java代码:
public static native void java_accessStaticMember();
//这里为Student类添加一个静态变量和一个静态方法:
public class Student extends Person{
...
private static int code = 0xff;
protected static String getVersion(){
return "1.0.0.1";
}
}
本地代码:
void native_accessStaticMember(JNIEnv *env, jobject obj){
jclass cls = env->FindClass("com/milanac007/jnistudydemo/Student");
if(env->ExceptionOccurred()){
env->ExceptionDescribe();
env->ExceptionClear();
}
if(cls == NULL){
return ;
}
jfieldID codeID = env->GetStaticFieldID(cls, "code", "I");
if(env->ExceptionOccurred()){
env->ExceptionDescribe();
env->ExceptionClear();
} else if(codeID != NULL){
int code = env->GetStaticIntField(cls, codeID);
logI(TAG, "code: 0x%02x", code);
env->SetStaticIntField(cls, codeID, 0x0a);
logI(TAG, "SetStaticIntField() code=0x0a");
code = env->GetStaticIntField(cls, codeID);
logI(TAG, "code: 0x%02x", code);
}
jmethodID versionID = env->GetStaticMethodID(cls, "getVersion", "()Ljava/lang/String;");
if(env->ExceptionOccurred()) {
env->ExceptionDescribe();
env->ExceptionClear();
} else if(versionID != NULL) {
jstring versionStr = (jstring)env->CallStaticObjectMethod(cls, versionID);
const char * version = env->GetStringUTFChars(versionStr, NULL);
logI(TAG, "version :%s", version);
}
}
static JNINativeMethod gMethods[] = {
{"java_accessStaticMember", "()V", (void *)native_accessStaticMember},
};
测试代码:
JavaLayer.java_accessStaticMember();
try {
Class<?> Student = Class.forName("com.milanac007.jnistudydemo.Student");
Field codeField = Student.getDeclaredField("code");
codeField.setAccessible(true);
int code = codeField.getInt(null);
Method versionMethod = Student.getDeclaredMethod("getVersion");
String version = (String)versionMethod.invoke(null);
Log.i(TAG, String.format("java_accessStaticMember执行后: Student.code= 0x%02x, version=%s", code,version));
} catch (Exception e) {
e.printStackTrace();
}
log:
10-30 18:20:35.996 18182-18182/com.milanac007.jnistudydemo I/JniLayer: code: 0xff
10-30 18:20:35.996 18182-18182/com.milanac007.jnistudydemo I/JniLayer: SetStaticIntField() code=0x0a
10-30 18:20:35.996 18182-18182/com.milanac007.jnistudydemo I/JniLayer: code: 0x0a
10-30 18:20:35.996 18182-18182/com.milanac007.jnistudydemo I/JniLayer: version :1.0.0.1
10-30 18:20:35.997 18182-18182/com.milanac007.jnistudydemo I/MainActivity: java_accessStaticMember执行后: Student.code= 0x0a, version=1.0.0.1
⚠️:
访问类的静态成员与访问类的实例成员相似:
GetStaticFieldID
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);
返回java类clazz的指定name和类型签名的字段的字段ID;
GetStaticField
NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz,
jfieldID fieldID);
获取指定静态字段ID的静态字段值;具体的类型参考如下:
SetStaticField
void SetStatic<type>Field(JNIEnv *env, jclass clazz,
jfieldID fieldID, NativeType value);
设置java对象的静态字段ID为fieldID的静态字段的值为value;
具体的类型参考如下:
GetStaticMethodID
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);
返回java类clazz的静态方法的ID;失败返回NULL;
CallStaticMethod
NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz,
jmethodID methodID, ...);
执行java类clazz的静态方法ID为methodID的静态方法;
具体类型如下:
CallStaticMethod Routine Name | Native Type |
---|---|
CallStaticVoidMethod() | void |
CallStaticObjectMethod() | |
CallStaticBooleanMethod() | jboolean |
CallStaticByteMethod() | jbyte |
CallStaticCharMethod() | jchar |
CallStaticShortMethod() | jshort |
CallStaticIntMethod() | jint |
CallStaticLongMethod() | jlong |
CallStaticFloatMethod() | jfloat |
CallStaticDoubleMethod() | jdouble |
CallNonvirtualMethod
CallNonvirtualMethod系列方法主要提供了访问指定类的超类的同名方法。
java代码:
public static native void java_CallNonvirtualMethod(Student instance);
本地代码:
void native_CallNonvirtualMethod(JNIEnv *env, jobject obj, jobject instance){
jclass cls = env->GetObjectClass(instance);
jmethodID toStringID1 = env->GetMethodID(cls, "toString", "()Ljava/lang/String;");
if(env->ExceptionOccurred()){
env->ExceptionDescribe();
env->ExceptionClear();
return;
}
jstring str1 = (jstring)env->CallObjectMethod(instance, toStringID1);
const char *p1 = env->GetStringUTFChars(str1, NULL);
logI(TAG, "child toString(): %s", p1);
jclass supperCls = env->GetSuperclass(cls);
jmethodID toStringID2 = env->GetMethodID(supperCls, "toString", "()Ljava/lang/String;");
if(env->ExceptionOccurred()){
env->ExceptionDescribe();
env->ExceptionClear();
return;
}
jstring str2 = (jstring)env->CallNonvirtualObjectMethod(instance, supperCls, toStringID2);
const char *p2 = env->GetStringUTFChars(str2, NULL);
logI(TAG, "supper toString(): %s", p2);
// 删除局部引用(jobject或jobject的子类才属于引用变量),允许VM释放被局部变量所引用的资源
env->DeleteLocalRef(cls);
env->ReleaseStringUTFChars(str1, p1);
env->DeleteLocalRef(str1);
env->DeleteLocalRef(supperCls);
env->ReleaseStringUTFChars(str2, p2);
env->DeleteLocalRef(str2);
}
测试代码:
JavaLayer.java_CallNonvirtualMethod(stu);
log:
10-30 18:20:35.997 18182-18182/com.milanac007.jnistudydemo I/JniLayer: child toString(): Student{id=1,name=李雷,sex=male}
10-30 18:20:35.997 18182-18182/com.milanac007.jnistudydemo I/JniLayer: supper toString(): Person{name=李雷,sex=male}
⚠️:
这个例子中,分别调用子类Student、父类Person的toString()。首先通过GetObjectClass()获取类对象。通过GetSuperclass()获取指定类的父类。
CallNonvirtualMethod
NativeType CallNonvirtual<type>Method(JNIEnv *env, jobject obj,jclass clazz, jmethodID methodID, ...);
CallNonvirtual Method 簇类和Call Method簇类是不同的。 Call Method根据对象的类来调用方法,而CallNonvirtual Method根据由clazz参数指定的类来调用方法,并从中获取方法ID。 方法ID必须从对象的真实类或其超类之一获得。
当想调用基类的某个方法时,只需要先通过GetSuperclass()获取基类,然后再获取基类的某个方法的ID,传入CallNonvirtualMethod即可。
具体的类型参考如下:
CallNonvirtualMethod Routine Name | Native Type |
---|---|
CallNonvirtualVoidMethod() | void |
CallNonvirtualObjectMethod() | jobject |
CallNonvirtualBooleanMethod() | jboolean |
CallNonvirtualByteMethod() | jbyte |
CallNonvirtualCharMethod() | jchar |
CallNonvirtualShortMethod() | jshort |
CallNonvirtualIntMethod() | jint |
CallNonvirtualLongMethod() | jlong |
CallNonvirtualFloatMethod() | jfloat |
CallNonvirtualDoubleMethod() | jdouble |
以上就是本文的全部内容,起一个抛砖引玉的作用。更详细的JNI函数介绍请参考:
JNI Functions
本文实例的全部代码已上传自github:
JNIStudyDemo
不足之处望指正。
最后
以上就是俏皮铃铛为你收集整理的JNI学习总结JNI简介JavaVM和JNIEnvJNI Types and Data StructuresJNI注册在代码中学习jni functions的全部内容,希望文章能够帮你解决JNI学习总结JNI简介JavaVM和JNIEnvJNI Types and Data StructuresJNI注册在代码中学习jni functions所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复