做过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正题。
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 %s/n", 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 failed/n"); 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如何将Java类型映射到本地C类型。
Table 3-1 描述Java基本类型及其与机器相关的本地代码的等价类型。
Table 3-1 Primitive Types and Native Equivalents
其中,Java Type为Java代码层的对象类型,Native Type为Native层接收、操作的java对象的对应类型。
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; 复制代码
方法和字段ID是常规的C指针类型:
struct _jfieldID; /* opaque structure */ typedef struct _jfieldID* jfieldID; /* field IDs */ struct _jmethodID; /* opaque structure */ typedef struct _jmethodID* jmethodID; /* method IDs */ 复制代码
jvalue联合类型被用作参数数组中的元素类型。 声明如下:
typedef union jvalue { jboolean z; jbyte b; jchar c; jshort s; jint i; jlong j; jfloat f; jdouble d; jobject l; } jvalue; 复制代码
当Java代码去执行一个native 方法时,虚拟机是怎么知道该调用 so 中的哪个方法呢?答案就是注册,通过注册,将指定的 native方法和 so 中对应的本地方法进行绑定,建立起函数映射表,从函数映射表中找到相应的本地方法。注册分为 静态注册 和 动态注册 两种。默认的实现方式即静态注册。
虚拟机VM加载so时,动态链接器根据名称来进行解析。通过名称对应规则,匹配并链接java中对应的native方法。规则如下: == Java_包名_类名_方法名 ==
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"表示。
优点 : 简单明了
缺点 : 必须遵循注册规则 名字过长 运行时查找效率低
原理:通过 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, ...); 复制代码
JNI使用Java VM的类型签名表示。 Table 3-2显示了这些类型签名。
Table 3-2 Java VM Type Signatures
从上述表格可以总结各种类型的类型签名:
举例,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 %s/n", 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 failed/n"); 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的第一个参数都为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 复制代码
:warning::
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的原始指针。
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指向的内存。
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); 复制代码
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 本文就不介绍了。
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] 复制代码
:warning:: 这个本地方法的作用是将给定的int数组的每个元素加1。
jsize GetArrayLength(JNIEnv *env, jarray array); 复制代码
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 */) 。 其他类型的所有数组都保证在内存中是连续的。
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后,原数组的每个元素也都加一了。 感兴趣的话可以试试另外两个参数。它们可以更好地控制内存管理,但使用应格外小心。
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] 复制代码
:warning::
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。 具体的函数分类可参见下表:
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] 复制代码
:warning::
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,以下三个方法较常用:
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主要有以下两个方法:
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不能是数组类型的引用。
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} 复制代码
:warning::
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}] 复制代码
:warning:: 这个例子的本地代码首先创建了一个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 复制代码
:warning:: 访问类的静态成员与访问类的实例成员相似:
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系列方法主要提供了访问指定类的超类的同名方法。
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} 复制代码
:warning:: 这个例子中,分别调用子类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
不足之处望指正。