引言
前一篇文章基本上动态注册和静态注册以及从Java传递各种数据到C/C++进行处理,正所谓来而不往非礼也,这篇是针对从在C/C++封装各种数据并传递到Java层。此系列文章基链接:
- Android NDK——必知必会之配置Windows与Linux共享及 Linux NDK 交叉编译环境配置(一)
- Android NDK——必知必会之JNI和NDK基础全面详解(二)
- Android NDK——必知必会之JNI的C++操作函数详解和小结(三)
- Android NDK——必知必会之从Java 传递各种数据到C/C++进行处理全面详解(四)
- Android NDK——必知必会之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 * 获取),然后在经过反射调用响应的方法实现的。
未完待续……