Android JNI 进阶(含完整 Demo)

本文详细介绍了Android JNI中如何进行Java向native传递基本数据类型和数组,包括静态/动态方法调用引用类型,并展示了如何在native层构造函数调用和使用全局引用。通过实例演示了JNI方法的实现过程,以及可能出现的问题和解决方案,如局部引用和全局引用的管理。
摘要由CSDN通过智能技术生成

上一章:Android JNI 入门(含完整Demo)

native 接收 java 传递基本数据和数组

定义 native:

 //基本类型调用
public native void nativeBasicTypes(int arg0, double arg2, boolean arg3, String arg4, String[] arg5, int[] arg6);

定义点击事件:

public void onClick1(View view) {
        String strarray[] = {"成龙", "李小龙", "松井宝", "迪丽热巴", "ohh"};
        int ints[] = {1, 4, 2, 62, 61};
        nativeBasicTypes(42, 24.42, true, "李元霸", strarray, ints);

        for (int i = 0; i < ints.length; i++) {
            Log.i("szjjava 层打印", ints[i] + "");
        }
    }

native 代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_jni_activity_AdvancedActivity_nativeBasicTypes(JNIEnv *env, jobject thiz,
                                                                jint arg0, jdouble arg2,
                                                                jboolean arg3, jstring arg4,
                                                                jobjectArray arg5,
                                                                jintArray arg6) {

    // 接收int类型
    int j_int_arg0 = arg0;
    LOGE("参数一为%d", j_int_arg0);

    // 接收double类型
    double j_double_arg2 = arg2;
    LOGE("参数二为%f", j_double_arg2);
    
     // 接收bool类型  1=true 0=false
    int j_bool_arg3 = arg3;
    LOGE("参数三为%d", j_bool_arg3);

    //接收 String 类型的值
	//    const char* GetStringUTFChars(jstring string, jboolean* isCopy)
    char *j_string = const_cast<char *>(env->GetStringUTFChars(arg4, NULL));
    LOGE("参数四为%s", j_string);

    //接收 String[]类型的值
    // jsize GetArrayLength(jarray array)
    jsize j_size = env->GetArrayLength(arg5);
    for (int i = 0; i < j_size; i++) {
    
		//jobject GetObjectArrayElement(jobjectArray array, jsize index)
        jstring j_string2 = static_cast<jstring>(env->GetObjectArrayElement(arg5, i));

        //修改为 C++认识的 char*类型
        const char *jstr = env->GetStringUTFChars(j_string2, NULL);
        LOGE("参数五为%s", jstr);

        //释放 jstring
        env->ReleaseStringChars(j_string2, reinterpret_cast<const jchar *>(jstr));
    }

    //int[] 元素打印
    jint *j_ints = env->GetIntArrayElements(arg6, NULL);
    jsize j_size_ints = env->GetArrayLength(arg6);
    for (int i = 0; i < j_size_ints; i++) {
        *(j_ints + i) += 100;
        LOGE("第六个参数为:%d", *(j_ints + i))
    }
    //返回修改后的数组给 java 层
    env->ReleaseIntArrayElements(arg6, j_ints, 0);
}

简单代码就不说了,只说一下陌生的;

String类型解释:

  • java -> String
  • JNI -> jstring
  • C++ -> char*

想要吧 java 的 String 转换为 C++认识的 char*就需要用到:

char * j_string = const_cast<char *>(env->GetStringUTFChars(arg4, NULL));

GetObjectArrayElement解释:

在JNI 中只有基本类型和引用类型,String 是引用类型,所以遍历 String[]的时候,采用GetObjectArrayElement()来接收

  • 参数一:jobjectArray:所有的元素
  • 参数二:jsize:当前的位置

ReleaseIntArrayElements解释:

ReleaseIntArrayElements()方法是 C++通知 JNI 改变 java 的值,在代码中 java层 传递过来一个数组,然后修改完成后再返回给 java 层

  • 参数一:jintArray:java 传递过来的数组
  • 参数二:修改后的数组
  • 参数三:修改的模式,填 0 就好

ReleaseStringChars解释:

ReleaseStringChars是用来释放 JNI 内存的,虽然说不释放也可以,因为在方法结束的时候会弹栈,一旦弹栈就会自动释放内存,但是如果说一个方法中有 5000 行代码,在一直不释放,等待到 JNI 弹栈自动释放,那么就会引起 JNI 在调用该方法的时候占用空间略大(养成好习惯)

运行结果:

native 运行结果:
2021-04-28 17:36:22.217 6501-6501/com.example.jni E/szj: 参数一为42
2021-04-28 17:36:22.217 6501-6501/com.example.jni E/szj: 参数二为24.420000
2021-04-28 17:36:22.217 6501-6501/com.example.jni E/szj: 参数三为1
2021-04-28 17:36:22.217 6501-6501/com.example.jni E/szj: 参数四为李元霸
2021-04-28 17:36:22.217 6501-6501/com.example.jni E/szj: 参数五为成龙
2021-04-28 17:36:22.217 6501-6501/com.example.jni E/szj: 参数五为李小龙
2021-04-28 17:36:22.217 6501-6501/com.example.jni E/szj: 参数五为松井宝
2021-04-28 17:36:22.217 6501-6501/com.example.jni E/szj: 参数五为迪丽热巴
2021-04-28 17:36:22.217 6501-6501/com.example.jni E/szj: 参数五为ohh
2021-04-28 17:36:22.217 6501-6501/com.example.jni E/szj: 第六个参数为:101
2021-04-28 17:36:22.217 6501-6501/com.example.jni E/szj: 第六个参数为:104
2021-04-28 17:36:22.217 6501-6501/com.example.jni E/szj: 第六个参数为:102
2021-04-28 17:36:22.217 6501-6501/com.example.jni E/szj: 第六个参数为:162
2021-04-28 17:36:22.217 6501-6501/com.example.jni E/szj: 第六个参数为:161

java 运行结果:
2021-04-28 17:36:22.217 6501-6501/com.example.jni I/szjjava 层打印: 101
2021-04-28 17:36:22.217 6501-6501/com.example.jni I/szjjava 层打印: 104
2021-04-28 17:36:22.217 6501-6501/com.example.jni I/szjjava 层打印: 102
2021-04-28 17:36:22.217 6501-6501/com.example.jni I/szjjava 层打印: 162
2021-04-28 17:36:22.217 6501-6501/com.example.jni I/szjjava 层打印: 161

引用类型 静态/动态 方法调用

java 代码:

public native void nativeMethod(Persion persion);

辅助图:
在这里插入图片描述
右侧的红框是需要调用的方法;

  • void setName()
  • void setAge()
  • static void show(String name)

native 代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_jni_activity_AdvancedActivity_nativeMethod(JNIEnv *env, jobject thiz,
                                                            jobject persion) {

    /**
     * 获取 Persion 的 class
     *  源码: jclass FindClass(const char* name)
     */
    jclass j_class = env->FindClass("com/example/jni/bean/Persion");

    /**
     * 获取方法 ID:
     * jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
     */
    jmethodID j_method_id = env->GetMethodID(j_class, "getName", "()Ljava/lang/String;");

    //获取 Persion getName方法返回值
    jstring j_strName = static_cast<jstring>(env->CallObjectMethod(persion, j_method_id));

    //修改为 C++认识的 char*类型
    char *j_char_name = const_cast<char *>(env->GetStringUTFChars(j_strName, NULL));
    LOGE("通过 getName获取值为:%s", j_char_name)


    //获取 setName的 ID
    jmethodID j_setName_id = env->GetMethodID(j_class, "setName", "(Ljava/lang/String;)V");
    //获取静态的 show 方法
    jmethodID j_show_id = env->GetStaticMethodID(j_class, "show", "(Ljava/lang/String;)V");

    env->CallVoidMethod(persion, j_setName_id, env->NewStringUTF("糖果超甜"));
    env->CallStaticVoidMethod(j_class, j_show_id, env->NewStringUTF("糖果超甜"));
}

这个方法中没有陌生方法,大家应该都认识,看不懂记得评论区留言哦~

运行结果为:

native运行结果:
2021-04-28 17:44:29.775 6501-6501/com.example.jni E/szj: 通过 getName获取值为:洪静宝

java 运行结果:
2021-04-28 17:44:29.775 6501-6501/com.example.jni I/szjjava 层 setName方法:: 糖果超甜
2021-04-28 17:44:29.775 6501-6501/com.example.jni I/szjjava 层show方法:: 糖果超甜

引用类型进阶

java 定义native方法:

 public native void nativeMethod2(Dog dog);

辅助图:
在这里插入图片描述

native 层代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_jni_activity_AdvancedActivity_nativeMethod2(JNIEnv *env, jobject thiz,
                                                             jobject dog_jobj) {

    //获取 Dog 类的 Class
    jclass j_dog_class = env->GetObjectClass(dog_jobj);

    //获取 Dog 类中的 DogShow 方法 ID
    jmethodID jmethodId = env->GetMethodID(j_dog_class, "DogShow",
                                           "(Lcom/example/jni/bean/Persion;)V");

    //获取 Persion 的 jclass
    jclass j_persion_class = env->FindClass("com/example/jni/bean/Persion");
    //获取 Persion 的 jobj //AllocObject只实例化对象,不会调用构造函数
    jobject j_persion_job = env->AllocObject(j_persion_class);

    //给 Persion 赋值
    jmethodID j_setName_id = env->GetMethodID(j_persion_class, "setName", "(Ljava/lang/String;)V");
    jmethodID j_setAge_id = env->GetMethodID(j_persion_class, "setAge", "(I)V");

    //调用 Persion 的 setName 和 setAge 给 name 和 age 赋值
    env->CallVoidMethod(j_persion_job, j_setName_id, env->NewStringUTF("蔡徐坤"));
    env->CallVoidMethod(j_persion_job, j_setAge_id, 52);

    //调用 Dog 的 DogShow(Persion)方法
    env->CallVoidMethod(dog_jobj, jmethodId, j_persion_job);

    //释放引用类型 class 和 job 都建议释放
    env->DeleteLocalRef(j_dog_class);
    env->DeleteLocalRef(j_persion_class);
    env->DeleteLocalRef(j_persion_job);
}

思路:

  • 创建 Persion 对象
  • 调用 Persion 的 setName() / setAge() 方法给 name/age 赋值
  • 创建 Dog 对象
  • 调用 Dog 对象中的 DogShow(Persion) 方法,把 Persion 传递进去即可

AllocObject解释:

AllocObject 只实例化对象,不会调用构造函数,在新创建对象(不是传递过来的)的时候,必须用这个来实例化对象,否则调用 实例化的对象不起作用

构造函数调用

java 定义 native 方法:

public native void nativeStructure(Dog dog);

native 代码:


extern "C"
JNIEXPORT void JNICALL
Java_com_example_jni_activity_AdvancedActivity_nativeStructure(JNIEnv *env, jobject thiz,
                                                               jobject dog) {

    //获取 Dog 的 jclass
    jclass j_dog_class = env->GetObjectClass(dog);

    //获取构造方法
    jmethodID id1 = env->GetMethodID(j_dog_class, "<init>", "()V");
    jmethodID id2 = env->GetMethodID(j_dog_class, "<init>", "(II)V");
    jmethodID id3 = env->GetMethodID(j_dog_class, "<init>", "(III)V");

    //调用构造方法
    env->CallVoidMethod(dog, id1);
    env->CallVoidMethod(dog, id2, 100, 200);
    env->CallVoidMethod(dog, id3, 300, 400, 500);
}

这里非常好理解:

构造方法用<init>表示即可,其余的都没变

全局引用

java 定义 native 代码:

//全局引用测试
public native void nativeAllQuote();


native代码(局部引用):

发现错误找到错误

jclass dogClass;//注意这里定义到外边也是局部引用(和 java 不一样)
extern "C"
JNIEXPORT void JNICALL
Java_com_example_jni_activity_AdvancedActivity_nativeAllQuote(JNIEnv *env, jobject thiz) {
    if (NULL == dogClass) {
        //这个是局部引用,(和 java 不同)
     dogClass = env->FindClass("com/example/jni/bean/Dog");
    }
    //获取 Dog 的 jclass
    jclass j_dog_class = env->GetObjectClass(dogClass);

    //获取构造方法
    jmethodID id1 = env->GetMethodID(dogClass, "<init>", "()V");
    jmethodID id2 = env->GetMethodID(dogClass, "<init>", "(II)V");
    jmethodID id3 = env->GetMethodID(dogClass, "<init>", "(III)V");

    //调用构造方法
    env->CallVoidMethod(j_dog_class, id1);
    env->CallVoidMethod(j_dog_class, id2, 100, 200);
    env->CallVoidMethod(j_dog_class, id3, 300, 400, 500);
//    dogClass =  NULL;//专门注释掉的!
}//因为当方法结束的时候,dogClass 会弹栈,但是dogClass还是不等于 NULL,此时只是内存地址消失,但是指针还指向内一个地址,是悬空指针状态

运行效果为:

可以看出,第二次点击按钮的时候会崩溃;

因为当方法结束的时候,dogClass 会弹栈,但是dogClass还是不等于 NULL,此时只是内存地址消失,但是指针还指向内一个地址,是悬空指针状态

解决思路:
将dogClass 转变成全局引用;

 	const char *a = "com/example/jni/bean/Dog";
        jclass temp = env->FindClass(a);
        //全局引用
        dogClass = static_cast<jclass>(env->NewGlobalRef(temp));
        //释放临时的 jclass
        env->DeleteLocalRef(temp);

辅助图:
在这里插入图片描述

再来看看运行结果:

可以看出,在怎么点击都不会出现问题了

但是:全局引用不会自动回收,必须手动回收!!!

回收代码:

if (dogClass != NULL) {
        //释放全局引用
        LOGE("释放全局引用")
        env->DeleteLocalRef(dogClass);
        dogClass = NULL;
    }

辅助图:
在这里插入图片描述

注意:

如果 so 库报错,把其他两个注释掉

同理,如果你想看 JNI 基本使用(native-simple-lib.cpp)的代码,那么吧 JNI 进阶和 QQ语音实战的代码注释掉!

现在只能有一个 cpp 文件存在
在这里插入图片描述

完整代码

其他 JNI 文章:

第一篇:Android JNI 入门(含完整Demo)

第三篇:Android JNI QQ 搞怪语音实战 (含完整 Demo)

第四篇:Andoird JNI动态注册与 JNI 线程

第五篇:JNI 异常捕获与处理

原创不易,您的点赞就是对我最大的支持~

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

s10g

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值