Android 高级开发 JNI 之 C & Java 之间互相调用案例
在前一篇文章中讲解了 JNI 和 NDK 的介绍和基本配置 ,现在来一起了解下 C/C++ 之间的相互调用方式,文中使用的是.c 文件。
文中使用的环境是
Android Studio 3.6.1
build:gradle:3.6.1
一、Java 调用 C 案例
详情查看 NDKDemo ->app
1、Java 调用 C 执行加法运算
JNI.java 代码
/**
加载动态链接库 和C 代码进行交互
编译的动态链接库 是以当前 的 module 名称为库名称, lib+moduleName+.so
并且在 build.gradle 中配置 NDK 对应的 CPU 架构 ,并配置动态链接库名称
ndk{
moduleName "Hello" //so文件: lib+moduleName+.so
abiFilters "armeabi-v7a", "x86", 'x86_64' //cpu的类型一定要适配多数,否则运行在少部 分机型上会崩溃
}
也需要在 CMakeLists.txt 配置 编译动态链接库名称 保持一致
*/
static {
System.loadLibrary("Hello");
}
/**
* 让C代码做加法运算,把结果返回
*
* @param x
* @param y
* @return
*/
public native int add(int x, int y);
Hello.c 代码
/**
* IDE 可自动生成创建JNI对应的 C 方法名
*/
JNIEXPORT jint JNICALL
Java_com_thisfeng_ndkdemo2_JNI_add(JNIEnv *env, jobject thiz, jint jx, jint jy) {
int result = jx + jy;
LOGE("value===%d\n", result);
return result;
}
MainActivity 中进行调用,前提要先初始化 JNI.java
public void add(View view) {
int result = mJIN.add(99, 1);
Toast.makeText(this, String.valueOf(result), Toast.LENGTH_SHORT).show();
}
2、Java 调用 C 执行字符串拼接运算
JNI.java 代码
/**
* 定义 native 方法
* 调用 C 代码对应的方法
* 从java传入字符串,C代码进程拼接
* *
* * @param s I am from java
* * @return I am form java add I am from C
*/
public native String sayHello(String s);
Hello.c 代码
/**
* java 重载方法 ,携带参数 s
*
*
* 从java传入字符串,C代码进程拼接
*
* @param java : I am from java
* c : add I am from C
* @return I am form java add I am from C
*/
JNIEXPORT jstring JNICALL
Java_com_thisfeng_ndkdemo2_JNI_sayHello__Ljava_lang_String_2(JNIEnv *env, jobject thiz,
jstring jstr) {
char *fromJava = _JString2CStr(env, jstr);//I am form java
//C:
char *fromC = " add I am from C! 很高兴拼接成功";
//拼接函数 strcat
strcat(fromJava, fromC);//拼接的结果放在第一参数里面
LOGE("fromJava===%s\n", fromJava);
//jstring (*NewStringUTF)(JNIEnv*, const char*);
return (*env)->NewStringUTF(env, fromJava);
}
MainActivity
public void string(View view) {
String result = mJIN.sayHello("Iam from java");
Toast.makeText(this, result, Toast.LENGTH_SHORT).show();
}
3、Java 调用 C 执行数组运算
JNI.java 代码
/**
* 从 Native 返回基本数据类型数组
* 让C代码给每个元素都加上10
*/
public native int[] increaseArrayEles(int[] intArray);
Hello.c 代码
/**
* 给每个元素加上10
*/
JNIEXPORT jintArray JNICALL
Java_com_thisfeng_ndkdemo2_JNI_increaseArrayEles(JNIEnv *env, jobject thiz, jintArray jarray) {
//1.得到数组的长度
// jsize (*GetArrayLength)(JNIEnv*, jarray);
jsize size = (*env)->GetArrayLength(env, jarray);
//2.得到数组元素
//jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
jint *intArray = (*env)->GetIntArrayElements(env, jarray,
JNI_FALSE);// true 重新开辟内存空间操作数组;false 相 当于同一份就在当前传入的 jarray 的 Native 内存空间去操作数组
//3.遍历数组,给每个元素加上10
int i;
for (i = 0; i < size; ++i) {
// *(intArray+i) = *(intArray+i) + 10;
*(intArray + i) += 10;//每循环一次指针往后移动下一位
}
// 4.使用完了别忘了释放内存 释放数组。注意:释放模式为0,java数组会修改;如果是JNI_ABORT,java的数组并不会被修改
(*env)->ReleaseIntArrayElements(env, jarray, intArray, 0);
//5.返回结果
return jarray;
}
MainActivity
public void array(View view) {
int[] array = {1, 2, 3, 4, 5};
int[] result = mJIN.increaseArrayEles(array);
StringBuilder stringBuffer = new StringBuilder();
for (int i = 0; i < result.length; i++) {
Log.e(MainActivity.class.getSimpleName(), "array[" + i + "]===" + result[i]);
stringBuffer.append("array[" + i + "]===" + result[i]);
stringBuffer.append("\n");
}
tvContent.setText(stringBuffer.toString());
}
4、Java 调用 C 执行密码校验
JNI.java 代码
/**
* 应用: 检查密码是否正确, 如果正确返回200, 否则返回400
*/
public native int checkPwd(String password);
Hello.c 代码
/**
* 校验密码
*/
JNIEXPORT jint JNICALL
Java_com_thisfeng_ndkdemo2_JNI_checkPwd(JNIEnv *env, jobject thiz, jstring password) {
//服务器的密码是123456
char *origin = "123456";
char *fromUser = _JString2CStr(env, password);
//函数比较字符串是否相同
int code = strcmp(origin, fromUser);
if (code == 0) {
return 200;
} else {
return 400;
}
}
MainActivity
public void checkpwd(View view) {
int result = mJIN.checkPwd("123456");
Toast.makeText(this, String.valueOf(result), Toast.LENGTH_SHORT).show();
}
二、C 调用 Java 案例
详情查看 NDKDemo - > ccalljava
1、让C代码调用 java 中JNI类的 public int add(int x, int y)
JNI.java 代码
static {
System.loadLibrary("ccalljava");
}
/**
* 当执行这个方法的时候,让C代码调用
* public int add(int x, int y)
*/
public native void callbackAdd();
public int add(int x, int y) {
//接收到 C 传递过来的参数 进行使用 ,在 java 层 进行 相加处理 然后返回
Log.e("TAG", "add() x=" + x + " y=" + y);
return x + y;
}
Hello.c 代码
/**
* 让C代码调用 java 中JNI类的 public int add(int x, int y)
*/
JNIEXPORT void JNICALL
Java_com_thisfeng_ccalljava_JNI_callbackAdd(JNIEnv *env, jobject thiz) {
//1.得到字节码
//jclass (*FindClass)(JNIEnv*, const char*);
jclass jclazz = (*env)->FindClass(env, "com/thisfeng/ccalljava/JNI");//注意需要将包.使用 / 杠 类名
//2. 通过字节码得到方法
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//最后一个参数是方法签名 ,IDE 可自动生成 对应的签名标记,输入任意 进行 提示 fix
jmethodID jmethodIDs = (*env)->GetMethodID(env, jclazz, "add", "(II)I");
//3.实例化该类 , 分配对象
// jobject (*AllocObject)(JNIEnv*, jclass);
jobject jobject = (*env)->AllocObject(env, jclazz);
//4.调用方法
//jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
jint value = (*env)->CallIntMethod(env, jobject, jmethodIDs, 99, 1); //传入的参数 可在java中使用
//成功调用了public int add(int x, int y)
// printf("value===%d\n", value);
LOGE("value===%d\n", value); //注意 要打印有返回值 的
}
MainActivity
jni.callbackAdd();
2、让C代码调用 public void helloFromJava()
JNI.java 代码
/**
* 当执行这个方法的时候,让C代码调用
* public void helloFromJava()
*/
public native void callbackHelloFromJava();
public void helloFromJava() {
Log.e("TAG", "helloFromJava()");
}
Hello.c 代码
/**
* 让C代码调用 public void helloFromJava()
*/
JNIEXPORT void JNICALL
Java_com_thisfeng_ccalljava_JNI_callbackHelloFromJava(JNIEnv *env, jobject thiz) {
//1.得到字节码
//jclass (*FindClass)(JNIEnv*, const char*);
jclass jclazz = (*env)->FindClass(env, "com/thisfeng/ccalljava/JNI");//注意需要将包.使用 / 杠 类名
//2. 通过字节码得到方法
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//最后一个参数是方法签名
jmethodID jmethodIDs = (*env)->GetMethodID(env, jclazz, "helloFromJava", "()V");
//3.实例化该类 , 分配对象
// jobject (*AllocObject)(JNIEnv*, jclass);
jobject jobject = (*env)->AllocObject(env, jclazz);
//4.调用方法
//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
(*env)->CallVoidMethod(env, jobject, jmethodIDs); //传入的参数 可在java中使用
//成功调用了public void helloFromJava()
}
MainActivity
jni.callbackHelloFromJava();
3、让C代码调用 void printString(String s)
JNI.java 代码
/**
* 当执行这个方法的时候,让C代码调用void printString(String s)
*/
public native void callbackPrintString();
public void printString(String s) {
Log.e("TAG", "C中输入的:" + s);
}
Hello.c 代码
/**
* 让C代码调用void printString(String s)
*/
JNIEXPORT void JNICALL
Java_com_thisfeng_ccalljava_JNI_callbackPrintString(JNIEnv *env, jobject thiz) {
//1.得到字节码
//jclass (*FindClass)(JNIEnv*, const char*);
jclass jclazz = (*env)->FindClass(env, "com/thisfeng/ccalljava/JNI");//注意需要将包.使用 / 杠 类名
//2. 通过字节码得到方法
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//最后一个参数是方法签名
jmethodID jmethodIDs = (*env)->GetMethodID(env, jclazz, "printString", "(Ljava/lang/String;)V");
//3.实例化该类 , 分配对象
// jobject (*AllocObject)(JNIEnv*, jclass);
jobject jobject = (*env)->AllocObject(env, jclazz);
//定义一个字符串对象
//jstring (*NewStringUTF)(JNIEnv*, const char*);//java 的String 对应 JNI 里面的 jstring
jstring jst = (*env)->NewStringUTF(env, "I am thisfeng!!!(*env)->");
//4.调用方法
//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
(*env)->CallVoidMethod(env, jobject, jmethodIDs, jst); //传入的参数 可在java中使用
//成功调用了public void helloFromJava()
}
MainActivity
jni.callbackPrintString();
4、让C代码静态方法 static void sayHello(String s)
JNI.java 代码
/**
* 当执行这个方法的时候,让C代码静态方法 static void sayHello(String s)
*/
public native void callbackSayHello();
public static void sayHello(String s) {
Log.e("TAG", "我是java代码中的JNI.java中的sayHello(String s)静态方法,我被C调用了:" + s);
}
Hello.c 代码
/**
* 让C代码调用 静态方法 static void sayHello(String s)
*/
JNIEXPORT void JNICALL
Java_com_thisfeng_ccalljava_JNI_callbackSayHello(JNIEnv *env, jobject thiz) {
//1.得到字节码
//jclass (*FindClass)(JNIEnv*, const char*);
jclass jclazz = (*env)->FindClass(env, "com/thisfeng/ccalljava/JNI");//注意需要将包.使用 / 杠 类名
//2. 通过字节码得到方法
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//最后一个参数是方法签名
jmethodID jmethodIDs = (*env)->GetStaticMethodID(env, jclazz, "sayHello","(Ljava/lang/String;)V");//调用静态方法 不需要实例化
//3.创建jstring 对象 对应方法 传递给 java
//void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);
jstring jst1 = (*env)->NewStringUTF(env, "I am Android 123");
//log 直接打印 jst1 会报错
//4.调用方法
(*env)->CallStaticVoidMethod(env, jclazz, jmethodIDs, jst1); //传入的参数 可在java中使用
//成功调用了public void sayHello()
}
MainActivity
jni.callbackSayHello();
5、让C代码调用 MainActivity 中的showToast 方法进行更新UI(谁调用就是谁的实例)
JNI.java 代码
调用的方法会根据调用者的实例进行返回更新,所以需要将方法定义到 MainActivity中!
Hello.c 代码
/**
* instance:谁调用了当前 Java_com_thisfeng_ccalljava_JNI_callBackShowToast 对应的Java的接口
* 就是谁的实例:当前是JNI.this-->MainActivity.this
*
* 需要使用此
* @param env
* @param thiz
*/
JNIEXPORT void JNICALL
Java_com_thisfeng_ccalljava_MainActivity_callBackShowToast(JNIEnv *env, jobject instance) {
//1.得到字节码
//jclass (*FindClass)(JNIEnv*, const char*);
jclass jclazz = (*env)->FindClass(env, "com/thisfeng/ccalljava/MainActivity");
//2. 通过字节码得到方法
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//最后一个参数是方法签名
jmethodID jmethodIDs = (*env)->GetMethodID(env, jclazz, "showToast", "()V");
//3.实例化该类 , 分配对象
// jobject (*AllocObject)(JNIEnv*, jclass);
// jobject jobject = (*env)->AllocObject(env, jclazz);
//4.调用方法
//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
(*env)->CallVoidMethod(env, instance, jmethodIDs); //传入的参数 可在java中使用
//成功调用了public void helloFromJava()
}
MainActivity
/**
* 让C代码调用MainActiity里面的showToast
*/
public native void callBackShowToast();
public void showToast() {
System.out.println("showToast----------------");
Toast.makeText(MainActivity.this, "showToast----------------", Toast.LENGTH_SHORT).show();
}
public void onClick(View view) {
// jni.callbackAdd();
// jni.callbackHelloFromJava();
// jni.callbackPrintString();
// jni.callbackSayHello();
//更新UI
// jni.callBackShowToast(); //上下文对象错误
this.callBackShowToast();//使用当前的上下文进行调用
}
tips:反射 New 出来的 Object 是普通的类,不是上下文,要注意传递过来的是 应该是当前的 上下文 jobject
三、JNI与java数据类型对应关系
以上就是常见的 互调的方式,其实细心后会发现内部方法雷同,不同类型进行举一反三就行。
图引用自 Carson_Ho
以上例子
详情可查看 NDKDemo