HAL/JNI简明笔记(五)——JNI访问java中的属性和方法

来源:互联网 发布:淘宝店铺首页宝贝展示 编辑:程序博客网 时间:2024/05/29 17:36

前面的大多是java如何使用JNI提供的本地接口,还有JNI来访问JVM中的基本类型数据和字符串、数组这样的引用类型数据;现在说说JNI如何来访问JVM,即访问java的属性和方法,从实现原理来看jni和java的互相调用都离不开JVM这个环境。下例子即一些结论摘自网络,有些说法或名词可能不严谨,比如我说的jni可能就和本地接口是一个意思,实际上是本地接口通过jni来(在JVM环境)与java交互,但大体上不影响理解就行。


JNI访问java属性

属性的访问或设置操作分为静态属性和实例属性。静态变量也称为类变量(属性),在所有实例对象中共享同一份数据,可以直接通过类名.变量名来访问。实例变量也称为成员变量(属性),每个实例都拥有一份实例变量数据的拷贝,它们之间修改后的数据互不影响。

下面看一个例子都是基于用命名规则的JNI非stub架构,我们推荐stub架构的,但是此处我们只关注其JNI的函数体:

<span style="font-size:14px;">package com.study.jnilearn;    /**  * C/C++访问类的实例变量和静态变量  */  public class AccessField {            private native static void accessInstanceField(ClassField obj);            private native static void accessStaticField();        public static void main(String[] args) {          ClassField obj = new ClassField();          obj.setNum(10);          obj.setStr("Hello");                    // 本地代码访问和修改ClassField为中的静态属性num          accessStaticField();          accessInstanceField(obj);                    // 输出本地代码修改过后的值          System.out.println("In Java--->ClassField.num = " + obj.getNum());          System.out.println("In Java--->ClassField.str = " + obj.getStr());      }        static {          System.loadLibrary("AccessField");      }        }</span><span style="font-size: 18px;">  </span>


AccessField是程序的入口类,定义了两个native方法:accessInstanceField和accessStaticField,分别用于演示在本地代码中访问Java类中的实例变量和静态变量。其中accessInstaceField方法访问的是类的实例变量,所以该方法需要一个ClassField实例作为形参,用于访问该对象中的实例变量。

<span style="font-size:14px;">package com.study.jnilearn;    /**  * ClassField.java  * 用于本地代码访问和修改该类的属性  *  */  public class ClassField {        private static int num;      private String str;      public int getNum() {          return num;      }        public void setNum(int num) {          ClassField.num = num;      }        public String getStr() {          return str;      }        public void setStr(String str) {          this.str = str;      }  }  </span>


在本例中没有将实例变量和静态变量定义在程序入口类中,新建了一个ClassField的类来定义类的属性,目的是为了加深在C/C++代码中可以访问任意Java类中的属性。在这个类中定义了一个int类型的实例变量num,和一个java.lang.String类型的静态变量str。这两个变量会被本地代码访问和修改。

本地代码libAccessField.so的实现:

<span style="font-size:14px;"><span style="font-family:Arial;"><span style="line-height: 26px;">// AccessField.c    #include "com_study_jnilearn_AccessField.h"    /*  * Class:     com_study_jnilearn_AccessField  * Method:    accessInstanceField  * Signature: ()V  */  JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessInstanceField  (JNIEnv *env, jclass cls, jobject obj)  {      jclass clazz;      jfieldID fid;      jstring j_str;      jstring j_newStr;      const char *c_str = NULL;            // 1.获取ClassField类的Class引用      clazz = (*env)->GetObjectClass(env,obj);      if (clazz == NULL) {          return;      }       //获取ClassField类的str属性ID </span></span><span style="font-family: Arial; line-height: 26px;">    fid = (*env)->GetFieldID(env,clazz,"str", "Ljava/lang/String;");      if (clazz == NULL) {          return;      }            // 3. 获取实例变量str的值      j_str = (jstring)(*env)->GetObjectField(env,obj,fid);            // 4. 将unicode编码的java字符串转换成C风格字符串      c_str = (*env)->GetStringUTFChars(env,j_str,NULL);      if (c_str == NULL) {          return;      }      printf("In C--->ClassField.str = %s\n", c_str);      (*env)->ReleaseStringUTFChars(env, j_str, c_str);            // 5. 修改实例变量str的值      j_newStr = (*env)->NewStringUTF(env, "This is C String");      if (j_newStr == NULL) {          return;      }            (*env)->SetObjectField(env, obj, fid, j_newStr);            // 6.删除局部引用      (*env)->DeleteLocalRef(env, clazz);      (*env)->DeleteLocalRef(env, j_str);      (*env)->DeleteLocalRef(env, j_newStr);  }    /*  * Class:     com_study_jnilearn_AccessField  * Method:    accessStaticField  * Signature: ()V  */  JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessStaticField  (JNIEnv *env, jclass cls)  {      jclass clazz;      jfieldID fid;      jint num;            //1.获取ClassField类的Class引用      clazz = (*env)->FindClass(env,"com/study/jnilearn/ClassField");      if (clazz == NULL) {    // 错误处理          return;      }            //2.获取ClassField类静态变量num的属性ID      fid = (*env)->GetStaticFieldID(env, clazz, "num", "I");      if (fid == NULL) {          return;      }            // 3.获取静态变量num的值      num = (*env)->GetStaticIntField(env,clazz,fid);      printf("In C--->ClassField.num = %d\n", num);            // 4.修改静态变量num的值      (*env)->SetStaticIntField(env, clazz, fid, 80);            // 删除属部引用      (*env)->DeleteLocalRef(env,clazz);  }  </span></span>
运行结果如下:


上例main为入口,先初始化静态int变量值为10,实例变量String为“Hello”,调用本地接口在JNI中去访问这两个属性并打印,输出与初始化一致,然后在JNI接口中重新设置这两个属性,回到java中再次打印,与JNI修改后的两个属性值一致,

1、实例属性访问解析

在main中访问实例属性的jni接口是accessInstanceField(ClassField obj),在这个接口中访问及修改的步骤如下:

1)获取class引用

实例的访问需要传入java的实例参数,然后调用jclass (*GetObjectClass)(JNIEnv*, jobject)获得在JNI中的class引用。

2)获取实例属性ID

jfieldID实例属性获取ID只用 (*GetFieldID)(JNIEnv*, jclass, const char* name, const char* sig),参数一为JNI函数表指针,参数二为上一步骤获取的class引用,参数三为在java实例中属性名(即变量名),参数四是这个参数对应的signature即参数类型对应的符号串描述符,此处是String,其对应sig为"Ljava/lang/String;",具体规则见我的另一个文章HAL/JNI简明笔记(三)

3)获取实例属性值

调用JNI函数指针jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID)来获取String类型属性值,参数二为java传的实例参数,参数三为上一步骤获取的实例属性ID。因为String是引用类型,所以采用GetObjectField;如果使用的是基本类型,那么就该使用j<type>  Get<type>Field(JNIEnv*, jobject, jfieldID),包含8中基本类型,其返回值对应于type,例如jint (*GetIntField)(JNIEnv*, jobject, jfieldID);。原型如下:

<span style="font-size:14px;">    jobject     (*GetObjectField)(JNIEnv*, jobject, jfieldID);    jboolean    (*GetBooleanField)(JNIEnv*, jobject, jfieldID);    jbyte       (*GetByteField)(JNIEnv*, jobject, jfieldID);    jchar       (*GetCharField)(JNIEnv*, jobject, jfieldID);    jshort      (*GetShortField)(JNIEnv*, jobject, jfieldID);    jint        (*GetIntField)(JNIEnv*, jobject, jfieldID);    jlong       (*GetLongField)(JNIEnv*, jobject, jfieldID);    jfloat      (*GetFloatField)(JNIEnv*, jobject, jfieldID);    jdouble     (*GetDoubleField)(JNIEnv*, jobject, jfieldID);</span>
4)修改实例属性值

jstring (*NewStringUTF)(JNIEnv*, const char*)获取java中的String,因为后面设置需要这个参数,而String是引用类型,但是jni本身最多只能提供8种基本类型,所以需要通过JNI函数表中的适当函数来间接获取;再调用void (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject)来设置String属性值(参数三为待修改的实例属性ID,参数四为待设置的引用类型的值),这样java再打印这个“str”实例属性时值就会变了。如果是8种基本类型,那么就调用其对应的函数,例如对于Boolean类型:void (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean),其基本命名为void        (*Set<type>Field)(JNIEnv*, jobject, jfieldID, j<type>)。原型如下:

<span style="font-size:14px;">    void        (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject);    void        (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean);    void        (*SetByteField)(JNIEnv*, jobject, jfieldID, jbyte);    void        (*SetCharField)(JNIEnv*, jobject, jfieldID, jchar);    void        (*SetShortField)(JNIEnv*, jobject, jfieldID, jshort);    void        (*SetIntField)(JNIEnv*, jobject, jfieldID, jint);    void        (*SetLongField)(JNIEnv*, jobject, jfieldID, jlong);    void        (*SetFloatField)(JNIEnv*, jobject, jfieldID, jfloat);    void        (*SetDoubleField)(JNIEnv*, jobject, jfieldID, jdouble);</span>
5)释放本地引用

因为JVM引用表最大支持512个,防止溢出崩溃,在同一个JNI函数中引用完了需要及时的释放掉,jobject以及子类属于引用变量,会占用引用表的空间(详见HAL/JNI简明笔记(四))局部应用会在函数返回时由GC回收,只是安全考虑。我们的例子中为了访问实例属性,过程中利用JNI函数表间接获取的class引用,String引用需要及时放掉,释放引用的函数原型void  (*DeleteLocalRef)(JNIEnv*, jobject)。


2、静态属性访问解析

在main中访问静态属性的jni接口是void accessStaticField(),没有任何参数,因为是静态属性,在java中以【类名.静态属性名】来访问,在JNI中还是这个思路:

1)获取类的引用

因为访问静态属性,不需要实例的参数,直接找class然后访问其静态属性,jclass (*FindClass)(JNIEnv*, const char* name)获取对应于java类的在jni中的引用,此处name为类的描述符,如例子中为“com/study/jnilearn/ClassField”即表示找com.study.jnilearn包中的ClassField类。

2)获取静态属性ID

获取静态属性ID只用jfieldID (*GetStaticFieldID)(JNIEnv*, jclass, const char* name, const char* sig),要注意与实例属性ID获取函数GetFieldID不同,(*env)->GetStaticFieldID(env, clazz, "num", "I");表示获取clazz类中类型为int的静态变量"num"的ID。

3)获取静态属性值

静态属性值的访问与实例属性类似,只是函数名中多了个Static词。比如例子中获取静态int类型的属性num,jint (*GetStaticIntField)(JNIEnv*, jclass, jfieldID),参数二为获取的类引用,参数三为获取的静态属性ID。类似的函数名为j<type> (*GetStatic<type>Field)(JNIEnv*, jclass, jfieldID),其中<type>既可以是8中基本类型,也可以是应用类型GetStaticObjectField。原型如下:

<span style="font-size:14px;">    jobject     (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID);    jboolean    (*GetStaticBooleanField)(JNIEnv*, jclass, jfieldID);    jbyte       (*GetStaticByteField)(JNIEnv*, jclass, jfieldID);    jchar       (*GetStaticCharField)(JNIEnv*, jclass, jfieldID);    jshort      (*GetStaticShortField)(JNIEnv*, jclass, jfieldID);    jint        (*GetStaticIntField)(JNIEnv*, jclass, jfieldID);    jlong       (*GetStaticLongField)(JNIEnv*, jclass, jfieldID);    jfloat      (*GetStaticFloatField)(JNIEnv*, jclass, jfieldID);    jdouble     (*GetStaticDoubleField)(JNIEnv*, jclass, jfieldID);</span>

4)修改静态属性值

例子中修改静态int属性num值用void (*SetStaticIntField)(JNIEnv*, jclass, jfieldID, jint),注意其中的最后个参数是写入值为基本类型,直接写入就行,不同于引用类型要用env间接获取。类似的函数名为void (*SetStatic<type>Field)(JNIEnv*, jclass, jfieldID, j<type>),同时包含SetStaticObjectField。原型如下:

<span style="font-size:14px;">    void        (*SetStaticObjectField)(JNIEnv*, jclass, jfieldID, jobject);    void        (*SetStaticBooleanField)(JNIEnv*, jclass, jfieldID, jboolean);    void        (*SetStaticByteField)(JNIEnv*, jclass, jfieldID, jbyte);    void        (*SetStaticCharField)(JNIEnv*, jclass, jfieldID, jchar);    void        (*SetStaticShortField)(JNIEnv*, jclass, jfieldID, jshort);    void        (*SetStaticIntField)(JNIEnv*, jclass, jfieldID, jint);    void        (*SetStaticLongField)(JNIEnv*, jclass, jfieldID, jlong);    void        (*SetStaticFloatField)(JNIEnv*, jclass, jfieldID, jfloat);    void        (*SetStaticDoubleField)(JNIEnv*, jclass, jfieldID, jdouble);</span>

5)删除局部引用

删除局部class引用


访问变量小结

1、访问和修改实例变量操作步聚:

   1>、调用GetObjectClass函数获取实例对象的Class引用

   2>、调用GetFieldID函数获取Class引用中某个实例变量的ID

   3>、调用GetXXXField函数获取变量的值,需要传入实例变量所属对象和变量ID

   4>、调用SetXXXField函数修改变量的值,需要传入实例变量所属对象、变量ID和变量的值

2、访问和修改静态变量操作步聚:
   1>、调用FindClass函数获取类的Class引用

   2>、调用GetStaticFieldID函数获取Class引用中某个静态变量ID

   3>、调用GetStaticXXXField函数获取静态变量的值,需要传入变量所属Class的引用和变量ID

   4>、调用SetStaticXXXField函数设置静态变量的值,需要传入变量所属Class的引用、变量ID和变量的值



JNI访问java方法


本地代码访问还是分为静态方法和实例方法还是从例子说起:

<span style="font-size:14px;">package com.study.jnilearn;/** * AccessMethod.java * 本地代码访问类的实例方法和静态方法 * @author yangxin */public class AccessMethod {public static native void callJavaStaticMethod(); public static native void callJavaInstaceMethod();public static void main(String[] args) {callJavaStaticMethod();callJavaInstaceMethod();}static {System.loadLibrary("AccessMethod");}}package com.study.jnilearn;/** * ClassMethod.java * 用于本地代码调用 * @author yangxin */public class ClassMethod {private static void callStaticMethod(String str, int i) {System.out.format("ClassMethod::callStaticMethod called!-->str=%s," +" i=%d\n", str, i);}private void callInstanceMethod(String str, int i) {System.out.format("ClassMethod::callInstanceMethod called!-->str=%s, " +"i=%d\n", str, i);}}</span>

com.study.jnilearn包中有AccessFiled和ClassMethod两个类,前者含main入口,后者为本地方法要访问的静态方法和实例方法,对应的本地方法如下:
<span style="font-size:14px;">// AccessMethod.c#include "com_study_jnilearn_AccessMethod.h"/* * Class:     com_study_jnilearn_AccessMethod * Method:    callJavaStaticMethod * Signature: ()V */JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaStaticMethod(JNIEnv *env, jclass cls){    jclass clazz = NULL;    jstring str_arg = NULL;    jmethodID mid_static_method;    // 1、从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象    clazz =(*env)->FindClass(env,"com/study/jnilearn/ClassMethod");    if (clazz == NULL) {        return;    }    // 2、从clazz类中查找callStaticMethod方法    mid_static_method = (*env)->GetStaticMethodID(env,clazz,"callStaticMethod","(Ljava/lang/String;I)V");    if (mid_static_method == NULL) {        printf("找不到callStaticMethod这个静态方法。");        return;    }    // 3、调用clazz类的callStaticMethod静态方法    str_arg = (*env)->NewStringUTF(env,"我是静态方法");    (*env)->CallStaticVoidMethod(env,clazz,mid_static_method, str_arg, 100);    // 删除局部引用    (*env)->DeleteLocalRef(env,clazz);    (*env)->DeleteLocalRef(env,str_arg);}/* * Class:     com_study_jnilearn_AccessMethod * Method:    callJavaInstaceMethod * Signature: ()V */JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaInstaceMethod(JNIEnv *env, jclass cls){    jclass clazz = NULL;    jobject jobj = NULL;    jmethodID mid_construct = NULL;    jmethodID mid_instance = NULL;    jstring str_arg = NULL;    // 1、从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象    clazz = (*env)->FindClass(env, "com/study/jnilearn/ClassMethod");    if (clazz == NULL) {        printf("找不到'com.study.jnilearn.ClassMethod'这个类");        return;    }    // 2、获取类的默认构造方法ID    mid_construct = (*env)->GetMethodID(env,clazz, "<init>","()V");    if (mid_construct == NULL) {        printf("找不到默认的构造方法");        return;    }    // 3、查找实例方法的ID    mid_instance = (*env)->GetMethodID(env, clazz, "callInstanceMethod", "(Ljava/lang/String;I)V");    if (mid_instance == NULL) {           return;    }    // 4、创建该类的实例    jobj = (*env)->NewObject(env,clazz,mid_construct);    if (jobj == NULL) {        printf("在com.study.jnilearn.ClassMethod类中找不到callInstanceMethod方法");        return;    }    // 5、调用对象的实例方法    str_arg = (*env)->NewStringUTF(env,"我是实例方法");    (*env)->CallVoidMethod(env,jobj,mid_instance,str_arg,200);    // 删除局部引用    (*env)->DeleteLocalRef(env,clazz);    (*env)->DeleteLocalRef(env,jobj);    (*env)->DeleteLocalRef(env,str_arg);}</span>
运行结果:

静态方法调用为类名.方法名,而实例由于在源java中并没有实例化,需要在JNI中通过JNI函数来实例化才能调用实例方法。

1、静态方法解析

1)获取类引用

通过jclass (*FindClass)(JNIEnv*, const char*)来获取对应的class引用,作为后面使用方法的参数。

2)获取静态方法ID

通过jmethodID   (*GetStaticMethodID)(JNIEnv*, jclass, const char* name, const char* sig)来获取method ID,参数三为方法名,参数四为参数描述符,参数四的不同描述符代表不同的参数,这样就区别开了重载函数,他们返回的jmethodID不同

3)调用静态方法

通过调用void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...)执行,参数三为上一步骤获取的method ID,省略号后即变参数为对应于java中静态方法的参数,例子中(*env)->CallStaticVoidMethod(env,clazz,mid_static_method, str_arg, 100),省略号变参数分别为jstring和int,对应于callStaticMethod(String str, int i)的两个参数,由于参数jstring是应用类型,必须间接通过str_arg = (*env)->NewStringUTF(env,"我是静态方法")来获取。

NOTE:

调用静态方法命名规律 j<type> (*CallStatic<Type>Method)(JNIEnv*, jclass, jmethodID, ...)

根据不同的返回值有不同的函数,<type>代表8种基本类型加上void加上object,如jint (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...),具体如下

<span style="font-size:14px;">    <span style="white-space:pre"></span>jobject     (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...);boolean    (*CallStaticBooleanMethod)(JNIEnv*, jclass, jmethodID, ...);jbyte       (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);jchar       (*CallStaticCharMethod)(JNIEnv*, jclass, jmethodID, ...);jshort      (*CallStaticShortMethod)(JNIEnv*, jclass, jmethodID, ...);jint        (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...);jlong       (*CallStaticLongMethod)(JNIEnv*, jclass, jmethodID, ...);jfloat      (*CallStaticFloatMethod)(JNIEnv*, jclass, jmethodID, ...);jdouble     (*CallStaticDoubleMethod)(JNIEnv*, jclass, jmethodID, ...);void        (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);</span>

对于同一种返回值,jni也提供了3中可变参数类型:可变参列表,valist,const,jvalue*,以int为例如下

<span style="font-size:14px;">    jint        (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...);    jint        (*CallStaticIntMethodV)(JNIEnv*, jclass, jmethodID, va_list);    jint        (*CallStaticIntMethodA)(JNIEnv*, jclass, jmethodID, jvalue*);</span>

4)释放局部引用

安全考虑,释放不用的jobject及其之类的引用,局部引用在本地函数返回后会自动回收。

2、实例方法解析

上述例子中,由于本地接口调用时,还未实例化,需要在用jni函数实例化,才能调用实例方法。

1)获取类应用

与静态方法一样

2)获取构造方法ID

后面实例化需要构造方法,jmethodID (*GetMethodID)(JNIEnv*, jclass, const char* name, const char* sig);用法与获取静态方法methodID一样。本例中GetMethodID(env,clazz, "<init>","()V"),就是获取java中的与类名同名的默认构造methodID,默认构造方法name用“<init>”表示,默认构造方法无输入参数,无返回参数。

3)获取实例方法ID

获取实例方法通过jmethodID (*GetMethodID)(JNIEnv*, jclass, const char* name, const char* sig);不同于静态方法带有Static,使用方法与静态方法类似。

4)类的实例化

类的实例化需要构造函数,上面步骤2已经获取,通过JNI函数jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...);实例化,其中参数三为构造函数的mthodID,后面为传递给java构造函数的参数,由于构造函数既可以默认,也可以自己定义或者重载构造函数,省略号变参列表就是传递给带参数的构造函数用的。同样共有3个参数的NewObject

<span style="font-size:14px;">    jobject     (*NewObject)(JNIEnv*, jclass, jmethodID, ...);    jobject     (*NewObjectV)(JNIEnv*, jclass, jmethodID, va_list);    jobject     (*NewObjectA)(JNIEnv*, jclass, jmethodID, jvalue*);</span>

5)调用实例化的方法

调用实例化的方法void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...),此类函数与调用静态方法唯一的不同就是不带Static字段,其余一样有8种基本类型加上void加上object共10中类型的调用实例方法,每种返回值函数有3种参数传入方式。

6)释放局部引用

与调用静态方法类似


访问方法小结:

1、调用静态方法使用CallStaticXXXMethod/V/A函数,XXX代表返回值的数据类型。如:CallStaticIntMethod

2、调用实例方法使用CallXXXMethod/V/A函数,XXX代表返回的数据类型,如:CallIntMethod

3、获取一个实例方法的ID,使用GetMethodID函数,传入方法名称和方法签名

4、获以一个静态方法的ID,使用GetStaticMethodID函数,传入方法名称和方法签名

5、获取构造方法ID,方法名称使用"<init>"

6、获取一个类的Class实例,使用FindClass函数,传入类描述符。JVM会从classpath目录下开始搜索。

7、创建一个类的实例,使用NewObject函数,传入Class引用和构造方法ID

8、删除局部变量引用,使用DeleteLocalRef,传入引用变量

9、方法签名格式:(形参参数列表)返回值类型。注意:形参参数列表之间不需要用空格或其它字符分隔

10、类描述符格式:L包名路径/类名;,包名之间用/分隔。如:Ljava/lang/String;

11、调用GetMethodID获取方法ID和调用FindClass获取Class实例后,要做异常判断



另要注意的是,你有没有发现不管是实例属性还是静态属性,前面都有个private限制,就是说这些属性只能在该类内部访问,但是为什么本地代码可以通过JVM访问,说明了一点,jni是直接操作JVM的数据结构,而JVM数据结构是通过java来体现,修饰符只对java有效,对于直接操作JVM数据结构的本地代码无效,也就是说本地代码可以通过JNI函数访问非public限定的属性和方法。



感谢如下的文章,我的例子大部分借用

ref:  xyang0917     JNI/NDK开发指南

0 0
原创粉丝点击