Android NDK——必知必会之C/C++传递各种类型数据到Java进行处理及互相调用详解(五)

版权声明:本文为CrazyMo_原创,转载请在显著位置注明原文链接 https://blog.csdn.net/CrazyMo_/article/details/82352148

引言

前一篇文章基本上动态注册和静态注册以及从Java传递各种数据到C/C++进行处理,正所谓来而不往非礼也,这篇是针对从在C/C++封装各种数据并传递到Java层。此系列文章基链接:

一、从C/C++ 语言层传递各种数据到Java层

前面我们知道JNI的类型是和Java中的类型是一一对应的,只要严格遵守规则,对于能直接映射的数据类型要传递到Java层很简单,只要C/C++创建完毕之后直接返回即可,JNI框架会自动去进行转换,繁杂之处在于准确调用对应的函数来创建并封装成复杂的数据对象。

1、向Java层传递简单基本数据类型数据

// public native int getBasicData();

extern "C"
JNIEXPORT jint JNICALL
Java_com_crazymo_ndk_jni_JNIHelper_getBasicData(JNIEnv *env, jobject instance) {
    return 888888;
}

这里写图片描述

2、向Java 层传递字符串类型数据

//public native String getStringData();

extern "C"
JNIEXPORT jstring JNICALL
Java_com_crazymo_ndk_jni_JNIHelper_getStringData(JNIEnv *env, jobject instance) {
    char* tmpstr = "从JNI发送到Java的字符串";
    jstring rtstr = env->NewStringUTF(tmpstr);
    return rtstr;
}

这里写图片描述

3、向Java层传递数组类型数据

3.1、基本数据类型的数组

//public native int[] getArrayData(int pI);

extern "C"
JNIEXPORT jintArray JNICALL
Java_com_crazymo_ndk_jni_JNIHelper_getArrayData(JNIEnv *env, jobject instance,jint len) {
    //主要调用 set<Type>ArrayRegion 来填充数据,其他数据类型类似操作
    jintArray array = NULL;
    if (len > 0) {
        array = env->NewIntArray(len);
        jint buf[len];
        for (int i = 0; i < len; ++i) {
            buf[i] = i * 2;
        }
        // 使用 setIntArrayRegion 来赋值
        env->SetIntArrayRegion(array, 0, len, buf);
        return array;
    }
    return array;
}

这里写图片描述

3.2、复杂引用类型的数组

//public native Blog[] getObject();

extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_crazymo_ndk_jni_JNIHelper_getObject(JNIEnv *env, jobject instance) {
    jobjectArray result_arr = NULL;
    jint len = 2;
    jclass blog_clz = env->FindClass("com/crazymo/ndk/bean/Blog");//获取Blog对应的jclass
    result_arr = env->NewObjectArray(len, blog_clz, NULL);
    //获取构造方法的
	jmethodID construct_methd = env->GetMethodID(blog_clz, "<init>", "(Ljava/lang/String;I)V");
    for (int i = 0; i < len; ++i) {
        //1、创建java字符串
        jstring newAddr = env->NewStringUTF("在C++创建Java Blog对象");
        const char *c_newAddr = env->GetStringUTFChars(newAddr, 0);
        //2、调用构造方法 创建对象
        jobject blog = env->NewObject(blog_clz, construct_methd, newAddr, 6666+i*100);//这里传入c_newAddr则会直接报错
        env->SetObjectArrayElement(result_arr,i,blog);
        env->ReleaseStringUTFChars(newAddr, c_newAddr);
    }
    env->DeleteLocalRef(blog_clz);
    return result_arr;
}

这里写图片描述

4、向Java层传递集合类型数据

// public native List<String> getList(int size);

JNIEXPORT jobject JNICALL
Java_com_crazymo_ndk_jni_JNIHelper_getList(JNIEnv *env, jobject instance,jint size) {
    jclass list_clz=env->FindClass("java/util/ArrayList");
    jmethodID construct_methd=env->GetMethodID(list_clz,"<init>","()V");
    jmethodID add_method=env->GetMethodID(list_clz,"add","(Ljava/lang/Object;)Z");
    jobject list_obj=env->NewObject(list_clz,construct_methd);
    jstring newAddr = env->NewStringUTF("在C++创建Java List对象");
    for (int i = 0; i < size; i++) {
        ///const char *c_newAddr = env->GetStringUTFChars(newAddr, 0);
        ///env->CallBooleanMethod(list_obj,add_method,c_newAddr);
        env->CallBooleanMethod(list_obj,add_method,newAddr);
        //env->ReleaseStringUTFChars(newAddr, c_newAddr);
    }
    env->ReleaseStringChars(newAddr,0);
    env->DeleteLocalRef(list_clz);
    return list_obj;
}

这里写图片描述

5、向Java层传递Map类型数据

//public native Map<String,String> getMap();

extern "C"
JNIEXPORT jobject JNICALL
Java_com_crazymo_ndk_jni_JNIHelper_getMap(JNIEnv *env, jobject instance) {
    jclass map_clz=env->FindClass("java/util/HashMap");
    jmethodID construct_methd=env->GetMethodID(map_clz,"<init>","()V");
    jmethodID put_method=env->GetMethodID(map_clz,"put","(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
    jobject map_obj=env->NewObject(map_clz,construct_methd);
    jstring key = env->NewStringUTF("CrazyMo_");
    jstring value = env->NewStringUTF("在C++创建Java Map对象");
    env->CallObjectMethod(map_obj,put_method,key,value);
    return map_obj;
}

这里写图片描述

二、C/C++ 和 Java 互相调用

  • 定义一个JavaVM 全局变量(因为JavaVM 相当于是单例的,可能很多地方都需要用到)

  • 重写JNI_OnLoad函数,目的是为了指定JNI版本和初始化我们定义的全局JavaVM变量

JavaVM* _vm=NULL;

//重写JNI_OnLoad
int JNI_OnLoad(JavaVM* vm, void* reserved){
    LOG("【JNI_OnLoad 被执行并完成JavaVM的初始化】");
    _vm=vm;
    JNIEnv* env = 0;
    //从vm 中获得 JniEnv
    int r = vm->GetEnv((void**) &env, JNI_VERSION_1_6);
    if( r != JNI_OK)
    {
        return -1;
    }
    return JNI_VERSION_1_6;
}
  • 定义本地Java接口类方法和Java回调方法

    扫描二维码关注公众号,回复: 5790405 查看本文章
  • 实现Native 层的函数,如果需要接收来自Java层的对象,最好定义一个全局的结构体变量保存,而且还需要保存为全局引用

  • 像创建Native线程,因为需要通过反射调用Java层的回调方法,所以需要获取到对应的env,这里就是前面为什么要获取JavaVM 实例的原因了,通过JavaVM实例可以调用AttachCurrentThread函数获取对应的env

  • 通过上一步获取到的env 反射方式调用Java的方法,最后使用完毕之后再线程函数中JavaVM实例调用DetachCurrentThread 函数并且return 0

extern JavaVM *_vm;//如果已经在其他.cpp定义了,直接通过extern使用全局变量

/**
 * 因为需要回调Java层的方法,而在C/C++层回调Java方法需要一个对应的Java Class字节码对象,所以需要传递过来
 */
struct JavaContext {
    jobject instance;
};

void *interactWithJava(void *args) {
    JNIEnv *env = 0;
    LOGE("【通过JavaVM传入env执行AttachCurrentThread把env 附加到虚拟机,附加成功之后env就被赋值了】");
    //等价于_vm不等于NULL
    if (_vm) {
        jint ret = _vm->AttachCurrentThread(&env, 0); // native线程附加到Java 虚拟机,附加成功之后env就被赋值了
        if (ret == JNI_OK) {
            LOGE("【AttachCurrentThread 成功,env赋值】");
            JavaContext *context = static_cast<JavaContext *>(args);
            //等价于 context!=NULL
            if (context) {
                sleep(2);//模拟下载耗时2S
                //获得MainActivity的class对象
                jclass clz = env->GetObjectClass(context->instance);
                //com.crazymo.ndk.MainActivity
               //!!!!java.lang.ClassNotFoundException: Didn't find class "com.crazymo.ndk.jni.Blog" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /vendor/lib64, /product/lib64, /system/lib64, /vendor/lib64, /product/lib64]]为什么找不到?因为线程里attach时的jni 的env中 findclass用的BootClassLoader只能找到系统的类,不能找到自己编写的类。
                ///jclass clz = env->FindClass("com/crazymo/ndk/MainActivity");
                if (clz) {
                    // 反射获得方法
                    jmethodID updui_method = env->GetMethodID(clz, "update", "()V");
                    if (updui_method) {
                        LOGE("【通过反射找到要回调的Java方法的methodID并调用】");
                        env->CallVoidMethod(context->instance, updui_method);
                        delete (context);
                        env->DeleteGlobalRef(context->instance);
                        context = 0;
                    }
                }
            }
        }
    } else {
        LOGE("context==NULL");
    }
    //分离
    _vm->DetachCurrentThread();
    return 0;//线程函数必须记得返回0
}

extern "C"
JNIEXPORT void JNICALL
Java_com_crazymo_ndk_jni_JNIHelper_runNativeCallJava(JNIEnv *env, jobject instance, jobject obj) {
    //注意这里的instance 代表的是runNativeCallJava所属类的实例
    LOGE("在C/C++ 层创建Native线程...")
    pthread_t pid;
    //必须把传递过来的对象保存为全局引用,然后才能传递到另一个线程使用,否则报attempt to use stale WeakGlobal 0x3 (should be 0x7)
    JavaContext *context = new JavaContext;
    context->instance = env->NewGlobalRef(obj);
    pthread_create(&pid, 0, interactWithJava, context);//创建并启动Native线程
}

注意:在自己创建的线程中反射获取对应的class 时,在使用FindClass 的时候需要注意,因为线程里attach时的jni 的env中 findclass用的BootClassLoader只能找到系统的类,不能找到自己编写的类。

这里写图片描述

小结

无论是在本地Native 层C/C++源码中调用Java层的方法还是解析Java 传递过来的复杂数据,都是先获取对应的Java类的实例对象(通过JNIEnv * 获取),然后在经过反射调用响应的方法实现的。
未完待续……

猜你喜欢

转载自blog.csdn.net/CrazyMo_/article/details/82352148