HarmonyOS NEXT使用AKI轻松实现跨语言调用案例

介绍

针对JS与C/C++跨语言访问场景,NAPI使用比较繁琐。而AKI提供了极简语法糖使用方式,一行代码完成JS与C/C++的无障碍跨语言互调,使用方便。本示例将介绍使用AKI编写C++跨线程调用JS函数场景。通过调用C++全局函数,创建子线程来调用JS函数,实现对变量value的加10操作,为开发者使用AKI提供参考。

效果图预览

使用说明

  1. 点击页面“AKI跨线程调用JS函数”按钮,每次点击,显示数值加10。

实现思路

以下是使用AKI和NPAI的libuv实现跨线程调用JS函数的实现对比:

  1. AKI和NAPI初始化。

    AKI初始化使用JSBIND_ADDON注册Native插件,使用AKI的JSBIND_GLOBAL注册FFI特性,然后在JSBIND_GLOBAL作用域下使用AKI的JSBIND_FUNCTION绑定C++全局函数AkiThreadsCallJs。源码参考akiusepractice.cpp

    ...
    // 使用JSBIND_ADDON注册Native插件,可从JavaScript import导入插件。注册AKI插件名:即为编译*.so名称,规则与NAPI一致。
    JSBIND_ADDON(aki_use_practice)
    // 使用JSBIND_GLOBAL注册FFI特性。用于圈定需要绑定的全局函数作用域。
    JSBIND_GLOBAL() {
      // 在JSBIND_GLOBAL作用域下使用JSBIND_FUNCTION绑定C++全局函数后,可从JavaScript直接调用。
      JSBIND_FUNCTION(AkiThreadsCallJs);
    }
    ...

    NAPI的libuv初始化需要定义napi_property_descriptor结构体,准备模块加载相关信息,将Init函数与模块名等信息记录下来。源码参考hello.cpp

    ...
    static napi_value Init(napi_env env, napi_value exports)
    {
        // 第一个参数"add"为ArkTS侧对应方法的名称。
        napi_property_descriptor desc[] = {
            {"UvWorkTest", nullptr, UvWorkTest, nullptr, nullptr, nullptr, napi_default, nullptr}
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
    }
    
    // 准备模块加载相关信息,将Init函数与本模块名等信息记录下来。
    static napi_module demoModule = {
      .nm_version = 1,
      .nm_flags = 0,
      .nm_filename = nullptr,
      .nm_register_func = Init,
      .nm_modname = "entry",
      .nm_priv = ((void *)0),
      .reserved = {0},
    };
    
    extern "C" __attribute__((constructor)) void RegisterModule(void) { napi_module_register(&demoModule); } 
  2. AKI和NAPI在native侧的业务函数实现。

    AKI在native侧业务函数实现是在AkiThreadsCallJs中创建子线程,子线程中使用aki::JSBind:: GetJSFunction获取指定JavaScript函数akiAccumulate的句柄后,使用Invoke触发调用。源码参考akiusepractice.cpp

    // 定义C++函数AkiThreadsCallJs。从native主线程中创建子线程subThread调用JavaScript函数。
    void AkiThreadsCallJs(int value) {
        // 创建子线程subThread
        std::thread subThread([=]() {
            // 使用aki::JSBind::GetJSFunction获取指定JavaScript函数句柄后,使用Invoke触发调用。这里获取JS侧定义的函数akiAccumulate。
            if (auto func = aki::JSBind::GetJSFunction("akiAccumulate")) {
                // 定义一个函数对象callback,该函数对象接受一个整数参数并返回void。
                std::function<void(int)> callback = [](int value) {};
                // 调用JavaScript函数,Invoke<T>指定返回值类型。
                func->Invoke<void>(value, callback);
            }
        });
        // 将子线程subThread从主线程中分离出来,独立运行。
        subThread.detach();
        return;
    }

    NAPI的libuv在native侧业务函数实现是在native主线程中实现UvWorkTest接口。接口接收到ArkTS传入的JS回调函数后创建子线程,执行函数CallbackUvWorkTest。在CallbackUvWorkTest中创建工作任务workReq,通过uv_queue_work将工作任务添加到libuv队列中,等待被执行。源码参考hello.cpp

    static napi_value UvWorkTest(napi_env env, napi_callback_info info) {
        size_t argc = 1;
        napi_value argv[1] = {0};
        napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
    
        napi_valuetype valueType = napi_undefined;
        napi_typeof(env, argv[0], &valueType);
        if (valueType != napi_function) {
            OH_LOG_ERROR(LOG_APP, "UvWorkTest param is not function");
            return nullptr;
        }
    
        OH_LOG_INFO(LOG_APP, "UvWorkTest current value:[%{public}d]", g_cValue);
        for (int i = 0; i < g_threadNum; i++) {
            auto asyncContext = new CallbackContext();
            if (asyncContext == nullptr) {
                OH_LOG_ERROR(LOG_APP, "UvWorkTest new asyncContext fail!");
                return nullptr;
            }
            asyncContext->env = env;
            asyncContext->retData = i;
            OH_LOG_INFO(LOG_APP, "UvWorkTest thread begin index:[%{public}d], value:[%{public}d]", i, g_cValue);
            napi_create_reference(env, argv[0], 1, &asyncContext->callbackRef);
            std::thread testThread(CallbackUvWorkTest, asyncContext);
            testThread.detach();
            OH_LOG_INFO(LOG_APP, "UvWorkTest thread end index:[%{public}d], value:[%{public}d]", i, g_cValue);
        }
        return nullptr;
    }
    
    void CallbackUvWorkTest(CallbackContext *context) {
        if (context == nullptr) {
            OH_LOG_ERROR(LOG_APP, "UvWorkTest context is nullptr");
            return;
        }
        uv_loop_s *loop = nullptr;
        napi_get_uv_event_loop(context->env, &loop);
        // 创建工作数据结构,自定义数据结构添加在data中
        uv_work_t *workReq = new uv_work_t;
        if (workReq == nullptr) {
            if (context != nullptr) {
                napi_delete_reference(context->env, context->callbackRef);
                delete context;
                OH_LOG_INFO(LOG_APP, "UvWorkTest delete context");
                context = nullptr;
            }
            OH_LOG_ERROR(LOG_APP, "UvWorkTest new uv_work_t fail!");
            return;
        }
        workReq->data = (void *)context;
        // 此打印位于子线程
        OH_LOG_INFO(LOG_APP, "UvWorkTest childThread_1 [%{public}d]", g_cValue);
        // 添加工作任务到libuv的队列中
        uv_queue_work(loop, workReq, WorkCallback, AfterWorkCallback);
    }
  3. AKI和NAPI在ArkTS侧调用JS函数。

    AKI在ArkTS侧使用AKI的JSBind.bindFunction绑定JavaScript全局函数akiAccumulate。使用AKI调用C++全局函数AkiThreadsCallJs。源码参考AkiView.ets

    ...
    // 使用AKI的JSBind.bindFunction绑定JavaScript全局函数。
    libaki.JSBind.bindFunction("akiAccumulate", (values: number) => {
      // 对变量value做加10操作,刷新Text组件的value值。
      values += 10;
      this.value = values;
    });
    // TODO:知识点:使用AKI调用C++全局函数AkiThreadsCallJs,并传入参数value。
    libaki.AkiThreadsCallJs(this.value);
    ...

    NAPI的libuv在ArkTS侧调用C++全局函数UvWorkTest。源码参考Index.ets

    entry.UvWorkTest((values: number) => {
      values += 10;
      logger.info('UvWorkTest js callback value = ', values.toString());
      this.value = values;
      return values;
    }

通过以上AKI和NAPI实现跨线程调用JS的实现步骤的对比,可以发现AKI在native侧相较于NAPI实现的代码量要少很多,使用也更加方便。

高性能知识点

  1. AKI使用方便,但相比于NPAI,对性能的损耗相对会多一些。对性能要求不高,且更需要易用性开发的场景,推荐使用AKI。

工程结构&模块类型

akiusepractice                                 // har类型
|---src\main\cpp
|   |---akiusepractice.cpp                     // native层-native侧业务处理
|   |---CMakeLists.txt                         // native层-AKI相关CMake配置
|---src\main\ets\view
|   |---AkiView.ets                            // 视图层-AKI跨线程调用JS函数页面 

模块依赖

  1. 本实例依赖AKI
  2. 本实例依赖common模块来实现公共组件FunctionDescription

最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。 

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。

希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

如果你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感兴趣以及转行人员,可以直接领取这份资料

 获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

鸿蒙(HarmonyOS NEXT)最新学习路线

  •  HarmonOS基础技能

  • HarmonOS就业必备技能 
  •  HarmonOS多媒体技术

  • 鸿蒙NaPi组件进阶

  • HarmonOS高级技能

  • 初识HarmonOS内核 
  • 实战就业级设备开发

 有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

《鸿蒙 (OpenHarmony)开发入门教学视频》

《鸿蒙生态应用开发V2.0白皮书》

图片

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

图片

 《鸿蒙开发基础》

  • ArkTS语言
  • 安装DevEco Studio
  • 运用你的第一个ArkTS应用
  • ArkUI声明式UI开发
  • .……

图片

 《鸿蒙开发进阶》

  • Stage模型入门
  • 网络管理
  • 数据管理
  • 电话服务
  • 分布式应用开发
  • 通知与窗口管理
  • 多媒体技术
  • 安全技能
  • 任务管理
  • WebGL
  • 国际化开发
  • 应用测试
  • DFX面向未来设计
  • 鸿蒙系统移植和裁剪定制
  • ……

图片

《鸿蒙进阶实战》

  • ArkTS实践
  • UIAbility应用
  • 网络案例
  • ……

图片

 获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料

总结

总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值