放手一搏吧,别顾虑太多,这就是男人该有的性格
Java JNI的本意是Java Native Interface(Java本地接口),它是为了方便Java调用C、C++等本地代码所封装的一层接口。我们知道,Java的优点是跨平台,但是作为优点的同时,其在本地交互的时候就出现了短板。Java的跨平台特性导致其本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成,于是Java提供了JNI专门用于和本地代码交互,这样就增强了Java语言的本地交互能力。通过Java JNI,用户可以调用C、C++所编写的本地代码。
NDK是Android所提供的一个工具集合,通过NDK可以在Android中更加方便地通过JNI来访问本地代码,比如C或者C++。NDK还提供了交叉编译器,开发人员只需要简单地修改mk文件就可以生成特定CPU平台的动态库。
使用NDK有如下好处:
- 提交代码的安全性。由于so库反编译比较困难,因此NDK提高了Android程序的安全性。
- 可以很方便地使用目前已有的C/C++开源库
- 便于平台间的移植。通过C/C++实现的动态库可以很方便地在其他平台上使用。
- 提高程序在某些特定情形下的执行效率,但是并不能明显提升Android程序的性能。
JNI和NDK开发所用到的动态库的格式是以.so为后缀的文件,下面统一简称为so库。JNI和NDK主要用于底层和嵌入式开发。
JNI的开发流程
JNI的开发流程有如下几步,首先需要在Java中声明native方法,接着用C或者C++实现native方法,然后就可以编译运行了。
声明native方法
创建一个类,这里叫做JniTest.java,代码如下所示:
|
|
可以看到上面的代码中,声明了两个native方法:get和set(String),这两个就是需要在JNI中实现的方法。在JniTest类的代码中有一个加载动态库的过程,其中jni-test是so库的标识,so库完整的名称为libjni-test.so,这是加载so库的规范。
通过javah命令生成Java所对应的JNI头文件
编译Java源文件得到class文件,然后通过javah命令导出JNI的头文件,具体的命令如下:
javac io/github/zane/ndkdevdemo/JniTest.java
javah io.github.zane.ndkdevdemo.JniTest
在当前目录下,会产生一个 io_github_zane_ndkdevdemo_JniTest.h 的头文件:
它是javah命令自动生成的,内容如下所示:
|
|
上面的代码需要做一个说明,首先函数名的格式遵循如下规则:Java_包名_类名_方法名。比如JniTest中的set方法,到这里就变成了 JNICALL Java_io_github_zane_ndkdevdemo_JniTest_set
(JNIEnv *, jobject, jstring) ,其中 io_github_zane_ndkdevdemo 是包名,JniTest是类名,jstring是代表的是set方法的String类型的参数。JNIEXPORT、JNICALL、JNIEnv和jobject都是JNI标准中所定义的类型或者宏,它们的含义如下:
- JNIEnv*:表示一个指向JNI环境的指针,可以通过它来访问JNI提供的接口方法;
- jobject:表示Java对象中的this;
- JNIEXPORT和JNICALL:它们是JNI中所定义的宏,可以在jni.h这个头文件中查找到。
下面的宏定义是必需的,它指定extern “C”内部的函数采用C语言的命名风格来编译。否则当JNI采用C++来实现时,由于C和C++编译过程中函数的命名风格不同,这将导致JNI在链接时无法根据函数名查找到具体的函数,那么JNI调用就无法完成。更多的细节实际上是有关C和C++编译时的一些问题,这里就不再展开了。
|
|
实现JNI方法
JNI方法是指Java中声明的native方法,这里可以选择用C++或者C来实现,它们的实现过程是类似的,只有少量的区别,下面分别用C++和C来实现JNI方法。
1、首先,在工程的主目录下创建一个子目录,名称随意,这里选择“jni”作为子目录的名称。
在New Android Component中,直接点击Finish按钮完成JNI目录创建。
2、然后,将之前通过javah生成的头文件 io_github_zane_ndkdevdemo_JniTest.h 复制到jni目录下。
3、接着在/jni目录下创建 test.cpp 或 test.c 文件,它们的实现如下所示:
|
|
可以发现,tet.cpp和test.c的实现很类似,但是它们对env的操作方式有所不同,因此用C++和C来实现同一个JNI方法,它们的区别主要集中在对env的操作上,其他都是类似的,如下所示:
|
|
编译so库并在Java中使用
Mac、Linux环境下:
so库的编译这里采用gcc,切换到jni目录中,对于 test.cpp 和 test.c 来说,它们的编译指令如下所示:
|
|
上面的编译命令中,/user/tools/jdk/include是本地的jdk的安装路径,在其他环境编译时将其指向本机的jdk路径即可。而libjni-test.so则是生成的so库的名字,在Java中可以通过如下方式加载:System.loadLibrary(“jni-test”),其中so库名字中的”lib”和”.so”是不需要明确指出的。so库编译完成后,就可以在Java程序中调用so库了,这里通过Java指令来执行Java程序,切换到主目录,执行如下指令:java -Djava.library.path=jni io.github.zane.ndkdevdemo.JniTest,其中-Djava.library.path=jni指明了so库的路径。
Window环境下:
详情请看:NDK的开发流程