java代码中如何嵌入c语言,Android中Java代码与C的互相调用(JNI的简单使用)

2023-05-16

引言

最近在做项目的时候,接触到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

这个时候我们就需要配置一下

这样我们就关联起来了,或者在模块及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的开发使用,真正的实现,可以去参考大神们的博客。上述如有问题,可以给我留言,欢迎大家批评指正!谢谢大家!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java代码中如何嵌入c语言,Android中Java代码与C的互相调用(JNI的简单使用) 的相关文章

随机推荐