- 为什么使用JNI:
效率上 C/C++是本地语言,比java更高效;
代码移植,如果之前用C语言开发过模块,可以复用已经存在的c代码;
java反编译比C语言容易,一般加密算法都是用C语言编写,不容易被反编译;
- JNI是Java Native Interface的缩写,中文为JAVA本地调用。从Java1.1开始,Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的,比如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少保证本地代码能工作在任何Java 虚拟机实现下。 标准的java类库可能不支持你的程序所需的特性。 JNI·或许你已经有了一个用其他语言写成的库或程序,而你希望在java程序中使用它。你可能需要用底层语言实现一个小型的时间敏感代码,比如汇编,然后在你的java程序中调用这些功能。
- NDK是Google公司推出的帮助Android开发者通过C/C++本地语言编写应用的开发包,包含了C/C++的头文件、库文件、说明文档和示例代码,我们可以理解为Windows Platform SDK一样,是纯C/C++编写的,但是Android并不支持纯C/C++编写的应用,同时NDK提供的库和函数功能很有限,仅仅处理些算法效率敏感的问题,所以推荐初学者学好Java后再学习JNI。 NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。 NDK可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。
搭建环境
首先配置jdk环境
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html下载jdk,并且安装到电脑上。
第一步:
JAVA_HOME (没有就新建一个),变量值(jdk路径,我的jdk是1.8):
c:\Program Files(x86)\Java\jdk1.8.0_18
第二步:找到变量名为
Path的,在变量值前面加上
%JAVA_HOME%/bin;用分号隔开,如果 放到后面就是
;%JAVA_HOME%/bin
第三步:新建classpath(有的直接打开)输入.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar,千万不能漏掉.;表示当前路径。
然后
打开AS,选择file->project Structure->SDK Location,如图:
记得配置jdk时一定要先把上面那个默认勾选的去掉。
接下来是给AS配置NDK环境
- 下载NDK包,https://developer.android.com/ndk/downloads/index.html?hl=zh-cn 这个网址需要开发者翻墙才能访问下载,我用的是蓝灯翻到日本去下载。。。win64位的包约莫760M
- 下载好并且解压放到你要放的文件夹中,打开AS,选择file->project Structure->SDK Location,如图:
其中,ndk的路径改为你刚刚解压NKD包放置的文件里面的android-ndk-xxx的绝对路径。
记得配置完之后,要重启AS
- 重启完AS重新进来,打开Terminal界面,先试着输入javah,看看是否已经完成jdk的配置,如图:
如果出现了如下的提示,则说明jdk配置正确了,不然就是环境变量没有配置正确。
- 确认jdk环境配置无误后,打开项目的local.properties文件,在里面输入nkd的路径
- 接着在gradle.properties里面声明开启ndk,声明代码: android.useDeprecatedNdk=true
- 然后在module的build.gradle里面配声明生成的so文件
在android括号里面声明一个ndk配置信息
ndk{
moduleName "WangicClock" //生成的so名字
abiFilters "armeabi", "armeabi-v7a", "x86", "x86_64", "arm64-v8a" //输出指定五种abi体系结构下的so库。目前可有可无。
}
moduleName "WangicClock" //生成的so名字
abiFilters "armeabi", "armeabi-v7a", "x86", "x86_64", "arm64-v8a" //输出指定五种abi体系结构下的so库。目前可有可无。
}
这个配置信息包括moduleName和abiFilters,moduleName指的是你要声明的so文件的名字,abiFilters可有可无,目前主流的体系就5种。
到这一步配置阶段基本就告一段落了,接下来要开始写代码了
- 首先在java文件夹里面创建一个新的文件夹,里面创建一个class文件,声明native方法
public class JniClient {
public native String getStr() ;
public native int AddInt( int a , int b) ;
}
public native String getStr() ;
public native int AddInt( int a , int b) ;
}
- 然后找到app文件夹下的build->intermediates->classes->debug(release)
- 接着打开Terminal,把路径定到刚刚的路径上
输入:javah -jni 你创建的包名.你创建的类名.例如:
按回车之后就会执行命令,得到一个.h文件,在刚刚的build.intermediates.classes.debug(release)文件夹中
这个.h文件就是我们需要的C++文件了
打开.h文件
这里可以看到两个圈起来的方法,就是我们刚刚写的class定义的两个方法名
- 在java目录下再创建一个jni文件夹,将刚刚的.h文件剪切放入jni文件夹中,并且创建多一个.c文件,名字随意
- 打开刚刚创建的.c文件,输入
#include
"c_JniClient.h"
/*
* Class: io_github_yanbober_ndkapplication_NdkJniUtils
* Method: getCLanguageString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_c_JniClient_getStr //.h里面的其中一个方法名
(JNIEnv *env, jobject obj){
return (*env) ->NewStringUTF(env, "This just a test for Android Studio NDK JNI developer!");
}
/*
* Class: io_github_yanbober_ndkapplication_NdkJniUtils
* Method: getCLanguageString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_c_JniClient_getStr //.h里面的其中一个方法名
(JNIEnv *env, jobject obj){
return (*env) ->NewStringUTF(env, "This just a test for Android Studio NDK JNI developer!");
}
解释:
首先include "包名_类名.h"
然后 JNIEXPORT jstring JNICALL +.h里面的方法名+(*形参名,jobject obj){
return (*形参名)->NewStringUTF(形参名,“你要输出的文字”);
}
- 最后,回到最开始声明的java文件里面,加上一个静态加载代码
static {
System. loadLibrary( "WangicClock") ; //defaultConfig.ndk.moduleName
System. loadLibrary( "WangicClock") ; //defaultConfig.ndk.moduleName
}
静态方法就是loadLibray("你在build.gradle声明的so名字");
- 在你要获得字符串的地方执行方法即可
- 你就可以看到来自C的问候
有疑问的可以直接留言。。。。