【综合】HarmonyOS 鸿蒙Next Native侧跨线程调用Java侧回调函数

发布于 1周前 作者 zlyuanteng 来自 鸿蒙OS

【综合】HarmonyOS 鸿蒙Next Native侧跨线程调用Java侧回调函数

作者:HW-LC

在Android开发过程中,我们经常会遇到native调用Java的接口,这个网上资料也很多,我这里不再赘述了,我要讲的一种场景是native跨线程调用Java侧注册的回调函数。

我们的场景如下:

Java侧注册一个callback到native,native侧其他线程当触发某些条件之后(比如设备入网,设备离网),回调设备相关信息到Java侧。

具体结构图如下:

对应的代码如下:

(1)Java侧主线程注册Java端callback

private native void initIsyncNative(String userId, String deviceId, int proxyScore, HiDeviceCallback hiDeviceCallback);

(2)Native侧生成global reference

Java_com_huawei_hidevice_core_HiDeviceNative_initIsyncNative(JNIEnv *env, jobject, jstring jUserId,
                                                             jstring jDeviceId, jint jProxyScore, jobject cbObj) {
    ...
    jclass jclazz = env->FindClass("com/huawei/hidevice/core/HiDeviceCallback");
    if (jclazz == NULL) {
        ALOGE("find HiDeviceCallback fail");
        return;
    }

    deviceNotifyCb = env->GetMethodID(jclazz, "deviceNotifyCallback", "(Ljava/lang/String;I)V");
    recvDataCb = env->GetMethodID(jclazz, "recvDataCallback", "(ILjava/lang/String;Ljava/lang/String;[BI)I");
    serviceNotifyCb = env->GetMethodID(jclazz, "serviceNotifyCallback", "(ILjava/lang/String;Ljava/lang/String;I)V");

    if (deviceNotifyCb == NULL || recvDataCb == NULL || serviceNotifyCb == NULL ) {
        ALOGE("get method id fail");
        return;
    }

    // 此处需要创建object的global ref,否则isync跨线程回调这个obj会找不到而crash
    hiDeviceCbObj = env->NewGlobalRef(cbObj);

    if (hiDeviceCbObj == NULL) {
        ALOGE("init java callback fail");
    }
    ...
}

这里删除了业务代码,只保留了跟本文相关基础代码,可以看到主要是拿到callback的jmethodID和global reference。

(3)其他线程利用JNIEnv,jmethodID,global reference和参数,回调到Java侧的callback。

由于不同线程的JNIEnv是不一样的,所以要回调到主线程的callback,需要利用JavaVM找到主线程的JNIEnv(保存在g_JNIEnv中),代码如下:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *){
    vm->GetEnv((void **) &g_JNIEnv, JNI_VERSION_1_6);
    __android_log_write(ANDROID_LOG_DEBUG, "HiDevice", "OnLoad");
    g_JavaVM = vm;

    return JNI_VERSION_1_6;
}

jmethodID和global reference在步骤2的时候已经拿到了,看下其他线程如何回调到Java侧的callback的,代码如下:

void HiDeviceDeviceNotifyCb(const char *device_id, device_event_e event_type) {
    ALOGD("HiDeviceDeviceNotifyCb, deviceId:%s, type:%d", device_id, event_type);

    // 由于isync是跨线程调用的,所以需要attach到initIsyncNative线程(主线程)
    // 这里就是用JavaVM来attach的,否则拿不到hiDeviceObj
    g_JavaVM->AttachCurrentThread(&g_JNIEnv, 0);
    jstring deviceId = g_JNIEnv->NewStringUTF(device_id);
    g_JNIEnv->CallVoidMethod(hiDeviceCbObj, deviceNotifyCb, deviceId, event_type);
    g_JNIEnv->DeleteLocalRef(deviceId);
    g_JavaVM->DetachCurrentThread();
}

这里需要重点关注下AttachCurrentThread,通过JavaVM找到对应的JNIEnv,否则在调用CallVoidMethod的时候会crash,回调完毕注意detach即可。

到此为止native跨线程调用java侧注册的回调函数就能正常运行了,本例演示的是回调Java侧HiDeviceCallback的deviceNotifyCallback函数。


更多关于【综合】HarmonyOS 鸿蒙Next Native侧跨线程调用Java侧回调函数的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html

13 回复

想认识这位大神

更多关于【综合】HarmonyOS 鸿蒙Next Native侧跨线程调用Java侧回调函数的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


作者:HW-LC

在Android开发过程中,我们经常会遇到native调用Java的接口,这个网上资料也很多,我这里不再赘述了,我要讲的一种场景是native跨线程调用Java侧注册的回调函数。

我们的场景如下:

Java侧注册一个callback到native,native侧其他线程当触发某些条件之后(比如设备入网,设备离网),回调设备相关信息到Java侧。

具体结构图如下:

对应的代码如下: 

(1)Java侧主线程注册Java端callback

```java
private native void initIsyncNative(String userId, String deviceId, int proxyScore, HiDeviceCallback hiDeviceCallback);

(2)Native侧生成global reference

Java_com_huawei_hidevice_core_HiDeviceNative_initIsyncNative(JNIEnv *env, jobject, jstring jUserId,
                                                             jstring jDeviceId, jint jProxyScore, jobject cbObj) {
    ...
    jclass jclazz = env->FindClass("com/huawei/hidevice/core/HiDeviceCallback");
    if (jclazz == NULL) {
        ALOGE("find HiDeviceCallback fail");
        return;
    }

    deviceNotifyCb = env->GetMethodID(jclazz, "deviceNotifyCallback", "(Ljava/lang/String;I)V");
    recvDataCb = env->GetMethodID(jclazz, "recvDataCallback", "(ILjava/lang/String;Ljava/lang/String;[BI)I");
    serviceNotifyCb = env->GetMethodID(jclazz, "serviceNotifyCallback", "(ILjava/lang/String;Ljava/lang/String;I)V");

    if (deviceNotifyCb == NULL || recvDataCb == NULL || serviceNotifyCb == NULL ) {
        ALOGE("get method id fail");
        return;
    }

    // 此处需要创建object的global ref,否则isync跨线程回调这个obj会找不到而crash
    hiDeviceCbObj = env->NewGlobalRef(cbObj);
    if (hiDeviceCbObj == NULL) {
        ALOGE("init java callback fail");
    }
    ...
}

这里删除了业务代码,只保留了跟本文相关基础代码,可以看到主要是拿到callback的jmethodID和global reference。

(3)其他线程利用JNIEnv,jmethodID,global reference和参数,回调到Java侧的callback。

由于不同线程的JNIEnv是不一样的,所以要回调到主线程的callback,需要利用JavaVM找到主线程的JNIEnv(保存在g_JNIEnv中),代码如下:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *){
    vm->GetEnv((void **) &g_JNIEnv, JNI_VERSION_1_6);
    __android_log_write(ANDROID_LOG_DEBUG, "HiDevice", "OnLoad");
    g_JavaVM = vm;

    return JNI_VERSION_1_6;
}

jmethodID和global reference在步骤2的时候已经拿到了,看下其他线程如何回调到Java侧的callback的,代码如下:

void HiDeviceDeviceNotifyCb(const char *device_id, device_event_e event_type) {
    ALOGD("HiDeviceDeviceNotifyCb, deviceId:%s, type:%d", device_id, event_type);

    // 由于isync是跨线程调用的,所以需要attach到initIsyncNative线程(主线程)
    // 这里就是用JavaVM来attach的,否则拿不到hiDeviceObj
    g_JavaVM->AttachCurrentThread(&g_JNIEnv, 0);
    jstring deviceId = g_JNIEnv->NewStringUTF(device_id);
    g_JNIEnv->CallVoidMethod(hiDeviceCbObj, deviceNotifyCb, deviceId, event_type);
    g_JNIEnv->DeleteLocalRef(deviceId);
    g_JavaVM->DetachCurrentThread();
}

这里需要重点关注下AttachCurrentThread,通过JavaVM找到对应的JNIEnv,否则在调用CallVoidMethod的时候会crash,回调完毕注意detach即可。

到此为止native跨线程调用java侧注册的回调函数就能正常运行了,本例演示的是回调Java侧HiDeviceCallback的deviceNotifyCallback函数。

沙发,图文并茂,赞一个

图文结合很清晰
图文结合很清晰

作者:HW-LC

在Android开发过程中,我们经常会遇到native调用Java的接口,这个网上资料也很多,我这里不再赘述了,我要讲的一种场景是native跨线程调用Java侧注册的回调函数。

我们的场景如下:

Java侧注册一个callback到native,native侧其他线程当触发某些条件之后(比如设备入网,设备离网),回调设备相关信息到Java侧。

具体结构图如下:

对应的代码如下:

(1)Java侧主线程注册Java端callback

private native void initIsyncNative(String userId, String deviceId, int proxyScore, HiDeviceCallback hiDeviceCallback);

(2)Native侧生成global reference

Java_com_huawei_hidevice_core_HiDeviceNative_initIsyncNative(JNIEnv *env, jobject, jstring jUserId,
                                                             jstring jDeviceId, jint jProxyScore, jobject cbObj) {
    ...
    jclass jclazz = env->FindClass("com/huawei/hidevice/core/HiDeviceCallback");
    if (jclazz == NULL) {
        ALOGE("find HiDeviceCallback fail");
        return;
    }

    deviceNotifyCb = env->GetMethodID(jclazz, "deviceNotifyCallback", "(Ljava/lang/String;I)V");
    recvDataCb = env->GetMethodID(jclazz, "recvDataCallback", "(ILjava/lang/String;Ljava/lang/String;[BI)I");
    serviceNotifyCb = env->GetMethodID(jclazz, "serviceNotifyCallback", "(ILjava/lang/String;Ljava/lang/String;I)V");

    if (deviceNotifyCb == NULL || recvDataCb == NULL || serviceNotifyCb == NULL ) {
        ALOGE("get method id fail");
        return;
    }

    // 此处需要创建object的global ref,否则isync跨线程回调这个obj会找不到而crash
    hiDeviceCbObj = env->NewGlobalRef(cbObj);

    if (hiDeviceCbObj == NULL) {
        ALOGE("init java callback fail");
    }
    ...
}

这里删除了业务代码,只保留了跟本文相关基础代码,可以看到主要是拿到callback的jmethodID和global reference。

(3)其他线程利用JNIEnv,jmethodID,global reference和参数,回调到Java侧的callback。

由于不同线程的JNIEnv是不一样的,所以要回调到主线程的callback,需要利用JavaVM找到主线程的JNIEnv(保存在g_JNIEnv中),代码如下:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *){
    vm->GetEnv((void **) &g_JNIEnv, JNI_VERSION_1_6);
    __android_log_write(ANDROID_LOG_DEBUG, "HiDevice", "OnLoad");
    g_JavaVM = vm;

    return JNI_VERSION_1_6;
}

jmethodID和global reference在步骤2的时候已经拿到了,看下其他线程如何回调到Java侧的callback的,代码如下:

void HiDeviceDeviceNotifyCb(const char *device_id, device_event_e event_type) {
    ALOGD("HiDeviceDeviceNotifyCb, deviceId:%s, type:%d", device_id, event_type);

    // 由于isync是跨线程调用的,所以需要attach到initIsyncNative线程(主线程)
    // 这里就是用JavaVM来attach的,否则拿不到hiDeviceObj
    g_JavaVM->AttachCurrentThread(&g_JNIEnv, 0);
    jstring deviceId = g_JNIEnv->NewStringUTF(device_id);
    g_JNIEnv->CallVoidMethod(hiDeviceCbObj, deviceNotifyCb, deviceId, event_type);
    g_JNIEnv->DeleteLocalRef(deviceId);
    g_JavaVM->DetachCurrentThread();
}

这里需要重点关注下AttachCurrentThread,通过JavaVM找到对应的JNIEnv,否则在调用CallVoidMethod的时候会crash,回调完毕注意detach即可。

到此为止native跨线程调用java侧注册的回调函数就能正常运行了,本例演示的是回调Java侧HiDeviceCallback的deviceNotifyCallback函数。

沙发,图文并茂,赞一个

图文结合很清晰

沙发,图文并茂,赞一个

作者:HW-LC

在Android开发过程中,我们经常会遇到native调用Java的接口,这个网上资料也很多,我这里不再赘述了,我要讲的一种场景是native跨线程调用Java侧注册的回调函数。

我们的场景如下:

Java侧注册一个callback到native,native侧其他线程当触发某些条件之后(比如设备入网,设备离网),回调设备相关信息到Java侧。

具体结构图如下:

对应的代码如下: 

(1)Java侧主线程注册Java端callback

```java
private native void initIsyncNative(String userId, String deviceId, int proxyScore, HiDeviceCallback hiDeviceCallback);

(2)Native侧生成global reference

Java_com_huawei_hidevice_core_HiDeviceNative_initIsyncNative(JNIEnv *env, jobject, jstring jUserId,
                                                             jstring jDeviceId, jint jProxyScore, jobject cbObj) {
    ...
    jclass jclazz = env->FindClass("com/huawei/hidevice/core/HiDeviceCallback");
    if (jclazz == NULL) {
        ALOGE("find HiDeviceCallback fail");
        return;
    }

    deviceNotifyCb = env->GetMethodID(jclazz, "deviceNotifyCallback", "(Ljava/lang/String;I)V");
    recvDataCb = env->GetMethodID(jclazz, "recvDataCallback", "(ILjava/lang/String;Ljava/lang/String;[BI)I");
    serviceNotifyCb = env->GetMethodID(jclazz, "serviceNotifyCallback", "(ILjava/lang/String;Ljava/lang/String;I)V");

    if (deviceNotifyCb == NULL || recvDataCb == NULL || serviceNotifyCb == NULL ) {
        ALOGE("get method id fail");
        return;
    }
    
    // 此处需要创建object的global ref,否则isync跨线程回调这个obj会找不到而crash
    hiDeviceCbObj = env->NewGlobalRef(cbObj);
    if (hiDeviceCbObj == NULL) {
        ALOGE("init java callback fail");
    }
    ...
}

这里删除了业务代码,只保留了跟本文相关基础代码,可以看到主要是拿到callback的jmethodID和global reference。

(3)其他线程利用JNIEnv,jmethodID,global reference和参数,回调到Java侧的callback。

由于不同线程的JNIEnv是不一样的,所以要回调到主线程的callback,需要利用JavaVM找到主线程的JNIEnv(保存在g_JNIEnv中),代码如下:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *){
    vm->GetEnv((void **) &g_JNIEnv, JNI_VERSION_1_6);
    __android_log_write(ANDROID_LOG_DEBUG, "HiDevice", "OnLoad");
    g_JavaVM = vm;

    return JNI_VERSION_1_6;
}

jmethodID和global reference在步骤2的时候已经拿到了,看下其他线程如何回调到Java侧的callback的,代码如下:

void HiDeviceDeviceNotifyCb(const char *device_id, device_event_e event_type) {
    ALOGD("HiDeviceDeviceNotifyCb, deviceId:%s, type:%d", device_id, event_type);

    // 由于isync是跨线程调用的,所以需要attach到initIsyncNative线程(主线程)
    // 这里就是用JavaVM来attach的,否则拿不到hiDeviceObj
    g_JavaVM->AttachCurrentThread(&g_JNIEnv, 0);
    jstring deviceId = g_JNIEnv->NewStringUTF(device_id);
    g_JNIEnv->CallVoidMethod(hiDeviceCbObj, deviceNotifyCb, deviceId, event_type);
    g_JNIEnv->DeleteLocalRef(deviceId);
    g_JavaVM->DetachCurrentThread();
}

这里需要重点关注下AttachCurrentThread,通过JavaVM找到对应的JNIEnv,否则在调用CallVoidMethod的时候会crash,回调完毕注意detach即可。

到此为止native跨线程调用java侧注册的回调函数就能正常运行了,本例演示的是回调Java侧HiDeviceCallback的deviceNotifyCallback函数。

沙发,图文并茂,赞一个

图文结合很清晰

在HarmonyOS(鸿蒙)系统中,实现Native侧跨线程调用Java侧回调函数,通常涉及到JNI(Java Native Interface)技术的使用以及线程间通信机制。以下是关键步骤概述:

  1. JNI环境设置

    • 确保Java类和Native代码正确配置,包括加载Native库和声明Native方法。
    • 使用System.loadLibrary在Java代码中加载Native库。
  2. 线程管理

    • Native侧创建或获取目标线程,该线程将用于执行Java回调。
    • 使用鸿蒙提供的线程管理机制,如任务调度器或线程池,确保线程安全。
  3. 回调机制

    • 在Native代码中,通过JNI调用Java方法时,需确保Java虚拟机(JVM)环境有效。
    • 使用AttachCurrentThreadDetachCurrentThread管理Native线程与JVM的关联。
    • 利用FindClassGetMethodID等JNI函数获取Java类和方法,并执行回调。
  4. 异常处理

    • 检查JNI调用返回的错误码,处理可能的异常,如类未找到、方法未找到等。
  5. 同步与互斥

    • 确保跨线程访问共享资源时,使用适当的同步机制,如互斥锁,避免数据竞争。

如果问题依旧没法解决请联系官网客服,官网地址是 https://www.itying.com/category-93-b0.html

回到顶部