• Android JNI复杂用法,回调,C++中调用Java方法


    Android JNI复杂用法,回调,C++中调用Java方法

    一、前言

    Android JNI的 普通用法估计很多人都会,但是C++中调用Java方法很多人不熟悉,并且网上很多介绍都是片段的。

    虽然C/C++调用Java不常用,但是掌握多一点还是有好处的。

    Android JNI的基础知识介绍,之前已经有介绍,不熟悉的可以先看看:

    Android Jni的介绍和简单Demo实现:

    https://blog.csdn.net/wenzhi20102321/article/details/136291126

    本文主要介绍JNI C++调用Java代码实现和相关知识,有兴趣的可以看看。

    二、C++调用Java方法实现代码

    1、上层代码 MainAcitvity.java

    package com.demo.jnicallback;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.TextView;
    
    public class MainActivity extends AppCompatActivity {
    
        String TAG = "MainActivity.java";
    
        static {
            System.loadLibrary("native-lib");
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Log.i(TAG, "conCreate");
            TextView tv = findViewById(R.id.sample_text);
            String jniString = stringFromJNI();
            Log.i(TAG, "conCreate cppCallBackMethod jniString = " + jniString);
            tv.setText("" + jniString);
        }
    
        //C++调用Java 的方法,定义成private方法,cpp也是可以调用到的,因为是通过反射过来的
        public void cppCallBackMethod(String name, int age) {
            Log.i(TAG, "cppCallBackMethod name = " + name + ",age = " + age);
        }
    
    	//Java 调用到 cpp 的native方法
        public native String stringFromJNI();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    布局上未做修改,运行后的默认字符串"Hello from C++"。

    Java代码这里加了一个给C++调用过来的方法,具体实现效果可以看是日志。

    2、cpp代码 native-lib.cpp 代码:

    #include 
    #include 
    
    #include  //添加头文件
    #define LOG_TAG "native-lib.cpp" //定义TAG
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
    #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    
    #include 
    #include 
    #include 
    
    extern "C" JNIEXPORT jstring JNICALL
    Java_com_demo_jnicallback_MainActivity_stringFromJNI(
            JNIEnv* env,
            jobject thiz /* this */) {
        std::string hello = "Hello from C++";
        LOGI("stringFromJNI hello = %s", hello.c_str());
        
        //c++调用Java方法:public void cppCallBackMethod(String name, int age)
        jobject m_object = env->NewGlobalRef(thiz);//创建对象的本地变量
        jclass mainActivityCls=env->FindClass("com/demo/jnicallback/MainActivity");//获取类对象
        jmethodID cppCallBackMethod = env->GetMethodID(mainActivityCls, "cppCallBackMethod", "(Ljava/lang/String;I)V");
        const char *message = "cppA";
        int age = 10;
        env->CallVoidMethod(m_object, cppCallBackMethod, env->NewStringUTF(message), age);
        while (age < 50) {
            //睡眠1秒
            std::chrono::seconds duration(1); // 休眠一秒钟
            std::this_thread::sleep_for(duration);
            age = age + 10;
            env->CallVoidMethod(m_object, cppCallBackMethod, env->NewStringUTF(message), age);
        }
    
        return env->NewStringUTF(hello.c_str());
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    上面代码可以看到获取类对象,是为了获取方法id;获取对象的本地变量是为了调用方法。

    网上有些示例可能写法不一样,熟悉c++代码的应该知道,"env->“的写法和”(*env)."是一个意思。

    3、效果日志:

    //Java打印最开始日志
    2024-03-01 16:27:50.401  I/MainActivity.java: conCreate
    //cpp文件打印,开始的日志
    2024-03-01 16:27:50.402  I/native-lib.cpp: stringFromJNI hello = Hello from C++
    //cpp调用Java部分日志,在Java代码每隔一秒的打印
    2024-03-01 16:27:50.402  I/MainActivity.java: cppCallBackMethod name = cppA,age = 10
    2024-03-01 16:27:51.403  I/MainActivity.java: cppCallBackMethod name = cppA,age = 20
    2024-03-01 16:27:52.403  I/MainActivity.java: cppCallBackMethod name = cppA,age = 30
    2024-03-01 16:27:53.403  I/MainActivity.java: cppCallBackMethod name = cppA,age = 40
    2024-03-01 16:27:54.404  I/MainActivity.java: cppCallBackMethod name = cppA,age = 50
    //Java onCreate最后的日志,打印C++返回的字符串
    2024-03-01 16:27:54.404  I/MainActivity.java: conCreate cppCallBackMethod jniString = Hello from C++
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上面的代码就有Java --> C++和C++ --> Java的代码流程。

    注意,这里的示例代码添加了睡眠代码,如果在主线程长时间执行任务是有可能导致ANR的。

    4、cpp代码 native-lib.cpp 代码另一种写法

    下面这种写法不用NewGlobalRef创建对象的本地变量。

    中间的区别就是这里函数的调用没有使用"->“,使用的”(*env)."

    extern "C" JNIEXPORT jstring JNICALL
    Java_com_demo_jnicallback_MainActivity_stringFromJNI(
            JNIEnv* env,
            jobject thiz /* this */) {
        std::string hello = "Hello from C++";
        LOGI("stringFromJNI hello = %s", hello.c_str());
        //c++调用Java方法:public void cppCallBackMethod(String name, int age)
        jclass mainActivityCls=(*env).FindClass("com/demo/jnicallback/MainActivity");//获取类对象
        jmethodID cppCallBackMethod = (*env).GetMethodID(mainActivityCls, "cppCallBackMethod", "(Ljava/lang/String;I)V");
        const char *message = "cppA";
        int age = 10;
        (*env).CallVoidMethod(thiz, cppCallBackMethod, env->NewStringUTF(message), age);
        while (age < 50) {
            //睡眠1秒
            std::chrono::seconds duration(1); // 休眠一秒钟
            std::this_thread::sleep_for(duration);
            age = age + 10;
            (*env).CallVoidMethod(thiz, cppCallBackMethod, env->NewStringUTF(message), age);
        }
    
        return env->NewStringUTF(hello.c_str());
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    上面的代码运行也是一样的效果。

    "->“和”(*env)."有啥区别?因为不是很熟悉,还还说不清。

    上面不同写法调用方法的参数是有区别的,其实就是函数api的参数要求不同,具体可以看到jni.h的源码。

    三、其他

    1、C++到Java 相关api函数介绍

    上面示例中使用用到的api:

    (1)jobject m_object = env->NewGlobalRef(thiz);//创建对象的本地变量
    (2)jclass mainActivityCls=env->FindClass("com/demo/jnicallback/MainActivity");//获取类对象
    
    (3)jmethodID cppCallBackMethod = env->GetMethodID(mainActivityCls, "cppCallBackMethod", "(Ljava/lang/String;I)V");
    
    (4)env->CallVoidMethod(m_object, cppCallBackMethod, env->NewStringUTF(message), age);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上面(1)和(2)是没什么研究价值的,NewGlobalRef和FindClass都是固定的写法。

    (3)和(4)的不用方法的调用区别就比较大了,使用不同的api函数还可以修改Java的变量属性。

    静态方法和动态方法调用的api函数也不一样,有返回值的方法和没有返回值的方法调用的api函数也是不一样的。

    并且Java方法或者变量即使是private修饰的也不影响cpp调用过去,因为反射是不受修饰符影响的。

    第3步里面的签名字符串“(Ljava/lang/String;I)V”,表示的是Java的方法和返回值的签名,唯一性;

    这里面的签名字符串都是根据Java方法和方法的参数进行变化的。

    下面对3、4步的代码相关知识做展开介绍。

    2、调用获取不同方法和变量的api

    方法、变量修饰类型表格
    函数描述描述
    GetFieldID得到一个实例的域的ID
    GetStaticFieldID得到一个静态的域的ID
    GetMethodID得到一个实例的方法的ID
    GetStaticMethodID得到一个静态方法的ID

    上面Jni.cpp调用Java代码已经用到部分api方法,并且从字面含义也是比较容易里面这个表格的api的具体作用。

    这个表格的用于就是为了获取到方法的修饰类型,比如方法,静态方法,变量,静态变量。

    毕竟不同的修饰类型,在编译过程是有差异的。所以要区分。

    3、Java签名类型字符串 常用的数据类型及对应字符:

    上面示例中的"(Ljava/lang/String;I)V");字符串都是根据Java的方法通过下面这个表格转换来的。

    Java 类型Jni中表示的符号备注
    booleanZ不是类型首字母大写
    byteB
    charC
    shortS
    intI
    longL
    floatF
    doubleD
    voidV
    objects对象Lfully-qualified-class-name;L全类名;记得最后是有分号的
    Arrays数组[array-type [数组类型
    methods方法(argument-types)return-type(参数类型)返回类型

    这个表格是有有啥用?就更多人懵逼了。

    其实这些类型符号表示的是Java方法或者属性的一个签名,唯一性,目前就是为了让Jni.cpp调用到Java代码。

    举个例子就很容易清楚了:

    //XXX.Java
      int age;
      String name;
      public  int add(int number1,int number2){
            System.out.println("c/C++居然调用了我");
            return number1+number2;
        }
    
    //jni.cpp 修改Java属性值和调用Java方法示例
    
    //获取类对象
    jclass mainActivityCls=env->FindClass("com/zmw/jnitest/MainActivity");
    
    //获取属性的fieldId,--》这里就用到了签名类型
    jfieldID ageFid = env->GetFieldID(mainActivityCls,"age","I");
    jfieldID nameFid=env->GetFieldID(mainActivityCls, "name", "Ljava/lang/String;");
    //获取属性值
    jint  age = env->GetIntField(mainActivityThis,ageFid);
    jstring name = (jstring)env->GetObjectField(thiz,nameFid);//此处有编码转换问题未解决
    
    //修改属性值,C++中修改变量值后,Java重新获取打印发现是修改过的
    env->SetIntField(mainActivityThis, ageFid , 11);
    env->SetObjectField(thiz, nameFid,Stringvalue);
    
    //获取方法的methodId,--》这里就用到了签名类型
    jmethodID addMid=env->GetMethodID(mainActivityCls, "add", "(II)I");
    int result=env->CallIntMethod(mainActivityThis, addMid, 1, 1); //这里就能获取到2的值。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    仔细看一下上面的代码,就大致能理解这个签名表格的具体作用:为了找到Java方法的参数和返回值的形式。

    Java签名类型小结:

    (1)基础类型签名那些转换都是很容易记住的,基础类型中,特别留意一下boolean类型 是 Z 就行
    (2)对象Object类型的转换是:L+全包名(包名直接用 /间隔)+类名+分号
    (3)数组类型签名转换:[数组类型,比如[I,表示Java的 int[]
    
    (4)方法签名的转换:(参数类型)返回类型,中间多个参数类型依此填写就行,
    比如:Jni中的代码:env->GetMethodID("add", "(IILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;")
    如果不清楚上面的表格转换,看起来就头大,特别是那些有三四个以上参数的情况,但是学习过后就不难了,
    查看表格对应关系可以知道,Java中的对应方法是:public String add(int a,int b,String c,String d)
    其实就是先看括号后面的返回值,然后再一个个确定括号内的形参变量
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    共勉: 这短短的一生,我们最终都会过去,你不妨大胆一些,爱一个人、攀一座山、追一个梦

  • 相关阅读:
    文件上传漏洞利用与防御
    淹没虚函数地址过GS保护(关闭DEP保护)
    IDEA点击RUN后后台到底执行了什么操作
    使用 HTML、CSS 和 JavaScript 的简单模拟时钟
    Bootstrap与响应式图片设计相关的类
    SSH 基础学习使用
    Salesforce中国区或将解散?国产SaaS如何在竞争中扬长避短
    leetcode82-Remove Duplicates from Sorted List II
    Python安装教程(Windows10系统)
    《现代医学成像科学丛书——CT原理与技术》——X射线管的基本作用
  • 原文地址:https://blog.csdn.net/wenzhi20102321/article/details/136419405