Android 高级开发 JNI 之 C & Java 之间互相调用案例

2 篇文章 0 订阅

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值