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
JNI技术通过JVM调用到各个平台的API,虽然JNI可以调用C/C++,但是JNI调用还是比C/C++编写的原生应用还是要慢一点,不过对高性能计算来说,这点算不得什么,享受它的便利,也要承担它的弊端。3. JNI 与 NDK 区别
- JNI:JNI是一套编程接口,用来实现Java代码与本地的C/C++代码进行交互;
 - NDK: NDK是Google开发的一套开发和编译工具集,可以生成动态链接库,主要用于Android的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.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函数会返回数组的长度。
举一个简单的例子:
////输入一个数组,这里输入的是一个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数组:
////返回一个结构数组,返回一个硬盘信息的结构数组
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;
 }
JNIEnv 是 jni.h 文件最重要的部分,它的本质是指向函数表指针的指针(JavaVM也是),函数表里面定义了很多 JNI 函数,同时它也是区分 C 和 C++环境的(由上面介绍描述符时也可以看到),在 C 语言环境中,JNIEnv 是strut JNINativeInterface*的指针别名。
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”)`
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")
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.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下,运行如下命令:
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函数,完成动态注册;
 
 //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
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
#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

文章评论