美文网首页
Android NDK 开发之JNI常规操作

Android NDK 开发之JNI常规操作

作者: 土豆海马 | 来源:发表于2022-07-15 17:29 被阅读0次
    image.png

    1. JNI函数详解

    java中native方法在C++代码中一般如下:

    extern "C" JNIEXPORT jstring JNICALL
    Java_com_lucky_jnidemo_MainActivity_stringFromJNI(
            JNIEnv* env,
            jobject /* this */) {
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
    

    1. extern "C" 说明

    表示下面的代码,都采用C的编译方式。

    之所以用C的编译方式,是因为 JNIEnv 是C语言代码写的,避免一些函数重载等C语言中不支持的

    查看JNIEnv源码,进入到jni.h,可以看到:

    #if defined(__cplusplus)
    typedef _JNIEnv JNIEnv; // 如果是C++代码,用这个宏定义
    typedef _JavaVM JavaVM;
    #else
    typedef const struct JNINativeInterface* JNIEnv;  //如果是C代码,用这个宏定义
    typedef const struct JNIInvokeInterface* JavaVM;
    #endif
    

    继续跟踪 _JNIEnv,可以看到:

    struct _JNIEnv {
        /* do not rename this; it does not seem to be entirely opaque */
        const struct JNINativeInterface* functions;
    

    因此,env的调用方式如下:

    // C++的情况如下: 
    Java_com_lucky_jnidemo_MainActivity_stringFromJNI(JNIEnv * env, ...) 
    {
       env->NewStringUTF();  //详细看源码中的结构体定义
    }
    
    C的情况如下: 
    Java_com_lucky_jnidemo_MainActivity_stringFromJNI(JNIEnv * env, ...) 
    { 
      (*env)->NewStringUTF(); //二级指针
    }
    

    2. JNIEXPORT 宏定义说明

    针对Linux平台:该声明的作用是保证在本动态库中声明的方法 , 能够在其他项目中可以被调用 ;

    3. JNICALL 宏定义说明

    此宏定义为空,用来表示函数的调用规范。

    4. jobjectjclass

    jobject // java传递下来的对象,就是本项目中 MainActivity 对象 
    jclass // java传递下来的 class 对象,就是本项目中的 MainActivity class
    

    2. JNI中函数操作

    1. 在C层中修改Java/Kotlin层String变量(引用类型遍历)

    Kotlin代码:

    //即将要修改的代码熟悉
    private var mName: String = "Lucky" 
    
    //native 修改String name
    external fun changeStringName()
    
    //点击按钮进行修改
    binding.btn1.setOnClickListener {
        Log.i("MainActivity", "修改前:$mName")
        changeStringName()
        Log.i("MainActivity", "修改后:$mName")
    }
    

    C层中的代码

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_lucky_jnidemo_MainActivity_changeStringName(JNIEnv *env, jobject thiz) {
    
        //获取Class方式一
        jclass mainActivityCls = env->FindClass("com/lucky/jnidemo/MainActivity");
    
        //获取Class方式二
        //jclass mainActivityCls = env->GetObjectClass(thiz);
    
        // jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
        jfieldID nameFid = env->GetFieldID(mainActivityCls, "mName", "Ljava/lang/String;");
    
        // void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
        jstring value = env->NewStringUTF("Update Lucky");
    
        //void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
        env->SetObjectField(thiz, nameFid, value); //修改值
    }
    

    打印结果:

    com.lucky.jnidemo I/MainActivity: 修改前:Lucky
    com.lucky.jnidemo I/MainActivity: 修改后:Update Lucky
    

    2. 在C层中修改Kotlin层静态Int变量

    通过apk查看编译后的MainActivity 类,可以看到静态mAege是在MainActivity 中

    image.png

    因此,这里跟修改MainActivity中的变量一样的操作

    Kotlin代码:

    companion object {
        private var mAge: Int = 666
        init {
            System.loadLibrary("jnidemo")
        }
        external fun changeIntAge() //修改Int age
    }
    
    
    binding.btn2.setOnClickListener {
        Log.i("MainActivity", "修改前:${mAge}")
        changeIntAge()
        Log.i("MainActivity", "修改后:${mAge}")
    }
    

    C层中的代码

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_lucky_jnidemo_MainActivity_00024Companion_changeIntAge(JNIEnv *env, jobject thiz) {
    
        // 这里要注意用FindClass方式获取,不然拿到的jcalss会是 "com/lucky/jnidemo/MainActivity$Companion"
        jclass mainActivityCls = env->FindClass("com/lucky/jnidemo/MainActivity");
    
        //jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
        jfieldID ageFid = env->GetStaticFieldID(mainActivityCls, "mAge", "I");
        if (ageFid == NULL) {
            LOGI("%s", "no field  mAge ");
            return;
        }
        // jint 背后就是int,所以可以直接用,  但是String 必须用 jstring
        int age = env->GetStaticIntField(mainActivityCls, ageFid); // 获取之前的age
        //  void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value)
        env->SetStaticIntField(mainActivityCls, ageFid, age + 10); //修改值+10
    
    }
    

    打印结果:

    com.lucky.jnidemo I/MainActivity: 修改前:666
    com.lucky.jnidemo I/MainActivity: 修改后:676
    

    3. 在C层中修改Kotlin层double变量

    kotlin层代码:

    private var mHeight: Double = 175.0
    
    binding.btn3.setOnClickListener {
        Log.i("MainActivity", "修改前:${mHeight}")
        changeDoubleHeight()
        Log.i("MainActivity", "修改后:${mHeight}")
    }
    
    external fun changeDoubleHeight() //修改Double height
    

    C层中的代码

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_lucky_jnidemo_MainActivity_changeDoubleHeight(JNIEnv *env, jobject thiz) {
    
        jclass mainActivityCls = env->GetObjectClass(thiz);
    
        // jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
        jfieldID numberFid  = env->GetFieldID(mainActivityCls, "mHeight", "D");
    
        // void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value)
        env->SetDoubleField(thiz, numberFid, 178.55);
    
    }
    

    打印结果:

    com.lucky.jnidemo I/MainActivity: 修改前:175.0
    com.lucky.jnidemo I/MainActivity: 修改后:178.55
    

    4. 在C层中调用Kotlin层方法

    kotlin层代码:

    /***
     * "(II)I"
     * 被C层调用的方法
     */
    fun add(number1: Int, number2: Int): Int {
        return number1 + number2
    }
    
    /***
     * 被C调用的方法 (Ljava/lang/String;I) Ljava/lang/String;
     */
    fun showString(str: String, num: Int): String {
        Log.i("MainActivity", "Value from C++ str: $str  num: $num")
        return "【$str】"
    }
    
    external fun callKotlinMethod() //在C层中调用Kotlin代码
    
    binding.btn4.setOnClickListener {
        callKotlinMethod() //点击按钮
    }
    

    C层中的代码

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_lucky_jnidemo_MainActivity_callKotlinMethod(JNIEnv *env, jobject thiz) {
    
        jclass mainActivitCls = env->GetObjectClass(thiz);
    
        // jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
        jmethodID addMid = env->GetMethodID(mainActivitCls, "add", "(II)I"); //kotlin层 fun add(number1: Int, number2: Int): Int
    
        // jint CallIntMethod(jobject obj, jmethodID methodID, ...)
        int result = env->CallIntMethod(thiz, addMid, 10, 10); //调用方法
        LOGD("result:%d\n", result);
    
        // ++++++++++++++++++++++ C调用 fun showString(str: String, num: Int): String 函数
        jmethodID showStringMid = env->GetMethodID(mainActivitCls, "showString", "(Ljava/lang/String;I)Ljava/lang/String;");
    
        // jobject     (*CallObjectMethod)(jobject, jmethodID, ...);
        jstring value = env->NewStringUTF("C语言调用Kotlin");
        jstring resultStrJ = (jstring) env->CallObjectMethod(thiz, showStringMid, value, 10086);
        const char *resultStr = env->GetStringUTFChars(resultStrJ, NULL);
        LOGD("result2:%s\n", resultStr);
    }
    

    打印结果:

    //C中的打印
    com.lucky.jnidemo D/Lucky: result:20
    com.lucky.jnidemo D/Lucky: result2:【C语言调用Kotlin】
    
    //Kotlin中的打印
    com.lucky.jnidemo I/MainActivity: Value from C++ str: C语言调用Kotlin  num: 10086
    

    5. 在C层中修改Kotlin层数组

    kotlin层代码:

    binding.btn5.setOnClickListener {
        val ints = intArrayOf(1, 2, 3, 4, 5, 6)
        val strs = arrayOf("李小龙", "李连杰", "李元霸")
        testArrayAction(99, "你好", ints, strs)
    
        for (anInt in ints) {
            Log.d("MainActivity", "(Kotlin)修改后: ints:$anInt")
        }
    
        for (str in strs) {
            Log.e("MainActivity", "(Kotlin)修改后: strs:$str")
        }
    }
    
    //在C层中修改数组
    external fun testArrayAction(count: Int, textInfo: String, ints: IntArray, strs: Array<String>) 
    

    C层中的代码

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_lucky_jnidemo_MainActivity_testArrayAction(JNIEnv *env, jobject thiz, jint count, jstring text_info, jintArray ints, jobjectArray strs) {
    
        //++++++++++++++++++++++++++ 操作Int数组
        LOGD("count:%d\n", count);
        const char *_textInfoStr = env->GetStringUTFChars(text_info, NULL);
        LOGD("text_info:%s\n", _textInfoStr);
        env->ReleaseStringUTFChars(text_info, _textInfoStr); //释放
    
        int intsLen = env->GetArrayLength(ints); //获取数组个数
    
        for (int i = 0; i < intsLen; ++i) {
            jint *_ints = env->GetIntArrayElements(ints, NULL);
            *(_ints + i) = (i + 1000001); //修改数组的值
            LOGD("C++ _ints item:%d\n", *(_ints + i));
            // JNI_OK 0 == 代表 先用操纵杆刷新到JVM,JVM会更新上层代码。再释放C++层数组
            // JNI_COMMIT 1 == 代表 用操纵杆刷新到JVM,JVM会更新上层代码
            // JNI_ABORT 2 == 代表 释放C++层数组
            env->ReleaseIntArrayElements(ints, _ints, JNI_OK); //在数组循环中,一定要记得释放
        }
    
        //++++++++++++++++++++++++++ 操作String数组
        int strsLen = env->GetArrayLength(strs);
        for (int i = 0; i < strsLen; ++i) {
            jobject item = env->GetObjectArrayElement(strs, i); //获取每一个值
            jstring itemStr = (jstring) item;
            const char *itemStr_1 = env->GetStringUTFChars(itemStr, NULL);
            LOGI("C++ 修改前itemStr_:%s\n", itemStr_1);
            env->ReleaseStringUTFChars(itemStr, itemStr_1);
    
            jstring value = env->NewStringUTF("AAAAAAA");
            env->SetObjectArrayElement(strs, i, value); //修改数组值
            jobject item2 = env->GetObjectArrayElement(strs, i);
            jstring itemStr2 = (jstring) item2;
            const char *itemStr_2 = env->GetStringUTFChars(itemStr2, NULL);
            LOGI("C++ 修改后itemStr_2:%s\n", itemStr_2);
            env->ReleaseStringUTFChars(itemStr2, itemStr_2);
        }
    }
    

    打印日志:

    com.lucky.jnidemo D/Lucky: count:99
    com.lucky.jnidemo D/Lucky: text_info:你好
    com.lucky.jnidemo D/Lucky: C++ _ints item:1000001
    com.lucky.jnidemo D/Lucky: C++ _ints item:1000002
    com.lucky.jnidemo D/Lucky: C++ _ints item:1000003
    com.lucky.jnidemo D/Lucky: C++ _ints item:1000004
    com.lucky.jnidemo D/Lucky: C++ _ints item:1000005
    com.lucky.jnidemo D/Lucky: C++ _ints item:1000006
    com.lucky.jnidemo I/Lucky: C++ 修改前itemStr_:李小龙
    com.lucky.jnidemo I/Lucky: C++ 修改后itemStr_2:AAAAAAA
    com.lucky.jnidemo I/Lucky: C++ 修改前itemStr_:李连杰
    com.lucky.jnidemo I/Lucky: C++ 修改后itemStr_2:AAAAAAA
    com.lucky.jnidemo I/Lucky: C++ 修改前itemStr_:李元霸
    com.lucky.jnidemo I/Lucky: C++ 修改后itemStr_2:AAAAAAA
    com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000001
    com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000002
    com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000003
    com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000004
    com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000005
    com.lucky.jnidemo D/MainActivity: (Kotlin)修改后: ints:1000006
    com.lucky.jnidemo E/MainActivity: (Kotlin)修改后: strs:AAAAAAA
    com.lucky.jnidemo E/MainActivity: (Kotlin)修改后: strs:AAAAAAA
    

    6. 在C层中修改Kotlin层类

    修改实体类,我们这里用Java类,这样修改int 变量不需要kotlin中这么麻烦,需要转换Integer

    kotlin层代码:

    Person类

    public class Person {
    
        private String name;
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    binding.btn6.setOnClickListener {
        val person = Person()
        person.name = "张三"
        person.age = 18
        putObject(person, "Kotlin文本")
        Log.i("MainActivity", "(Kotlin)修改后, person:$person")
    }
    
    // 只玩Student对象里面的成员
    external fun putObject(person: Person, str: String) // 传递引用类型,传递对象
    

    C层中的代码

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_lucky_jnidemo_MainActivity_putObject(JNIEnv *env, jobject thiz, jobject person, jstring str) {
    
        const char *_str = env->GetStringUTFChars(str, NULL);
        LOGD("_str:%s\n", _str);
        env->ReleaseStringUTFChars(str, _str);
        jclass mStudentClass = env->GetObjectClass(person);
        // toString
        jmethodID toStringMethod = env->GetMethodID(mStudentClass, "toString", "()Ljava/lang/String;");
        jstring results = (jstring) env->CallObjectMethod(person, toStringMethod);
        const char *result = env->GetStringUTFChars(results, NULL);
        LOGD("C++ toString:%s\n", result);
        env->ReleaseStringUTFChars(results, result);
    
        // setName
        jmethodID setNameMethod = env->GetMethodID(mStudentClass, "setName", "(Ljava/lang/String;)V");
        jstring nameS = env->NewStringUTF("李四");
        env->CallVoidMethod(person, setNameMethod, nameS);
    
        // getName
        jmethodID getNameMethod = env->GetMethodID(mStudentClass, "getName", "()Ljava/lang/String;");
        jstring nameS2 = (jstring) env->CallObjectMethod(person, getNameMethod);
        const char *nameS3 = env->GetStringUTFChars(nameS2, NULL);
        LOGD("C++ getName:%s\n", nameS3);
        env->ReleaseStringUTFChars(nameS2, nameS3);
    
        // setAge
        jmethodID setAgeMethod = env->GetMethodID(mStudentClass, "setAge", "(I)V");
        env->CallVoidMethod(person, setAgeMethod, 99);
    
        // getAge
        jmethodID getAgeMethod = env->GetMethodID(mStudentClass, "getAge", "()I");
        int age = env->CallIntMethod(person, getAgeMethod);
        LOGD("C++ getAge:%d\n", age);
    
        env->DeleteLocalRef(mStudentClass);
    }
    

    7. 在C层中创建类

    Student类

    public class Student {
        private String name;
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

    Person 类

    public class Person {
    
        private String name;
        private int age;
        private static Student student;
    
        public static void putStudent(Student student) {
            Person.student = student;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public Student getStudent() {
            return student;
        }
    
        public void setStudent(Student student) {
            Person.student = student;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", student=" + student +
                    '}';
        }
    }
    

    kotlin层中的代码

    binding.btn7.setOnClickListener {
        insertObject()
    }
    external fun insertObject()
    

    C层中的代码

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_lucky_jnidemo_MainActivity_insertObject(JNIEnv *env, jobject thiz) {
    
        // @Student对象
        jclass studentClass = env->FindClass("com/lucky/jnidemo/Student");
        jobject student = env->AllocObject(studentClass);
    
        // setName
        jmethodID setNameMethod = env->GetMethodID(studentClass, "setName", "(Ljava/lang/String;)V");
        jstring value1 = env->NewStringUTF("张三");
        env->CallVoidMethod(student, setNameMethod, value1);
    
        // setAge
        jmethodID setAgeMethod = env->GetMethodID(studentClass, "setAge", "(I)V");
        env->CallVoidMethod(student, setAgeMethod, 99);
    
        // @Person对象
        jclass personClass = env->FindClass("com/lucky/jnidemo/Person");
        jobject person = env->AllocObject(personClass); // C++ 分配一个对象出来,不会调用此对象的构造函数
        // env->NewObject();  // C++ 实例化一个对象出来,会调用此对象的构造函数,相当于: new XXX();
    
        // void CallVoidMethod(person obj, jmethodID methodID, ...)
        jmethodID setStudent = env->GetMethodID(personClass, "setStudent", "(Lcom/lucky/jnidemo/Student;)V");
        env->CallVoidMethod(person, setStudent, student);
    
        // static void putStudent
        jmethodID putStudent = env->GetStaticMethodID(personClass, "putStudent", "(Lcom/lucky/jnidemo/Student;)V");
        env->CallStaticVoidMethod(personClass, putStudent, student);
    
        // TODO 释放工作
        // 释放方式一
        env->DeleteLocalRef(personClass);
        env->DeleteLocalRef(student);
        env->DeleteLocalRef(value1);
        env->DeleteLocalRef(personClass);
        env->DeleteLocalRef(person);
        // 释放方式二
        /*env->GetStringUTFChars()
        env->ReleaseStringUTFChars()*/
    
        // 释放方式三
        /*new StudentCPP对象
        delete StudentCPP对象*/
    }
    

    GitHub源码:MyJniDemo

    相关文章

      网友评论

          本文标题:Android NDK 开发之JNI常规操作

          本文链接:https://www.haomeiwen.com/subject/iilcirtx.html