题外话
目前各大厂商都在进行 HarmonyOS NEXT 的适配,它是对鸿蒙操作系统具有重大意义的版本,将不再兼容 Android 应用,需要使用专用的开发工具(DevEcho-Studio)和开发语言(ArkTS)重新开发 App。
我已经进行 Harmony 系统开发半月有余,也接触到了搭载 HarmonyOS NEXT 的真机,坦率来讲各方面体验并不友好。感觉华为在 HarmonyOS NEXT 上有点操之过急了,操作系统、开发工具、SDK和文档都还有很多需要完善的地方,对接群里几乎每天都有 Bug 报告出来,先行进行适配的开发者算是充当了华为的小白鼠了。
ArkTS 和 Node-API
HarmonyOS 官方的开发语言为 ArkTS:
ArkTS在保持TypeScript(简称TS)基本语法风格的基础上,对TS的动态类型特性施加更严格的约束,引入静态类型。同时,提供了声明式UI、状态管理等相应的能力,让开发者可以以更简洁、更自然的方式开发高性能应用。
Node.js 是跨平台的 JavaScript 运行时环境,HarmonyOS 使用 Node-API 规范接口来提供ArkTS/JS与C/C++模块之间交互能力。
Node-API 的线程约束
主要有两条:
Node-API
接口只能在 JS 线程使用,也即由 JS 调用 Native 的线程才能使用Node-API
接口,而 Native 的子线程无法使用。napi_env
是与 JS 线程绑定的,无法跨线程使用。即使使用全局变量将napi_env
保存,在其他线程使用也是会造成 Crash的。
以上约束实际上是一个意思,即涉及 Node-API 的操作必须在和 JS 的调用线程一致。
为什么会存在线程约束?
来自 ChatGPT 的回答:
主要因为V8引擎的限制。Node-API构建在V8引擎之上,而V8通常对于跨线程共享数据和并发执行的操作有一些限制。为了避免绕过V8引擎的限制,Node-API对线程调用进行了约束。
如何处理 Native 子线程需要调用 JS 方法的问题?
熟悉了 Objective-C/Swift 和 C/C++ 近乎丝滑无痕的交互之后,再来看 JS 和 Native 的交互简直就是灾难。因为 Node.js 运行环境的限制,在 Native 的子线程调用 JS 方法需要特殊处理,Node-API
通过将调用切换到 JS 线程的方式来规避 Node-API 的线程约束限制。具体如下。
1、在Native侧实现调用JS线程获取结果的方法
threadsafe API 可以确保调用到此函数的线程一定是JS线程。
static void hm_threadsafe_func(napi_env env, napi_value js_fun, void *context, void *data) {
size_t argc = 1;
napi_value argv[1];
napi_create_int32(env, *data, &argv[0]);
napi_value out_value = NULL;
napi_status ret = napi_call_function(env, NULL, js_fun, argc, argv, &out_value);
}
2、获取 threadsafe 句柄
定义暴露给 JS 层调用的 hm_register_threadsafe_func
方法。使用 napi_create_threadsafe_function
创建线程安全函数,并获取函数的引用句柄,对该句柄在多线程环境中的调用会异步切换到 JS 线程,最终会在 hm_threadsafe_func
函数中返回结果。
napi_threadsafe_function g_threadsafe_func_handle = NULL;
napi_value hm_register_threadsafe_func(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1] = {NULL};
napi_get_cb_info(env, info, &argc, args, NULL, NULL);//args[0] 为 TS 层传入的 function 引用
if (true) {
// 线程安全
napi_status status = napi_create_threadsafe_function(env, args[0], NULL, "threadsafe_func", 0, 1, NULL, NULL, NULL, hm_threadsafe_func, &g_threadsafe_func_handle);
} else {
// 非线程安全
napi_create_reference(env, args[0], 1, &g_func_ref);
}
return NULL;
}
3、在 Native 子线程中安全调用
void *data = null;//业务数据
napi_status status = napi_call_threadsafe_function(g_threadsafe_func_handle, data, napi_tsfn_blocking);
napi_call_threadsafe_function
将异步调用,调用结果将在 JS 线程中通过 hm_threadsafe_func
方法获取。
如何在子线程中同步获取结果?
上述方案实现了在子线程中调用 JS 代码,实际上只是将调用切换到主线程调用,最终结果将在主线程异步返回。那么,如何在子线程同步获取结果呢?
很遗憾,我没有找到很优雅的实现方案,即使通过 napi_create_async_work
相关接口也都是异步回调。
// C/C++ 子线程中的异步任务。执行耗时的操作,将结果传递给JavaScript层
void MyAsyncTask(napi_env env, napi_value callback) {
napi_async_work asyncWork;
napi_create_async_work(env, NULL, "MyAsyncTask", MyAsyncTaskExecute,
MyAsyncTaskComplete, NULL, &asyncWork);
napi_queue_async_work(env, asyncWork);
}
// 异步任务的执行函数,在 C/C++ 子线程中进行
void MyAsyncTaskExecute(napi_env env, void* data) {
}
// 异步任务完成时的回调函数,结果传递给 JavaScript 层
void MyAsyncTaskComplete(napi_env env, napi_status status, void* data) {
}
那么,如果有此类需求只好使用信号量等常规手段在子线程获取结果了。如果你有便捷的实现方案,欢迎交流学习。
留言板