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

使用说明
- 点击页面“AKI跨线程调用JS函数”按钮,每次点击,显示数值加10。
实现思路
以下是使用AKI和NPAI的libuv实现跨线程调用JS函数的实现对比:
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); }
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);
}
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实现的代码量要少很多,使用也更加方便。
高性能知识点
- 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函数页面
模块依赖
- 本实例依赖AKI。
- 本实例依赖common模块来实现公共组件FunctionDescription。
参考资料
HarmonyOS NEXT应用开发之使用AKI轻松实现跨语言调用的更多相关文章
- EasyPlayerPro(Windows)流媒体播放器开发之跨语言调用
下面我们来讲解一下关于EasyPlayerPro接口的调用,主要分为C++和C#两种语言,C++也可以基于VC和QT进行开发,C++以VC MFC框架为例进行讲解,C#以Winform框架为例进行讲解 ...
- HarmonyOS三方件开发指南(12)——cropper图片裁剪
鸿蒙入门指南,小白速来!0基础学习路线分享,高效学习方法,重点答疑解惑--->[课程入口] 目录:1. cropper组件功能介绍2. cropper使用方法3. cropper组件开发实现4. ...
- HarmonyOS三方件开发指南(13)-SwipeLayout侧滑删除
鸿蒙入门指南,小白速来!0基础学习路线分享,高效学习方法,重点答疑解惑--->[课程入口] 目录:1. SwipeLayout组件功能介绍2. SwipeLayout使用方法3. SwipeLa ...
- HarmonyOS三方件开发指南(14)-Glide组件功能介绍
<HarmonyOS三方件开发指南>系列文章合集 引言 在实际应用开发中,会用到大量图片处理,如:网络图片.本地图片.应用资源.二进制流.Uri对象等,虽然官方提供了PixelMap进行图 ...
- HarmonyOS三方件开发指南(15)-LoadingView功能介绍
目录: 1. LoadingView组件功能介绍2. Lottie使用方法3. Lottie开发实现4.<HarmonyOS三方件开发指南>系列文章合集 1. LoadingView组件功 ...
- HarmonyOS三方件开发指南(16)-VideoCache 视频缓存
目录: 1.引言 2.功能介绍 3.VideoCache使用指南 4.VideoCache开发指南 5.<HarmonyOS三方件开发指南>系列文章合集 引言 对于视频播放器这个app大家 ...
- HarmonyOS三方件开发指南(17)-BottomNavigationBar
目录: 1.引言 2.功能介绍 3.BottomNavigationBar使用指南 4.BottomNavigationBar开发指南 5.<HarmonyOS三方件开发指南>文章合集 引 ...
- HarmonyOS三方件开发指南(19)-BGABadgeView徽章组件
目录: 1.引言 2.功能介绍 3.BGABadgeView 使用指南 4.BGABadgeView 开发指南 5.<HarmonyOS三方件开发指南>系列文章合集 引言 现在很多的APP ...
- Android Camera开发系列(上)——Camera的基本调用与实现拍照功能以及获取拍照图片加载大图片
Android Camera开发系列(上)--Camera的基本调用与实现拍照功能以及获取拍照图片加载大图片 最近也是在搞个破相机,兼容性那叫一个不忍直视啊,于是自己翻阅了一些基本的资料,自己实现了一 ...
- 使用 PySide2 开发 Maya 插件系列三:qt语言国际化(internationalization)
使用 PySide2 开发 Maya 插件系列三:qt语言国际化(internationalization) 前言: 这是 qt for python 的语言国际化,基于 UI 的,python 也有 ...
随机推荐
- 2023中山市第三届香山杯网络安全大赛初赛wp
序 被带飞了 PWN move 先往变量 sskd 写入 0x20 字节,往第二个输入点输入 0x12345678 即可进入到第三个输入点,存在 0x8 字节的溢出.思路是在第一个输入点布置 rop ...
- 3D渲染慢,直接买显卡还是用云渲染更划算?
3D渲染对建筑师和设计师来说并不陌生,3D渲染的过程中出现渲染卡顿.特殊材质难以渲染,或者本地配置不足.本地渲染资源不够时,常常会影响工作效率.本文比较了3D渲染时,为提高工作效率,买显卡还是用云渲染 ...
- HTML(表格、列表、表单)
表格 表格的主要作用 1.表格主要用于显示.展示数据,因为它可以让数据显示的非常的规整,可读性非常好.特别是后台展示数据的时候,能够熟练运用表格就显得很重要.一个清爽简约的表格能够把繁杂的数据表现得很 ...
- 【Leetcode】300. 最长递增子序列
题目(链接) 给你一个整数数组nums,找到其中最长严格递增子序列的长度. 子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序.例如,[3,6,2,7]是数组[0,3,1 ...
- TP6框架--EasyAdmin学习笔记:数据表添加新参数,如何强制清除缓存
这是我写的学习EasyAdmin的第六章,这一章我给大家分享下如何在数据表中添加新参数,并强制清除缓存 这一章的主题是我在开发中碰到的一个问题,当我在网上疯狂查找解决方法依旧一无所获后,我又尝试了从底 ...
- 记录--手把手教你,用electron实现截图软件
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 背景 因为我们日常开发项目的时候,需要和同事对接api和文档还有UI图,所以有时候要同时打开多个窗口,并在多个窗口中切换,来选择自己要的信 ...
- Spring Boot框架中使用Jackson的处理总结
1.前言 通常我们在使用Spring Boot框架时,如果没有特别指定接口的序列化类型,则会使用Spring Boot框架默认集成的Jackson框架进行处理,通过Jackson框架将服务端响应的数据 ...
- Python - inspect 模块的简单使用
Python中的inspect模块解析 Python的inspect模块是一个强大的内省工具,允许开发者检查(inspect)活动对象和源代码.它提供了一系列函数,用于获取信息关于正在运行的程序和调用 ...
- 使用EasyPoi导出excel时如何避免导出null
使用replace将null替换为空字符串 @Excel(name = "是否自动开始", replace = { "是_1", "否_0" ...
- java实战字符串1:给定两个字符串 s 和 t,判断他们的编辑距离是否为 1。
题目描述给定两个字符串 s 和 t,判断他们的间距是否为 1.(满足以下三个条件) 往 s 中插入一个字符得到 t从 s 中删除一个字符得到 t在 s 中替换一个字符得到 t 例1 输入: ab ac ...