介紹
針對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。
參考資料
- AKI
- 公共元件FunctionDescription