Android 在JNI子线程调用Java方法

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 <jni.h>
#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++子线程

    1. 创建 JavaListener 对象
    1. 创建子线程和childCallback 回调,它就相当于 Thread 中的 run 方法一样。然后将 JavaListener 对象传递给 childCallback
    1. 在子线程中得到主线程传递过来的 JavaListener 实例
    1. 通过JavaListener去执行对应的onSuccess函数。
    1. 退出线程
#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日 双十一 光棍节~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容