转载

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 %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展示了接口指针的组织结构。

JNI学习总结

图 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

JNI学习总结

其中,Java Type为Java代码层的对象类型,Native Type为Native层接收、操作的java对象的对应类型。

Reference Types

JNI包含一系列的对应于不同种类的Java对象的引用类型。 JNI引用类型按照Figure 3-1.所示的层次结构进行组织。

JNI学习总结

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静态注册的优缺点

优点 : 简单明了

缺点 : 必须遵循注册规则 名字过长 运行时查找效率低

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

JNI学习总结

从上述表格可以总结各种类型的类型签名:

  • 对于基本类型,类型签名就是它们的 首字母大写 ;特殊情况: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 %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。

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::

  1. GetStringUTFLength:获取utf-8编码的字符串的长度;
  2. 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的原始指针。

  1. 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指向的内存。

  1. 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);
复制代码
  1. 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]
复制代码

:warning:: 这个本地方法的作用是将给定的int数组的每个元素加1。

  1. GetArrayLength:返回给定的java数组中元素的个数。
jsize GetArrayLength(JNIEnv *env, jarray array);
复制代码
  1. 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对应如下表:

JNI学习总结

不管Java VM中如何表示布尔数组,GetBooleanArrayElements()始终返回指向jboolean的指针,每个字节表示一个元素 (注: typedef uint8_t jboolean; /* unsigned 8 bits */) 。 其他类型的所有数组都保证在内存中是连续的。

  1. 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会产生以下影响,如下表所示:

JNI学习总结

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]
复制代码

:warning::

  1. 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;
  2. NewByteArray: 原型:
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
复制代码

构建并返回指定长度、指定基本类型的java数组对象,失败返回NULL。 具体的函数分类可参见下表:

JNI学习总结

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.

具体的函数分类可参见下表:

JNI学习总结

与之相对应的是

void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array,
jsize start, jsize len, NativeType *buf);
复制代码

将基本数组array的一个区域(起始坐标start、长度len)复制到缓冲区buf。 具体的函数分类可参见下表:

JNI学习总结

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]
复制代码

:warning::

  1. 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()获取的。具体的类型参考下表:

JNI学习总结

SetField Routines

void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID,
NativeType value);
复制代码

设置java类的实例obj的指定ID的字段值。具体的类型参考下表:

JNI学习总结

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::

  1. 这里本地方法分别使用两种方式创建Student对象并赋值。方式一使用AllocObject创建java类的实例,再调用SetObjectField给字段赋值,并使用CallVoidMethod调用类方法。方式二是直接使用NewObject调用特定的构造方法创建实例并赋值。
  2. 在本地方法的实现内创建的jobject及其子类,最后使用完后主动调用DeleteLocalRef以通知VM回收。
  3. 在本地代码中调用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}]
复制代码

: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的静态字段值;具体的类型参考如下:

JNI学习总结

SetStaticField

void SetStatic<type>Field(JNIEnv *env, jclass clazz,
jfieldID fieldID, NativeType value);
复制代码

设置java对象的静态字段ID为fieldID的静态字段的值为value; 具体的类型参考如下:

JNI学习总结

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}
复制代码

: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

不足之处望指正。

原文  https://juejin.im/post/5dbaaa106fb9a02058480062
正文到此结束
Loading...