Android NDK开发-JNI基础篇
1. NDK
NDK 提供了一份稳定、功能有限的 API 头文件声明,Google 明确声明该 API 是稳定的,在后续所有版本中都稳定支持当前发布的 API。从该版本的 NDK 中看出,这些 API 支持的功能非常有限,包含有:C 标准库(libc)、标准数学库(libm)、压缩库(libz)、Log 库(liblog)。
NDK的优点:
- 代码的保护。由于 apk 的 java 层代码很容易被反编译,而 C/C++ 库反汇难度较大。
- 可以方便地使用现存的开源库。大部分现存的开源库都是用 C/C++ 代码编写的。
- 提高程序的执行效率。将要求高性能的应用逻辑使用 C 开发,从而提高应用程序的执行效率。
- 便于移植。用 C/C++ 写得库可以方便在其他的嵌入式平台上再次使用。
2. JNI

3. JNI 与 NDK 区别
- JNI:JNI是一套编程接口,用来实现Java代码与本地的C/C++代码进行交互;
- NDK: NDK是Google开发的一套开发和编译工具集,可以生成动态链接库,主要用于Android的JNI开发;
4. JNI 作用
- 扩展:JNI扩展了JVM能力,驱动开发,例如开发一个wifi驱动,可以将手机设置为无限路由;
- 高效: 本地代码效率高,游戏渲染,音频视频处理等方面使用JNI调用本地代码,C语言可以灵活操作内存;
- 复用: 在文件压缩算法 7zip开源代码库,机器视觉 OpenCV开放算法库等方面可以复用C平台上的代码,不必在开发一套完整的Java体系,避免重复发明轮子;
- 特殊: 产品的核心技术一般也采用JNI开发,不易破解;
JNI在Android中作用:
JNI可以调用本地代码库(即C/C++代码),并通过 Dalvik 虚拟机与应用层和应用框架层进行交互,Android中JNI代码主要位于应用层和应用框架层;
- 应用层: 该层是由JNI开发,主要使用标准JNI编程模型;
- 应用框架层: 使用的是Android中自定义的一套JNI编程模型,该自定义的JNI编程模型弥补了标准JNI编程模型的不足;
5. JNI数据类型
5.1 基本数据类型
Java类型 | 本地类型 | 描述 |
boolean | jboolean | C/C++8位整型 |
byte | jbyte | C/C++带符号的8位整型 |
char | jchar | C/C++无符号的16位整型 |
short | jshort | C/C++带符号的16位整型 |
int | jint | C/C++带符号的32位整型 |
long | jlong | C/C++带符号的64位整型e |
float | jfloat | C/C++32位浮点型 |
double | jdouble | C/C++64位浮点型 |
5.2 引用数据类型
Java类型 | 本地类型 | 描述 |
Object | jobject | 任何Java对象,或者没有对应java类型的对象 |
Class | jclass | Class对象 |
String | jstring | 字符串对象 |
Object[] | jobjectArray | 任何对象的数组 |
boolean[] | jbooleanArray | 布尔型数组 |
byte[] | jbyteArray | 比特型数组 |
char[] | jcharArray | 字符型数组 |
short[] | jshortArray | 短整型数组 |
int[] | jintArray | 整型数组 |
long[] | jlongArray | 长整型数组 |
float[] | jfloatArray | 浮点型数组 |
double[] | jdoubleArray | 双浮点型数组 |
需要注意的是,
- 1)引用类型不能直接在 Native 层使用,需要根据 JNI 函数进行类型的转化后,才能使用;
- 2)多维数组(含二维数组)都是引用类型,需要使用 jobjectArray 类型存取其值;
例如,二维整型数组就是指向一位数组的数组,其声明使用方式如下:
//获得一维数组的类引用,即jintArray类型
jclass intArrayClass = env->FindClass(“[I”);
//构造一个指向jintArray类一维数组的对象数组,该对象数组初始大小为length,类型为 jsize
jobjectArray obejctIntArray = env->NewObjectArray(length ,intArrayClass , NULL);
|
6.JNI 描述符
6.1域描述符
1)基本类型描述符
下面是基本的数据类型的描述符,除了 boolean 和 long 类型分别是 Z 和 J 外,其他的描述符对应的都是Java类型名的大写首字母。另外,void 的描述符为 V
Java 类型 | 符号 |
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
void | V |
objects对象 | Lfully-qualified-class-name;L类名 |
Arrays数组 | [array-type [数组类型 |
methods方法 | (argument-types)return-type(参数类型)返回类型 |
2)引用类型描述符
一般引用类型描述符的规则如下,注意不要丢掉“;”
L + 类描述符 + ;
|
如,String 类型的域描述符为:
Ljava/lang/String;
|
数组的域描述符特殊一点,如下,其中有多少级数组就有多少个“[”,数组的类型为类时,则有分号,为基本类型时没有分号
[ + 其类型的域描述符
|
java 数组类型 | 描述符 |
---|---|
int[] | [I |
double[] | [D |
String[] | [Ljava/lang/String; |
Object[] | [Ljava/lang/Object; |
int[][] | [[I |
double[][] | [[D |
对应在 jni.h 获取 Java 的字段的 native 函数如下,name为 Java 的字段名字,sig 为域描述符
//C
jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID);
//C++
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
{ return functions->GetFieldID(this, clazz, name, sig); }
jobject GetObjectField(jobject obj, jfieldID fieldID)
{ return functions->GetObjectField(this, obj, fieldID); }
|
具体使用,后面会讲到。
6.2 类描述符
类描述符是类的完整名称:包名+类名,java 中包名用 . 分割,jni 中改为用 / 分割
如,Java 中 java.lang.String 类的描述符为 java/lang/String
native 层获取 Java 的类对象,需要通过 FindClass() 函数获取, jni.h 的函数定义如下:
//C
jclass (*FindClass)(JNIEnv*, const char*);
//C++
jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }
|
字符串参数就是类的引用类型描述符,如 Java 对象 cn.cfanr.jni.JniTest,对应字符串为Lcn/cfanr/jni/JniTest; 如下:
jclass jclazz = env->FindClass(“Lcn/cfanr/jni/JniTest;”);
|
详细用法的例子,后面会讲到。
6.3 方法描述符
方法描述符需要将所有参数类型的域描述符按照声明顺序放入括号,然后再加上返回值类型的域描述符,其中没有参数时,不需要括号,如下规则:
方法描述 | java 方法 |
---|---|
“()Ljava/lang/String;” | String f(); |
“(ILjava/lang/Class;)J” | long f(int i, Class c); |
“([B)V” | String(byte[] bytes); |
(II)I | int sum(int a, int b) |
([Ljava/lang/String;)V | void main(String[] args) |
另外,对应在 jni.h 获取 Java 方法的 native 函数如下,其中 jclass 是获取到的类对象,name 是 Java 对应的方法名字,sig 就是上面说的方法描述符
//C
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//C++
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
{ return functions->GetMethodID(this, clazz, name, sig); }
|
不过在实际编程中,如果使用 javah 工具来生成对应的 native 代码,就不需要手动编写对应的类型转换了。
7. JNI里使用Java里的数组和对象
7.1 使用数组
JNI通过JNIEnv提供的操作Java数组的功能。它提供了两个函数:一个是操作java的简单型数组的,另一个是操作对象类型数组的。
因为速度的原因,简单类型的数组作为指向本地类型的指针暴露给本地代码。因此,它们能作为常规的数组存取。这个指针是指向实际的Java数组或者Java数组的拷贝的指针。另外,数组的布置保证匹配本地类型。
为了存取Java简单类型的数组,你就要要使用GetXXXArrayElements函数(见下表),XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。
函数 | Java数组类型 | 本地类型 |
GetBooleanArrayElements | jbooleanArray | jboolean |
GetByteArrayElements | jbyteArray | jbyte |
GetCharArrayElements | jcharArray | jchar |
GetShortArrayElements | jshortArray | jshort |
GetIntArrayElements | jintArray | jint |
GetLongArrayElements | jlongArray | jlong |
GetFloatArrayElements | jfloatArray | jfloat |
GetDoubleArrayElements | jdoubleArray | jdouble |
JNI数组存取函数
当你对数组的存取完成后,要确保调用相应的ReleaseXXXArrayElements函数,参数是对应Java数组和GetXXXArrayElements返回的指针。如果必要的话,这个释放函数会复制你做的任何变化(这样它们就反射到java数组),然后释放所有相关的资源。
为了使用java对象的数组,你必须使用GetObjectArrayElement函数和SetObjectArrayElement函数,分别去get,set数组的元素。GetArrayLength函数会返回数组的长度。
举一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ////输入一个数组,这里输入的是一个Boolean类型的数组 JNIEXPORT void JNICALL Java_com_aoaoyi_jnidemo_ChangeMethodFromJni_setArray (JNIEnv *env, jobject, jbooleanArray ba) { jboolean* pba = (env)->GetBooleanArrayElements(ba, 0 ); jsize len = (env)->GetArrayLength(ba); int i=0; // change even array elements for( i=0; i < len; i+=2 ) { pba[i] = JNI_FALSE; printf( "boolean = %s\n", (pba[i]==JNI_TRUE ? "true" : "false") ); } (env)->ReleaseBooleanArrayElements(ba, pba, 0 ); } |
7.2 使用对象
JNI提供的另外一个功能是在本地代码中使用Java对象。通过使用合适的JNI函数,你可以创建Java对象,get、set 静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或方法的ID是任何处理域和方法的函数的必须参数。
下表列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID或jmethodID。
函数 | 描述 |
GetFieldID | 得到一个实例的域的ID |
GetStaticFieldID | 得到一个静态的域的ID |
GetMethodID | 得到一个实例的方法的ID |
GetStaticMethodID | 得到一个静态方法的ID |
※域和方法的函数
如果你有了一个类的实例,它就可以通过方法GetObjectClass得到,或者如果你没有这个类的实例,可以通过FindClass得到。
下面举例,看看如何通过使用数组和对象,从C++中的获取到Java中的DiskInfo 类对象,并返回一个DiskInfo数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | ////返回一个结构数组,返回一个硬盘信息的结构数组 JNIEXPORT jobjectArray JNICALL Java_com_aoaoyi_jnidemo_ChangeMethodFromJni_getStructArray (JNIEnv *env, jobject _obj) { //申明一个object数组 jobjectArray args = 0; //数组大小 jsize len = 5; //获取object所属类,一般为ava/lang/Object就可以了 jclass objClass = (env)->FindClass("java/lang/Object"); //新建object数组 args = (env)->NewObjectArray(len, objClass, 0); /* 下面为获取到Java中对应的实例类中的变量*/ //获取Java中的实例类 jclass objectClass = (env)->FindClass("com/aoaoyi/jnidemo/DiskInfo"); //获取类中每一个变量的定义 //名字 jfieldID str = (env)->GetFieldID(objectClass,"name","Ljava/lang/String;"); //序列号 jfieldID ival = (env)->GetFieldID(objectClass,"serial","I"); //给每一个实例的变量付值,并且将实例作为一个object,添加到objcet数组中 for(int i=0; i < len; i++ ) { //给每一个实例的变量付值 jstring jstr = WindowsTojstring(env,"我的磁盘名字是 D:"); //(env)->SetObjectField(_obj,str,(env)->NewStringUTF("my name is D:")); (env)->SetObjectField(_obj,str,jstr); (env)->SetShortField(_obj,ival,10); //添加到objcet数组中 (env)->SetObjectArrayElement(args, i, _obj); } //返回object数组 return args; } |
8. JNIEnv 分析
JNIEnv 是 jni.h 文件最重要的部分,它的本质是指向函数表指针的指针(JavaVM也是),函数表里面定义了很多 JNI 函数,同时它也是区分 C 和 C++环境的(由上面介绍描述符时也可以看到),在 C 语言环境中,JNIEnv 是strut JNINativeInterface*
的指针别名。
1 2 3 4 5 6 7 8 9 10 | struct _JNIEnv; struct _JavaVM; typedef const struct JNINativeInterface* C_JNIEnv; #if defined(__cplusplus) typedef _JNIEnv JNIEnv; //C++中的 JNIEnv 类型 typedef _JavaVM JavaVM; #else typedef const struct JNINativeInterface* JNIEnv; //C语言的 JNIEnv 类型 typedef const struct JNIInvokeInterface* JavaVM; #endif |
8.1 JNIEnv 特点
- JNIEnv 是一个指针,指向一组 JNI 函数,通过这些函数可以实现 Java 层和 JNI 层的交互,就是说通过 JNIEnv 调用 JNI 函数可以访问 Java 虚拟机,操作 Java 对象;
- 所有本地函数都会接收 JNIEnv 作为第一个参数;(不过 C++ 的JNI 函数已经对 JNIEnv 参数进行了封装,不用写在函数参数上)
- 用作线程局部存储,不能在线程间共享一个 JNIEnv 变量,也就是说 JNIEnv 只在创建它的线程有效,不能跨线程传递;相同的 Java 线程调用本地方法,所使用的 JNIEnv 是相同的,一个 native 方法不能被不同的 Java 线程调用;
8.2 JavaEnv 和 JavaVM 的关系
- 1)每个进程只有一个 JavaVM(理论上一个进程可以拥有多个 JavaVM 对象,但 Android 只允许一个),每个线程都会有一个 JNIEnv,大部分 JNIAPI 通过 JNIEnv 调用;也就是说,JNI 全局只有一个 JavaVM,而可能有多个 JNIEnv;
- 2)一个 JNIEnv 内部包含一个 Pointer,Pointer 指向 Dalvik 的 JavaVM 对象的 Function Table,JNIEnv 内部的函数执行环境来源于 Dalvik 虚拟机;
- 3)Android 中每当一个Java 线程第一次要调用本地 C/C++ 代码时,Dalvik 虚拟机实例会为该 Java 线程产生一个 JNIEnv 指针;
- 4)Java 每条线程在和 C/C++ 互相调用时,JNIEnv 是互相独立,互不干扰的,这样就提升了并发执行时的安全性;
- 5)当本地的 C/C++ 代码想要获得当前线程所想要使用的 JNIEnv 时,可以使用 Dalvik VM 对象的 JavaVM jvm->GetEnv()方法,该方法会返回当前线程所在的 JNIEnv;
- 6)Java 的 dex 字节码和 C/C++ 的 .so 同时运行 Dalvik VM 之内,共同使用一个进程空间;
8.3 C 语言的 JNIEnv
由上面代码可知,C 语言的JNIEnv 就是const struct JNINativeInterface*
,而 JNIEnv* env
就等价于JNINativeInterface** env
,env 实际是一个二级指针,所以想要得到 JNINativeInterface 结构体中定义的函数指针,就需要先获取 JNINativeInterface 的一级指针对象env,然后才能通过一级指针对象调用 JNI 函数,例如:
`(*env)->NewStringUTF(env, “hello”)`
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | struct JNINativeInterface { void* reserved0; void* reserved1; void* reserved2; void* reserved3; jint (*GetVersion)(JNIEnv *); jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*, jsize); jclass (*FindClass)(JNIEnv*, const char*); jmethodID (*FromReflectedMethod)(JNIEnv*, jobject); jfieldID (*FromReflectedField)(JNIEnv*, jobject); /* spec doesn't show jboolean parameter */ jobject (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean); jclass (*GetSuperclass)(JNIEnv*, jclass); jboolean (*IsAssignableFrom)(JNIEnv*, jclass, jclass); /* spec doesn't show jboolean parameter */ jobject (*ToReflectedField)(JNIEnv*, jclass, jfieldID, jboolean); //……定义了一系列关于 Java 操作的函数 } |
8.4 C++的 JNIEnv
由typedef _JNIEnv JNIEnv;
可知,C++的 JNIEnv 是 _JNIEnv 结构体,而 _JNIEnv 结构体定义了 JNINativeInterface 的结构体指针,内部定义的函数实际上是调用 JNINativeInterface 的函数,所以C++的 env 是一级指针,调用时不需要加 env 作为函数的参数,例如:env->NewStringUTF(env, "hello")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | struct _JNIEnv { /* do not rename this; it does not seem to be entirely opaque */ const struct JNINativeInterface* functions; #if defined(__cplusplus) jint GetVersion() { return functions->GetVersion(this); } jclass DefineClass(const char *name, jobject loader, const jbyte* buf, jsize bufLen) { return functions->DefineClass(this, name, loader, buf, bufLen); } jclass FindClass(const char* name) { return functions->FindClass(this, name); } jmethodID FromReflectedMethod(jobject method) { return functions->FromReflectedMethod(this, method); } jfieldID FromReflectedField(jobject field) { return functions->FromReflectedField(this, field); } jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic) { return functions->ToReflectedMethod(this, cls, methodID, isStatic); } jclass GetSuperclass(jclass clazz) { return functions->GetSuperclass(this, clazz); } //…… } |
9. JNI 的两种注册方式

9.1 静态注册
原理:根据函数名建立 Java 方法和 JNI 函数的一一对应关系。
实现方式1:
- 先编写 Java 的 native 方法;
- 然后用 javah 工具生成对应的头文件,执行命令
javah packagename.classname
可以生成由包名加类名命名的 jni 层头文件,或执行命名javah -o custom.h packagename.classname
,其中 custom.h 为自定义的文件名; - 实现 JNI 里面的函数,再在Java中通过System.loadLibrary加载 so 库即可;
在AndroidStudio中编译后,进入项目的目录app/build/intermediates/classes/debug下,运行如下命令:
1 | javah -d cpp -jni -classpath ..\..\build\intermediates\classes\debug com.aoaoyi.poker.jni.Jni |

这两个参数用于设置生成的C\C++头文件的指定,该两参数选项不能同时使用,-d是为<classes>中的每个有JNI方法的java类都生成一个头文件,并存放在-d指定的目录中,-o则是生成的所有JNI方法的头文件都放在-o指定的文件中。
显示当前javah的版本号.
实例2:
javah -version
javah version “1.6.0_11”
表示用于生成JNI风格的C\C++头文件,默认该参数就是开启的。不过应该不能关闭参数。
javah操作是针对类文件,-bootclasspath和-classpath就是指定在哪里进行类文件搜索。
JDK搜索类文件先后顺序如下:Bootstrap classes,User classes
Bootstrap默认的是JDK自带的jar或zip文件,它包括jre\lib下rt.jar等文件,JDK首先搜索这些文件.
可以通过-bootclasspath来设置它。文件之间用分号”;”进行分割。
User classes搜索顺序为当前目录、环境变量 CLASSPATH、-classpath。
它们用于告知JDK搜索类文件根目录名、jar文档名、zip文档名,用分号”;”进行分隔。
例如当你自己开发了公共类并包装成一个common.jar包,在使用 common.jar中的类时,就需要用-classpath common.jar 告诉JDK从common.jar中查找该类,否则JDK就会抛出java.lang.NoClassDefFoundError异常,表明未找到类定义。
使用-classpath后JDK将不再使用CLASSPATH中的类搜索路径,如果-classpath和CLASSPATH都没有设置,则JDK使用当前路径(.)作为类搜索路径。
推荐使用-classpath来定义JDK要搜索的类路径,而不要使用环境变量CLASSPATH的搜索路径,以减少多个项目同时使用CLASSPATH时存在的潜在冲突。例如应用1要使用a1.0.jar中的类G,应用2要使用 a2.0.jar中的类G,a2.0.jar是a1.0.jar的升级包,当a1.0.jar,a2.0.jar都在CLASSPATH中,JDK搜索到第一个包中的类G时就停止搜索,如果应用1应用2的虚拟机都从CLASSPATH中搜索,就会有一个应用得不到正确版本的类G。
javah命令是针对类文件中的,你肯定需要要把你要操作的类文件的根目录包含在搜索路径中,对于包文件(Jar或zip)形式的类文件,它的根目录就是包文件。另外这里的-bootclasspath和-classpath与java或javac命令都很相似,但是javah命令没有用来设置Extension classes的-extdirs参数选项,有点奇怪!还有这里的”-classpath”也不能缩写成”-cp”
*.cppji及JNI方法。


静态注册的方式有两个重要的关键词 JNIEXPORT 和 JNICALL,这两个关键词是宏定义,主要是注明该函数式 JNI 函数,当虚拟机加载 so 库时,如果发现函数含有这两个宏定义时,就会链接到对应的 Java 层的 native 方法。
那么怎么知道对应Java中的哪个类的哪个native方法呢,我们仔细观察JNI函数名的构成其实是:Java_PkgName_ClassName_NativeMethodName,以Java为前缀,并且用“_”下划线将包名、类名以及native方法名连接起来就是对应的JNI函数了。
可以看出 JNI 的调用函数的定义是按照一定规则命名的:
JNIEXPORT 返回值 JNICALL Java_全路径类名_方法名_参数签名(JNIEnv* , jclass, 其它参数);
其中 Java_ 是为了标识该函数来源于 Java。经检验(不一定正确),如果是重载的方法,则有“参数签名”,否则没有;另外如果使用的是 C++,在函数前面加上 extern “C”(表示按照 C 的方式编译),函数命名后面就不需要加上“参数签名”。
另外还需要注意几点特殊规则:
- 1. 包名或类名或方法名中含下划线 _ 要用 _1 连接;
- 2. 重载的本地方法命名要用双下划线 __ 连接;
- 3. 参数签名的斜杠 “/” 改为下划线 “_” 连接,分号 “;” 改为 “_2” 连接,左方括号 “[” 改为 “_3” 连接;
另外,对于 Java 的 native 方法,static 和非 static 方法的区别在于第二个参数,static 的为 jclass,非 static 的 为 jobject;JNI 函数中是没有修饰符的。
优点:
实现比较简单,可以通过 javah 工具将 Java代码的 native 方法直接转化为对应的native层代码的函数;
缺点:
- javah 生成的 native 层函数名特别长,可读性很差;
- 后期修改文件名、类名或函数名时,头文件的函数将失效,需要重新生成或手动改,比较麻烦;
- 程序运行效率低,首次调用 native 函数时,需要根据函数名在 JNI 层搜索对应的本地函数,建立对应关系,有点耗时;
9.2 动态注册
原理:直接告诉 native 方法其在JNI 中对应函数的指针。通过使用 JNINativeMethod 结构来保存 Java native 方法和 JNI 函数关联关系,步骤:
- 先编写 Java 的 native 方法;
- 编写 JNI 函数的实现(函数名可以随便命名);
- 利用结构体 JNINativeMethod 保存Java native方法和 JNI函数的对应关系;
- 利用
registerNatives(JNIEnv* env)
注册类的所有本地方法; - 在 JNI_OnLoad 方法中调用注册方法;
- 在Java中通过System.loadLibrary加载完JNI动态库之后,会调用JNI_OnLoad函数,完成动态注册;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //JNINativeMethod结构体 typedef struct { const char* name; //Java中native方法的名字 const char* signature; //Java中native方法的描述符 void* fnPtr; //对应JNI函数的指针 } JNINativeMethod; /** * @param clazz java类名,通过 FindClass 获取 * @param methods JNINativeMethod 结构体指针 * @param nMethods 方法个数 */ jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods) //JNI_OnLoad JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved); |
Jni2.java
1 2 3 4 5 6 7 8 9 10 11 12 | public class Jni2 { static { System.loadLibrary("aoaoyi-dynamic"); } public static native String getBaseUrl(); public static native String getReportUrl(); public static native String getUserProfileUrl(int userId); } |
aoaoyi-dynamic.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | #include <jni.h> #include <string> #include <string.h> #include <assert.h> //char const *BASE_URL = "http://aoaoyi.com/api/"; std::string BASE_URL = "http://aoaoyi.com/api/"; #ifdef __cplusplus extern "C" { #endif jstring JNICALL getBaseUrl( JNIEnv * env, jobject) { return env->NewStringUTF(BASE_URL.c_str()); } jstring JNICALL getReportUrl( JNIEnv * env, jobject) { std::string reportUrl = BASE_URL; reportUrl += "report/"; return env->NewStringUTF(reportUrl.c_str()); } jstring JNICALL getUserProfileUrl( JNIEnv * env, jobject, jint pUserId) { std::string profileUrl = BASE_URL; profileUrl += "profile/"; profileUrl += std::to_string(pUserId); return (env)->NewStringUTF(profileUrl.c_str()); } //指定要注册的类 #define JNI_REG_CLASS "com/aoaoyi/poker/jni/Jni2" /** * 映射 */ static JNINativeMethod gMethods[] = { { "getBaseUrl", "()Ljava/lang/String;", (void*)getBaseUrl }, { "getReportUrl", "()Ljava/lang/String;", (void*)getReportUrl }, { "getUserProfileUrl", "(I)Ljava/lang/String;", (void*)getUserProfileUrl }, }; /* * * 注册JNI函数 */ static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods) { jclass clazz = NULL; clazz = env->FindClass(className); if (env->ExceptionCheck() || clazz == NULL) { env->ExceptionClear(); clazz = NULL; return JNI_FALSE; } /** * RegisterNatives * * @param clazz java类名,通过 FindClass 获取 * @param methods JNINativeMethod 结构体指针 * @param nMethods 方法个数 */ int nRC = env->RegisterNatives(clazz, gMethods, numMethods); if (env->ExceptionCheck() || nRC < 0) { env->ExceptionClear(); return JNI_FALSE; } return JNI_TRUE; } /* * 注册给类成员函数 */ int registerNatives(JNIEnv* env) { if (NULL == env) { return JNI_FALSE; } if (!registerNativeMethods(env, JNI_REG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) return JNI_FALSE; return JNI_TRUE; } /* * 反注册给类成员函数 */ void unregisterNatives(JNIEnv* env) { if (NULL == env) { return; } jclass clazz = NULL; clazz = env->FindClass(JNI_REG_CLASS); if (env->ExceptionCheck() || clazz == NULL) { env->ExceptionClear(); clazz = NULL; return; } env->UnregisterNatives(clazz); if (env->ExceptionCheck()) { env->ExceptionClear(); } } /* * 成功则返回JNI版本号,失败返回-1. */ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) { return -1; } assert(env != NULL); //注册 if (!registerNatives(env)) { return -1; } /* 注册成功,返回JNI版本号 */ result = JNI_VERSION_1_6; return result; } /* * 解除注册 */ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) { JNIEnv *env = NULL; if (vm->GetEnv((void **) &env, JNI_VERSION_1_6)) return; unregisterNatives(env); return; } #ifdef __cplusplus } #endif |

发表评论
要发表评论,您必须先登录。