Android中Java代码与C的互相调用(JNI的简单使用)

引言 最近在做项目的时候,接触到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
先就看这么多吧,网上也有很多大神写的博客,写得很好,有时间可以去看看,参考博客:https://blog.csdn.net/yuzhou_zang/article/details/78410632 ,这里我只是教大家如何使用Java与C/C++的互相调用。 实战 在这里我给大家做的例子是Java调用C语言和C语言调用Java来实现加法操作。
二话不说,开干,新建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
void V object L用/分割的完整类名 例如: Ljava/lang/String表示String类型 Array [签名 例如: [I表示int数组, [Ljava/lang/String表示String数组 Method (参数签名)返回类型签名 例如: ([I)I表示参数类型为int数组, 返回int类型的方法 如本例中的函数声明:
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的开发使用,真正的实现,可以去参考大神们的博客。上述如有问题,可以给我留言,欢迎大家批评指正!谢谢大家!

    推荐阅读