介紹 針對JS與C/C++跨語言訪問場景,NAPI使用比較繁瑣。而AKI提供了極簡語法糖使用方式,一行代碼完成JS與C/C++的無障礙跨語言互調,使用方便。本示例將介紹使用AKI編寫C++跨線程調用JS函數場景。通過調用C++全局函數,創建子線程來調用JS函數,實現對變數value的加10操作,為開 ...
介紹
針對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。