引言 最近在做项目的时候,接触到JNI,想一想自己第一次接触这个东西的时候,还是好久之前,现在既然接触到了,那我就简单的跟大家讲一讲JNI的基本使用方法。
JNI(Java Native Interface):java本地开发接口,JNI是一个协议,这个协议用来沟通java代码和外部的本地代码(c/c++),外部的c/c++代码也可以调用java代码。
我们为什么要使用JNI呢,可以从效率和安全性两方面来说:
1. 安全性:java是版解释型语言,很容易比反编译拿到源代码,我们一些加密方面的问题,就可以用JNI来实现,
2. 效率:C/C++是本地语言,比java更高效。
做JNI,我们先的下载Android NDK(Native Development Kit )下载链接:(https://developer.android.google.cn/ndk/downloads/index.html),Android NDK是一套工具集合,允许你用像C/C++语言那样实现应用程序的一部分。
JNI和NDK的区别:
从工具上说,NDK其实多了一个把.so和.apk打包的工具,而JNI开发并没有打包,只是把.so文件放到文件系统的特定位置。
从编译库说,NDK开发C/C++只能使用NDK自带的有限的头文件,而使用JNI则可以使用文件系统中带的头文件。
从编写方式说,它们一样。
知识前瞻 Java类型和本地类型的对应关系:
Java类型 | 本地类型(JNI) | 描述 |
---|---|---|
boolean(布尔型) | jboolean | 无符号8个比特 |
byte(字节型) | jbyte | 有符号8个比特 |
char(字符型) | jchar | 无符号16个比特 |
short(短整型) | jshort | 有符号16个比特 |
int(整型) | jint | 有符号32个比特 |
long(长整型) | jlong | 有符号64个比特 |
float(浮点型) | jfloat | 32个比特 |
double(双精度浮点型) | jdouble | 64个比特 |
void(空型) | void | N/A |
二话不说,开干,新建Android工程。XMl界面定义如下:
XML代码
MainActivity代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {private EditText mEt1;
private EditText mEt2;
private TextView mResult;
private int mNumber1;
private int mNumber2;
//加载本地C语言文件库。库名字为你写的C语言文件名
static {
System.loadLibrary("Test");
}//调用本地C语言方法计算结果
public native int getResult(int number1, int number2);
//调用本地C语言方法
public native int callCMethod(int number1, int number2);
//本地C语言调用Java方法
public static int calculateResult(int number1, int number2){
return number1 + number2;
}@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}private void initView(){
mEt1 = findViewById(R.id.et1);
mEt2 = findViewById(R.id.et2);
mResult = findViewById(R.id.result);
Button calCMethod = findViewById(R.id.calCMethod);
calCMethod.setOnClickListener(this);
Button calCToJavaMethod = findViewById(R.id.calCToJavaMethod);
calCToJavaMethod.setOnClickListener(this);
}@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.calCMethod) {
getNumber();
int result = getResult(mNumber1, mNumber2);
if (mNumber1 + mNumber2 != result){
Toast.makeText(this, "结果错误了!调用本地C语言失败!", Toast.LENGTH_LONG).show();
}else {
Toast.makeText(this, "结果正确!调用本地C语言成功!", Toast.LENGTH_LONG).show();
}
mResult.setText(String.valueOf(result));
}else if (id == R.id.calCToJavaMethod){
getNumber();
int result = callCMethod(mNumber1, mNumber2);
if (mNumber1 + mNumber2 != result){
Toast.makeText(this, "结果错误了!本地C语言调用Java方法失败!", Toast.LENGTH_LONG).show();
}else {
Toast.makeText(this, "结果正确!本地C语言调用Java方法成功!", Toast.LENGTH_LONG).show();
}
mResult.setText(String.valueOf(result));
}}private void getNumber(){
String text1 = mEt1.getText().toString().trim();
String text2 = mEt2.getText().toString().trim();
if (text1.equals("")) {
text1 = mEt1.getHint().toString().trim();
}
if (text2.equals("")) {
text2 = mEt2.getHint().toString().trim();
}
mNumber1 = Integer.parseInt(text1);
mNumber2 = Integer.parseInt(text2);
}
}
上述都是入门级别的代码了,除了ConstraintLayout(参考https://www.jianshu.com/p/17ec9bd6ca8a)使用较少之外,其他的so easy!不过ConstraintLayout没有使用过的小伙伴,建议大家学习和使用一下。
我们接下来将围绕这段代码展开分析
//加载本地C语言文件库。
static {
System.loadLibrary("Test");
}//调用本地C语言方法计算结果
public native int getResult(int number1, int number2);
//调用本地C语言方法
public native int callCMethod(int number1, int number2);
//本地C语言调用Java方法
public static int calculateResult(int number1, int number2){
return number1 + number2;
}
Java调用本地C语言 Java文件中定义public native int getResult(int number1, int number2)这样的一个native方法,接下来我们需要调用javah命令来自动生成本地C语言头文件(javah -v -d F:\AndroidProject\JniLibsDemo\app\src\main\jni -jni com.wiky.jnilibsdemo.jni)。javah命令使用帮助
或者你也可以使用AS扩展工具, 自定义一些命令行工具。参考https://www.jianshu.com/p/9cb8514d1ba0
生成的头文件文件如下:
这个时候我们在jni目录下新建一个Test.c的文件。
Test.c的代码如下
//引用我们生成的头文件
#includeJNIEXPORT jint JNICALL Java_com_wiky_jnilibsdemo_MainActivity_getResult
(JNIEnv *env, jobject obj, jint number1, jint number2) {
return number1 + number2;
}
这个时候要想java调用本地语言,我们还需要添加一个Android的.mk配置文件,例如Android.mk
LOCAL_PATH:=$(call my-dir)
include$(CLEAR_VARS)
LOCAL_MODULE:=Test//对应加载的文件库名字(System.loadLibrary("Test"))
LOCAL_SRC_FILES:=Test.c//C语言文件名
include$(BUILD_SHARED_LIBRARY)
有的人以为这样就可以了,有没有发现我们还咩有配置Android运行本地语言的环境,而且发现我们的Android.mk文件还没用到么,点击运行,报错如下:
Error: Your project contains C++ files but it is not using a supported native build system.
Consider using CMake or ndk-build integration. For more information, go to:
https://d.android.com/r/studio-ui/add-native-code.html
Alternatively, you can use the experimental plugin:
https://developer.android.com/r/tools/experimental-plugin.html
这个时候我们就需要配置一下
【Android中Java代码与C的互相调用(JNI的简单使用)】这样我们就关联起来了,或者在模块及build.gradle配置
externalNativeBuild {
ndkBuild {
path file('src/main/jni/Android.mk')
}
}
点击同步,就可以了。我们运行一下
运行OK,大功告成!
本地C语言调用Java方法 同样的操作,我们利用我们输入的数字传入到C,在C中调用java方法计算得数,
定义这两个方法
//调用本地C语言方法
public native int callCMethod(int number1, int number2);
//本地C语言调用Java方法
public static int calculateResult(int number1, int number2){
return number1 + number2;
}
头文件中的方法定义这里就不再叙述,按照上述方法生成即可,我们接下来看Test.c文件中的实现
JNIEXPORT jint JNICALL Java_com_wiky_jnilibsdemo_MainActivity_callCMethod
(JNIEnv *env, jobject obj, jint number1, jint number2) {
char *classname = "com/wiky/jnilibsdemo/MainActivity";
jclass jClazz = (*env)->FindClass(env, classname);
//这里实现了互相调用,Java中调用了C的getStringFormc方法传递了x,y参数,这里C又调用了Java的add方法将x,y回传回去求和;
jmethodID methodID = (*env)->GetStaticMethodID(env, jClazz, "calculateResult", "(II)I");
jint result = (*env)->CallStaticIntMethod(env, jClazz, methodID, number1, number2);
return result;
}
这段代码的就是利用反射获取到Java中的方法,然后去调用。在这里我要讲的是GetStaticMethodID方法中的第四个参数"(II)I",这个代表的是调用Java方法的的方法签名,关于签名,如下:
java类型 | Signature | 备注 |
---|---|---|
boolean | Z | |
byte | B | |
char | C | |
short | S | |
int | I | |
long | L | |
float | F | |
double | D |
JNIEXPORT jint JNICALL Java_J2C_write2proc(JNIEnv *, jobject, jint);
注释中的签名是 Signature: (I)I
通过上述的表格之后我们很自然的清楚上述代码的意思,点击运行
看到这里,比较欣喜,真正的打完收工。本人C++的已经忘记了,所以。。。。。。
扩展 如果你需要将本地语言移植到另一个项目,你可以考虑将本地语言编译成.so文件,首先添加.mk文件,例如Application.mk文件(参考https://blog.csdn.net/kwuwei/article/details/21718097)
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := all
APP_PLATFORM := android-8
利用AS扩展工具, 自定义ndk-build命令,运行命令就可以看到.so文件了,或者cd到Application.mk的文件所在目录,输入ndk-build运行,这个命令在你的ndk文件夹中,记得配置环境变量。
总结 这些只是简单的JNI的开发使用,真正的实现,可以去参考大神们的博客。上述如有问题,可以给我留言,欢迎大家批评指正!谢谢大家!