首页 > 其他分享 >HarmonyOS NEXT应用开发之使用AKI轻松实现跨语言调用

HarmonyOS NEXT应用开发之使用AKI轻松实现跨语言调用

时间:2024-04-18 20:44:44浏览次数:28  
标签:LOG nullptr value NEXT HarmonyOS AKI napi 函数

介绍

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

效果图预览

image

使用说明

  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); } 
  1. 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);
}
  1. 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

参考资料

  1. AKI
  2. 公共组件FunctionDescription

标签:LOG,nullptr,value,NEXT,HarmonyOS,AKI,napi,函数
From: https://www.cnblogs.com/HarmonyOSNext/p/18144363

相关文章

  • 鸿蒙HarmonyOS实战-ArkUI组件(Canvas)
    ......
  • HarmonyOS NEXT应用开发之Tab组件实现增删Tab标签
    介绍本示例介绍使用了Tab组件实现自定义增删Tab页签的功能。该场景多用于浏览器等场景。效果图预览使用说明:点击新增按钮,新增Tab页面。点击删除按钮,删除Tab页面。实现思路设置Tab组件的barHeight为0,隐藏组件自带的TabBar。Tabs(){...}.barHeight(0)//隐藏tab......
  • HarmonyOS NEXT应用开发之Axios获取解析网络数据
    介绍本示例介绍使用第三方库的Axios获取GBK格式的网络数据时,通过util实现GBK转换UTF-8格式。该场景多用于需要转换编码格式的应用。效果图预览使用说明直接进入页面就可获取GBK格式的用户名信息并进行解码操作。实现思路使用第三方库Axios获取网络数据,并将获取数据类型设......
  • HarmonyOS NEXT应用开发之图片缩放效果实现
    介绍图片预览在应用开发中是一种常见场景,在诸如QQ、微信、微博等应用中均被广泛使用。本模块基于Image组件实现了简单的图片预览功能。使用说明:双指捏合缩放图片大小双击图片进行图片的大小切换图片在放大模式下,滑动图片查看图片的对应位置效果图预览实现思路image组......
  • HarmonyOS 优化布局性能
    背景介绍 用户界面(UI)布局是应用程序中至关重要的部分,它不仅影响应用的外观和用户体验,还直接影响应用的性能。不合理的布局可能会导致过度的布局计算和界面嵌套,从而增加渲染和计算的开销,导致性能下降。 常用布局方式 HarmonyOS的ArkUI框架提供了多种布局方式,包括线性布局......
  • HarmonyOS 性能优化
    如何合理使用动效来获得更好的性能组件转场动画使用transition:推荐使用转场动画(transition)而不是组件动画(animateTo),因为transition只需要在条件改变时更新一次,而animateTo需要在动画前后做两次属性更新,导致性能开销更大。反例:通过改变透明度属性并使用animateTo来......
  • HarmonyOS 项目 module.json5默认配置属性及描述
    module.json5entry>src>main>module.json5是模块的配置文件,包含当前模块的配置信息。 其中module对应的是模块的配置信息,一个模块对应一个打包后的hap包,hap包全称是HarmonyOSAbilityPackage,其中包含了ability、第三方库、资源和配置文件。其具体属性及其描述可以参照下表1。......
  • docker compose部署nextcloud
    dockercompose部署nextcloudversion:'3'services:nextcloud:image:nextcloudcontainer_name:nextcloudenvironment:-MYSQL_HOST=nextcloud-db-MYSQL_DATABASE=nextcloud-MYSQL_USER=nextcloud-MYSQL_PASSWOR......
  • 鸿蒙HarmonyOS实战-ArkUI组件(Shape)
    ......
  • HarmonyOS-基础之动画
    1、是什么?动画可以在UI界面发生变化时,提供渐变过渡效果,提升用户体验。动画的实现原理是通过在一段时间内连续播放一系列静止画面(帧),从而产生流畅的视觉效果。ArkUI提供了多种动画接口,例如:显式动画、属性动画、转场动画等,来实现各种动画效果。2、显示动画animateTo()是ArkUI......