JNI和java层互相调用学习

代码可以直接复制到项目里运行学习

package com.example.zhuangtest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.example.zhuangtest.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    val name = "changeString"
    val age = 29

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Example of a call to a native method
        changeName()
        changeAge()
        callAddMethod()
        binding.sampleText.text = age.toString()
        testo1()
        testo2()
        testo3()
        test04()
        test05()
    }

    fun testo1() {
        var intArray = intArrayOf(1,2,3,4,5,6)
        var stringArrayy = arrayOf("李小龙", "李连杰" , "李元霸")

        testArrayAction(99,"你好", intArray, stringArrayy)

        for (i in intArray) {
            Log.v("java层数据", "$i")
        }
    }

    fun testo2() {
        var student = Student()
        student.name = "史泰龙"
        student.age = 88
        putObject(student, "九阳神功")
    }

    fun testo3() {
        insertObject()
    }

    fun test04() {
        quote()
    }

    fun test05() {
        delQuote()
    }

    /**
     * A native method that is implemented by the 'zhuangtest' native library,
     * which is packaged with this application.
     */
    external fun stringFromJNI(): String
    external fun getStringPwd(): String
    // 引用类型
    external fun changeName()
    // 基本数据类型
    external fun changeAge()
    // 给jni调用
    external fun callAddMethod()
    // String引用类型  数组
    external fun testArrayAction(count : Int, textInfo: String, ints: IntArray, strs: Array<String>)
    // 类类型引用 Student
    external fun putObject(student: Student, str : String)
    // 凭空构建对象,没有传递对象给JNI
    external fun insertObject()
    // JNI验证构造函数的调用和全局或局部引用
    external fun quote()
    // 释放全局引用
    external fun delQuote()

    // 给native调用
    fun add(n1: Int,  n2 : Int) = n1 + n2

    /**
     *  属性签名规则
     *  java的boolean ----------- Z
     *  java的byte ----------- B
     *  java的char ----------- C
     *  java的short ----------- S
     *  java的Int ----------- I
     *  java的Long ----------- L
     *  java的float ----------- F
     *  java的double ----------- D
     *  java的void ----------- V
     *  java的引用类型 ----------- Lxxx/xxxx/类名;
     *  java的Strding ----------- Ljava/lang/String;
     *  java的array  int[]  ----------- [I             double[][][]  [[[D
     *
     *  javap -s -p xxx.class 输出全部属性的签名
      */


    companion object {
        // Used to load the 'zhuangtest' library on application startup.
        init {
            // System.load("D:/xxx/xxxx.so")  绝对路劲加载so库


            // 这种是从库目录遍历层级目录,去自动查找 apk里面的lib/libzhuangtest.so
            System.loadLibrary("zhuangtest")
        }
    }
}
package com.example.zhuangtest

import android.util.Log

class Dog {
    constructor(n1 : Int) { // "<init>" JIN中调用构造函数的名字都是<init>,它靠签名去确定的
        Log.v("Dog", "n1")
    }

    constructor(n1 : Int, n2 : Int) { // "<init>"
        Log.v("Dog", "n1 + n2")
    }

    constructor(n1 : Int, n2 : Int, n3 : Int) { // "<init>"
        Log.v("Dog", "n1 + n2 + n3")
    }
}
package com.example.zhuangtest

import android.util.Log

class Person {
    var student : Student ? = null
    set(value) {
        Log.v("Person call setStudent ", value.toString());
    }

    companion object {
        @JvmStatic
        fun putStudent(student: Student) {
            Log.v("Person call putStudent ", student.toString());
        }
    }
}
package com.example.zhuangtest

import android.util.Log

class Student {
    var name : String ? = null
    var age = 0

    companion object {
        @JvmStatic
        fun showInfo(info: String) {
            Log.v("Student", info)
        }
    }

    override fun toString(): String {
        return "Student(name=$name, age=$age)"
    }
}

默认创建的:native-lib.cpp

#include <jni.h>
#include <string>

// NDK工具库里面的log库
#include <android/log.h>

#define TAG "JNI_TEST"

// ... 我都不知道传入什么,借助JNI里面的宏来自动帮我填充 __VA_ARGS__
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)

using namespace std;

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_zhuangtest_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    string hello = "Hello from C++";
    const char * name = hello.c_str();
    name = "test jni";
    return env->NewStringUTF(name);
}

extern "C" // 无论是C或者是C++,最后全部都是采用C的方式,所以采用C的标准
JNIEXPORT  // 标记该方法可以被外部调用,某些平台上可以不加,为了兼容最好加上
jstring    // java <---> native 转换用的,jni层对string的封装
JNICALL    // 代表是JNI的标记,可以少
// 包名_类名_方法名  注意: 我们的包名_   native_1
// jobject thiz  谁调用,就是谁的实例
Java_com_example_zhuangtest_MainActivity_getStringPwd(JNIEnv *env, jobject thiz) {

}


extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_changeName(JNIEnv *env, jobject thiz) {
    // 获取class
    jclass j_cls = env->GetObjectClass(thiz);

    // 获取属性
    // jfieldID GetFieldID(MainActivity.class, 属性名, 属性的签名)
    jfieldID j_fid = env->GetFieldID(j_cls, "name", "Ljava/lang/String;");

    //通过属性名或者变量
    jstring j_str = static_cast<jstring>(env->GetObjectField(thiz, j_fid));

    // 打印字符串
    char * c_str = const_cast<char *>(env->GetStringUTFChars(j_str, NULL));


    LOGI("jni : %s", c_str);
    // printf() C  cout << <<endl; C++

    //修改成 Beyond
    jstring  jName = env->NewStringUTF("Beyond");
    env->SetObjectField(thiz, j_fid, jName);

}


extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_changeAge(JNIEnv *env, jobject thiz) {
    // 跟反射差不多,没什么好说的,直接用,这个东西写多几次自然而然就了解了。
    jclass j_cls = env->GetObjectClass(thiz);
    jfieldID j_fid = env->GetFieldID(j_cls, "age", "I");
    jint age = env->GetIntField(thiz, j_fid);
    age += 10;
    env->SetIntField(thiz, j_fid, age);
}

// 调用java层的函数
extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_callAddMethod(JNIEnv *env, jobject job) {
    jclass j_cls = env->GetObjectClass(job);
    // 两个int类型的入参和一个int的返回参数 "(II)I"
    jmethodID j_method = env->GetMethodID(j_cls, "add", "(II)I");
    int sum = env->CallIntMethod(job, j_method, 10, 10);
    LOGE("sum result : %d\n",sum);
}

// jint == int
// jstring == String
// jintArray == int[]
// jobjectArray == 引用类型数组
extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_testArrayAction(JNIEnv *env, jobject thiz, jint count,
                                                         jstring text_info, jintArray ints,
                                                         jobjectArray strs) {
    // int 和 String ==> jint jstring
    int countInt = count; // jint 本质上是int,所以可以用int接收,int是它的顶层父类
    LOGI("参数一 count:%d\n", countInt);

    // 操作杆 --> JMV  env->
    const char * textInfo = env->GetStringUTFChars(text_info, NULL);
    LOGI("参数二 textInfo:%s\n", textInfo);

    // 把jint[] 转成 int*
    jint * jintArray = env->GetIntArrayElements(ints, NULL);

    //java 层数组的长度
    // jsize GetArrayLength(jarray array)
    // 接收jarray 我们传入了一个 jintArray ,因为jintArray的父类也是_jarray
    jsize size = env->GetArrayLength(ints);

    for(int i = 0; i < size; ++i) {
        *(jintArray+i) += 100;
        LOGI("参数三 int[]:%d\n", *jintArray+i);
    }
    // 直接修改指针所对应的值 java层打印,发现没修改成功,还是原来的123456.
    // java --> JVM(JNI) --> C/C++  我们直接修改C/C++层,发现java层显示的还是原来的值,说明这中间肯定有赋值操作有另外的引用。
    // 类似这种情况,我们修改之后还需要通过env进行刷新  env操作杆告诉JVM

    /**
     * 第一个入参是原来的jinArray数组,第二个是修改后的数组
     * 第三个刷新类型:
     *  0:           刷新java数组,并释放C++层数组。
     *  JIN——COMMIT : 只提交,只刷新java层数组,不释放C++层数组
     *  JIN——ABORT:    只释放C++层数组
     *  基本上我们经常用到的是 0 ,其它两种几乎用不到。
     */

    // 刷新之后java层打印就是我们修改后预期的数字了
    env->ReleaseIntArrayElements(ints, jintArray, 0);

    // jobjectArray 代表是java层的引用类型,不一样 ,注意长度一定要经过env从JVM获取到java层的长度
    jsize strsize = env->GetArrayLength(strs);
    for(int i = 0; i < strsize; i++) {
        // 我们知道是string,强制转换
        jstring jobj = static_cast<jstring>(env->GetObjectArrayElement(strs, i));
        // jstring 转成 char * C++层的
        const char * jobjCharp = env->GetStringUTFChars(jobj, NULL);
        LOGI("参数四 引用类型String 具体的: %s\n", jobjCharp);

        //释放jstring 不写也没问题,最好是写。
        env->ReleaseStringUTFChars(jobj, jobjCharp);
    }


}

// jobject student == Student
// jstring str == String

extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_putObject(JNIEnv *env,
                                                   jobject thiz,
                                                   jobject student,
                                                   jstring str) {

    const char * strChar = env->GetStringUTFChars(str, NULL);
    LOGI("strChar: %s\n", strChar);
    // 释放
    env->ReleaseStringUTFChars(str, strChar);
    // 第一种方式,编译器非常智能,会自动填充
//    jclass  studentClass = env->FindClass("com/example/zhuangtest/Student");
    // 第二种
    jclass studentClass = env->GetObjectClass(student);

    // Student 类里面的函数规则 签名
    // 编译器非常智能,第二个参数例如写了:setName,第三个参数签名会自动填充修正,但是基本的规则我们还是需要了解的
    jmethodID  setName = env->GetMethodID(studentClass, "setName","(Ljava/lang/String;)V");
    jmethodID  getName = env->GetMethodID(studentClass, "getName", "()Ljava/lang/String;");
    jmethodID  showInfo = env->GetStaticMethodID(studentClass, "showInfo","(Ljava/lang/String;)V");

    // 调用setName
    jstring value = env->NewStringUTF("AAAA");
    env->CallVoidMethod(student, setName, value);
    // 调用getName
    jstring getNameResult = static_cast<jstring>(env->CallObjectMethod(student, getName));
    // 直接打印是不行的,会报错,getNameResult是JNI层的,打印这里是C++,需要的是指针。
    // LOGI("调用到getNmae方法,值是:%s\n", getNameResult);
    const char * getNameValue = env->GetStringUTFChars(getNameResult, NULL);
    LOGI("调用到getNmae方法,值是:%s\n", getNameValue);
    // 调用showInfo
    jstring jstringInfo = env->NewStringUTF("静态方法你好,我是C++");
    env->CallStaticVoidMethod(studentClass, showInfo, jstringInfo);


}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_insertObject(JNIEnv *env, jobject thiz) {
    // 1、通过包名 + 类名的方式 拿到 Student class
    const char * studentstr = "com/example/zhuangtest/Student";
    jclass studentClass = env->FindClass(studentstr);

    // 2、通过Student 的class 实例化Student对象,C++ new Student
    // AllocObject 只实例化对象,不会调用对象的构造函数
    jobject studentObject = env->AllocObject(studentClass);

    // 方法的规则定义
    jmethodID setNmae = env->GetMethodID(studentClass, "setName", "(Ljava/lang/String;)V");
    jmethodID setAge = env->GetMethodID(studentClass, "setAge", "(I)V");

    // 调用方法
    jstring strValue = env->NewStringUTF("无");
    env->CallVoidMethod(studentObject, setNmae, strValue);
    env->CallVoidMethod(studentObject, setAge, 99);

    // =================================== 下面是Person 对象

    const char * persontstr = "com/example/zhuangtest/Person";
    jclass personClass = env->FindClass(persontstr);
    jobject personObject = env->AllocObject(personClass);

    jmethodID setStudent = env->GetMethodID(personClass, "setStudent", "(Lcom/example/zhuangtest/Student;)V");
    jmethodID putStudent = env->GetStaticMethodID(personClass, "putStudent","(Lcom/example/zhuangtest/Student;)V");

    env->CallVoidMethod(personObject, setStudent, studentObject);
    env->CallStaticVoidMethod(personClass, putStudent, studentObject);

    // 释放 第一类
    env->DeleteLocalRef(personClass);
    env->DeleteLocalRef(studentClass);
    env->DeleteLocalRef(personObject);
    env->DeleteLocalRef(studentObject);

    // 第二类
    // env->GetStringUTFChars()
    // env->ReleaseStringUTFChars()

    // jobject jstring jclass 这里都属于局部引用,函数结束后会自动释放,养成好习惯用完马上释放,内存性能更加。
}

// C++ 有堆 、栈
// JNI函数 局部引用 全局引用 它属于JVM中的一块
jclass dogClass; // 这个实际上是局部引用

extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_quote(JNIEnv *env, jobject thiz) {
    if (NULL == dogClass) {
//        const char * dogStr = "com/example/zhuangtest/Dog";
//        dogClass = env->FindClass(dogStr);
        // 升级为全局引用,需要手动释放 相当于C++ new 对象
        const char * dogStr = "com/example/zhuangtest/Dog";
        jclass temp = env->FindClass(dogStr);
        dogClass = static_cast<jclass>(env->NewGlobalRef(temp));
    }

    // "<init>"  签名返回对象V 是不会变的,构造函数是不会有返回值的
    jmethodID init1 = env->GetMethodID(dogClass, "<init>", "(I)V");
    jmethodID init2 = env->GetMethodID(dogClass, "<init>", "(II)V");
    jmethodID init3 = env->GetMethodID(dogClass, "<init>", "(III)V");
    jobject dog1 = env->NewObject(dogClass, init1, 100);
    jobject dog2 = env->NewObject(dogClass, init2, 100, 200);
    jobject dog3 = env->NewObject(dogClass, init3, 100, 200, 300);
    env->DeleteLocalRef(dog1);
    env->DeleteLocalRef(dog2);
    env->DeleteLocalRef(dog3);
    // 函数结束释放 dogClass 如果调用两次,第二次进不了if会崩溃,dogClass虽然被释放了,但是不等于NULL,是一个悬空指针。
}

extern int age; // 声明
extern void show();// 声明 show 函数

extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_delQuote(JNIEnv *env, jobject thiz) {
    if (dogClass != NULL) {
        LOGI("全局引用释放完毕");
        env->DeleteLocalRef(dogClass);
        dogClass = NULL; //不要去成为悬空指针
    }

    // 直接调用,实现在另外的文件 test.cpp
    show();
}

记得CMakeLists.txt里面要加上 native-lib.cpp test.cpp ,有两个cpp文件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JNIJava Native Interface)是一种技术,它可以让Java程序与C(或C++)程序进行互相调用。 在JNI中,我们可以使用Java的native关键字声明一个方法,该方法的具体实现在C(或C++)语言中完成。然后,我们可以通过JNI提供的接口函数,在Java和C(或C++)之间进行数据和方法的传递。 在Java代码中,我们首先需要使用System.loadLibrary()方法加载C(或C++)动态链接库。然后,通过Java的native方法调用C(或C++)中的函数,实现Java与C(或C++)的互相调用。 在C(或C++)代码中,我们需要编写与Java声明的native方法对应的具体实现代码。可以使用JNI提供的接口函数,通过JNIEnv结构体来访问Java的对象、方法和属性。 Java与C(或C++)之间的数据传递可以通过JNI提供的接口函数来完成。我们可以使用JNI的函数将Java的基本数据类型与C(或C++)的对应类型进行转换,例如将int转换为jint、jint转换为int等等。同时,我们也可以使用JNI函数来处理复杂的数据结构,例如将Java的字符串转换为C(或C++)的字符串,或将Java的数组转换为C(或C++)中的数组。 在实际应用中,JNI可以用于优化性能,因为C(或C++)代码相对于Java代码可以更高效地执行某些操作。同时,JNI也可以用于与现有的C(或C++)库进行集成,以利用已有的功能。 总之,JNI提供了一种机制,可以在Java和C(或C++)之间实现互相调用,使得我们可以充分利用两者各自的优势,在应用开发中更加灵活和高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值