1. C++ 全局调用Java方法
之前讨论过,如何C++主线程中调用 Java 函数C++主线程调用Java方法,下面来看看如何在子线程中调用 Java 函数。
由于JNIEnv
是与线程绑定的,就像 Android 的 Looper 也是和线程绑定一样,每一个 Looper 会对应一个线程。因此要在子线程中调用 Java 的方法,需要得到当前线程的 JNIEnv 实例。
那么如何在获取当前线程的 JNIEnv 呢?
1.1 获取当前线程的 JNIEnv
因为JVM
是进程相关的,所以可以通过JVM
来获取当前线程的JNIEnv
,然后就可以调用java
的函数了。
- 获取 JVM 实例
通过System.loadLibrary("xxx")
加载动态库时,系统就会回调JNI_OnLoad
函数,因此我们只需要覆写这个函数,并且将JavaVM 指针
保存到类成员变量即可。
JNI_OnLoad(JavaVM* vm,void* reserved)
- 通过
JavaVM
获取JNIEnv
实例
下面演示在子线程获取 JNIEnv 的伪代码
JNIEnv *env;
//1.根据 AttachCurrentThread 获取到当前线程的 JNIEnv 实例
vm->AttachCurrentThread(&env, 0);
//2.调用 java 函数
//call method
//3.解除挂载当前线程
vm->DetachCurrentThread();
1.2 C++子线程调用Java方法
下面是 native code 创建子线程调用 Java 层的 onSuccess 函数的流程图
1.2.1 获取 JVM 实例
上面分析了要想得到 JNIEnv 指针就必须先得到 JavaVM 指针。
下面可以在 native 代码中重写 JNI_OnLoad
得到 JavaVM 实例
//定义一个全局 java vm 实例
JavaVM *jvm;
//在加载动态库时回去调用 JNI_Onload 方法,在这里可以得到 JavaVM 实例对象
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
jvm = vm;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
return JNI_VERSION_1_6;
}
1.2.2 JavaListener
JavaListener 是在主线程创建,然后传递给子线程的。那么它是干什么用的?
它会负责获取当前子线程的 JNIEnv 实例并且去调用 Java
层的 onSuccess
函数
下面来看一下 JavaListener
类是如何定义的?
- 因为
JavaListener
是一个C++
类,因此我们要先创建一个JavaListener.h
头文件。
JavaListener.h
class JavaListener {
//定义类成员属性
public:
JavaVM *vm;
JNIEnv *env;
jobject jobj;
jmethodID jmethod;
public:
//定义构造函数
JavaListener(JavaVM *vm, JNIEnv *env, jobject jobj);
//析构函数
~JavaListener();
//定义类成员方法 onSuccess 在这个函数会负责去调用 Java 层的 onSuccess 函数
void onSuccess(const char *msg);
};
- 有了 JavaListener.h 文件之后,我们就可以开始定义对应的实现 JavaListener.cpp 文件。
JavaListener.cpp
#include
#include "JavaListener.h"
#include "android/log.h"
#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,"liaowejian",FORMAT,##__VA_ARGS__);
//在构造函数接收对应的参数
JavaListener::JavaListener(JavaVM *vm, JNIEnv *env, jobject jobj) {
this->vm = vm;
this->env = env;
this->jobj = jobj;
jclass jclaz = env->GetObjectClass(jobj);
//得到 jmethodid 实例
jmethod = env->GetMethodID(jclaz, "onSuccess", "(Ljava/lang/String;)V");
}
//在子线程回调这个方法 onSuccess
void JavaListener::onSuccess(const char *msg) {
//得到子线程相关的 JNIEnv 实例
JNIEnv *env;
vm->AttachCurrentThread(&env, 0);
//将需要传递给 Java 层 onSuccess 的 msg 转化为 jstring 实例
jstring jmsg = env->NewStringUTF(msg);
//调用 Java 层的函数
env->CallVoidMethod(jobj, jmethod, jmsg);
//删除本地引用 jmsg,避免内存泄露
env->DeleteLocalRef(jmsg);
//取消挂载线程
vm->DetachCurrentThread();
}
1.2.3 创建C++子线程
-
- 创建 JavaListener 对象
-
- 创建子线程和
childCallback
回调,它就相当于Thread
中的run
方法一样。然后将JavaListener
对象传递给childCallback
- 创建子线程和
-
- 在子线程中得到主线程传递过来的
JavaListener
实例
- 在子线程中得到主线程传递过来的
-
- 通过
JavaListener
去执行对应的onSuccess
函数。
- 通过
-
- 退出线程
#include "JavaListener.h"
pthread_t childThread;
//线程执行体
void *childCallback(void *data) {
//3. 在子线程中得到主线程传递过来的 JavaListener 实例
JavaListener *javaListener = (JavaListener *) data;
//4. 通过 JavaListener 去执行对应的 onSuccess 函数。
javaListener->onSuccess("hello from child thread");
//5. 退出线程
pthread_exit(&childThread);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_lib_JniThreadDemo_callJavaMethodOnCppChildThread(JNIEnv *env, jobject instance) {
//1. 传递给子线程的对象 JavaListener ,**这里需要 instance 转化为全局的实例**
JavaListener *javaListener = new JavaListener(jvm, env, env->NewGlobalRef(instance));
//2. 创建子线程,将 javaListener 传递给线程执行体 childCallback
pthread_create(&childThread, NULL, childCallback, javaListener);
}
1.2.4 在 Java 层调用
public void callJavaMethodOnCppChildThread(View view) {
JniThreadDemo jniThreadDemo = new JniThreadDemo();
jniThreadDemo.callJavaMethodOnCppChildThread();
}
上面演示了如何在子线程中回调 Java 层的函数。
小结:以上的应用一般是在 Java 层调用 Jni 函数做一些耗时的计算工作,然后在异步线程中回调给 Java 层。到此为止,在 JNI 层主线程和子线程回调 Java 函数的学习就到此为止。
项目地址:
https://github.com/liaowjcoder/Jni4Android
记录于 2018年11月11日 双十一 光棍节~