赞
踩
HarmonyOS 的应用必须用 js 来桥接 native。需要使用ace_napi仓中提供的 napi 接口来处理 js 交互。napi 提供的接口名与三方 Node.js 一致,目前支持部分接口,符号表见 ace_napi 仓中的 libnapi.ndk.json 文件。
在 DevEco Studio 的模板工程中包含使用 Native API 的默认工程,使用 File->New->Create Project 创建 Native C++模板工程。创建后在 main 目录下会包含 cpp 目录,可以使用 ace_napi 仓下提供的 napi 接口进行开发。
js 侧通过 import 引入 native 侧包含处理 js 逻辑的 so,如:import hello from 'libhello.so',意为使用 libhello.so 的能力,native 侧通过 napi 接口创建的 js 对象会给到应用 js 侧的 hello 对象。
● nm_register_func 对应的函数需要加上 static,防止与其他 so 里的符号冲突。
● 模块注册的入口,即使用__attribute__((constructor))修饰的函数的函数名需要确保不与其他模块重复。
● 每个模块对应一个 so。
● 如模块名为 hello,则 so 的名字为 libhello.so,napi_module 中 nm_modname 字段应为 hello,大小写与模块名保持一致,应用使用时写作:import hello from 'libhello.so'。
ark 引擎会对 js 对象线程使用进行保护,使用不当会引起应用 crash,因此需要遵循如下原则:
● napi 接口只能在 js 线程使用。
● env 与线程绑定,不能跨线程使用。native 侧 js 对象只能在创建时的线程使用,即与线程所持有的 env 绑定。
在使用 napi 的对象和方法时需要引用"napi/native_api.h"。否则在只引入三方库头文件时,会出现接口无法找到的编译报错。
napi_create_async_work 里有两个回调:
● execute:用于异步处理业务逻辑。因为不在 js 线程中,所以不允许调用 napi 的接口。业务逻辑的返回值可以返回到 complete 回调中处理。
● complete:可以调用 napi 的接口,将 execute 中的返回值封装成 js 对象返回。此回调在 js 线程中执行。
napi_status napi_create_async_work(napi_env env,
napi_value async_resource,
napi_value async_resource_name,
napi_async_execute_callback execute,
napi_async_complete_callback complete,
void* data,
napi_async_work* result)
本示例通过实现 storage 模块展示了同步和异步方法的封装。storage 模块实现了数据的保存、获取、删除、清除功能。
- import { AsyncCallback } from './basic';
- declare namespace storage {
- function get(key: string, callback: AsyncCallback<string>): void;
- function get(key: string, defaultValue: string, callback: AsyncCallback<string>): void;
- function get(key: string, defaultValue?: string): Promise<string>;
- function set(key: string, value: string, callback: AsyncCallback<string>): void;
- function remove(key: string, callback: AsyncCallback<void>): void;
- function clear(callback: AsyncCallback<void>): void;
- function getSync(key: string, defaultValue?: string): string;
- function setSync(key: string, value: string): void;
- function removeSync(key: string): void;
- function clearClear(): void;
- }
- export default storage;
-
完整代码参见仓下路径:OpenHarmony/arkui_napi仓库 sample/native_module_storage/
1、模块注册
如下,注册了 4 个同步接口(getSync、setSync、removeSync、clearSync)、4 个异步接口(get、set、remove、clear)。
- /***********************************************
- * Module export and register
- ***********************************************/
- static napi_value StorageExport(napi_env env, napi_value exports)
- {
- napi_property_descriptor desc[] = {
- DECLARE_NAPI_FUNCTION("get", JSStorageGet),
- DECLARE_NAPI_FUNCTION("set", JSStorageSet),
- DECLARE_NAPI_FUNCTION("remove", JSStorageDelete),
- DECLARE_NAPI_FUNCTION("clear", JSStorageClear),
-
- DECLARE_NAPI_FUNCTION("getSync", JSStorageGetSync),
- DECLARE_NAPI_FUNCTION("setSync", JSStorageSetSync),
- DECLARE_NAPI_FUNCTION("deleteSync", JSStorageDeleteSync),
- DECLARE_NAPI_FUNCTION("clearSync", JSStorageClearSync),
- };
- NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
- return exports;
- }
-
- // storage module
- static napi_module storage_module = {.nm_version = 1,
- .nm_flags = 0,
- .nm_filename = nullptr,
- .nm_register_func = StorageExport,
- .nm_modname = "storage",
- .nm_priv = ((void*)0),
- .reserved = {0}};
-
- // storage module register
- extern "C" __attribute__((constructor)) void StorageRegister()
- {
- napi_module_register(&storage_module);
- }
-

2、getSync 函数实现
如上注册时所写,getSync 对应的函数是 JSStorageGetSync 。从 gKeyValueStorage 中获取数据后,创建一个字符串对象并返回。
- static napi_value JSStorageGetSync(napi_env env, napi_callback_info info)
- {
- GET_PARAMS(env, info, 2);
- NAPI_ASSERT(env, argc >= 1, "requires 1 parameter");
- char key[32] = {0};
- size_t keyLen = 0;
- char value[128] = {0};
- size_t valueLen = 0;
-
- // 参数解析
- for (size_t i = 0; i < argc; i++) {
- napi_valuetype valueType;
- napi_typeof(env, argv[i], &valueType);
-
- if (i == 0 && valueType == napi_string) {
- napi_get_value_string_utf8(env, argv[i], key, 31, &keyLen);
- } else if (i == 1 && valueType == napi_string) {
- napi_get_value_string_utf8(env, argv[i], value, 127, &valueLen);
- break;
- } else {
- NAPI_ASSERT(env, false, "type mismatch");
- }
- }
-
- // 获取数据的业务逻辑,这里简单地从一个全局变量中获取
- auto itr = gKeyValueStorage.find(key);
- napi_value result = nullptr;
- if (itr != gKeyValueStorage.end()) {
- // 获取到数据后创建一个string类型的JS对象
- napi_create_string_utf8(env, itr->second.c_str(), itr->second.length(), &result);
- } else if (valueLen > 0) {
- // 没有获取到数据使用默认值创建JS对象
- napi_create_string_utf8(env, value, valueLen, &result);
- } else {
- NAPI_ASSERT(env, false, "key does not exist");
- }
- // 返回结果
- return result;
- }
-

3、get 函数实现
如上注册时所写,get 对应的函数式 JSStorageGet。
- static napi_value JSStorageGet(napi_env env, napi_callback_info info)
- {
- GET_PARAMS(env, info, 3);
- NAPI_ASSERT(env, argc >= 1, "requires 1 parameter");
-
- // StorageAsyncContext是自己定义的一个类,用于保存执行过程中的数据
- StorageAsyncContext* asyncContext = new StorageAsyncContext();
-
- asyncContext->env = env;
-
- // 获取参数
- for (size_t i = 0; i < argc; i++) {
- napi_valuetype valueType;
- napi_typeof(env, argv[i], &valueType);
-
- if (i == 0 && valueType == napi_string) {
- napi_get_value_string_utf8(env, argv[i], asyncContext->key, 31, &asyncContext->keyLen);
- } else if (i == 1 && valueType == napi_string) {
- napi_get_value_string_utf8(env, argv[i], asyncContext->value, 127, &asyncContext->valueLen);
- } else if (i == 1 && valueType == napi_function) {
- napi_create_reference(env, argv[i], 1, &asyncContext->callbackRef);
- break;
- } else if (i == 2 && valueType == napi_function) {
- napi_create_reference(env, argv[i], 1, &asyncContext->callbackRef);
- } else {
- NAPI_ASSERT(env, false, "type mismatch");
- }
- }
-
- napi_value result = nullptr;
-
- // 根据参数判断开发者使用的是promise还是callback
- if (asyncContext->callbackRef == nullptr) {
- // 创建promise
- napi_create_promise(env, &asyncContext->deferred, &result);
- } else {
- napi_get_undefined(env, &result);
- }
-
- napi_value resource = nullptr;
- napi_create_string_utf8(env, "JSStorageGet", NAPI_AUTO_LENGTH, &resource);
-
- napi_create_async_work(
- env, nullptr, resource,
- // 回调1:此回调由napi异步执行,里面就是需要异步执行的业务逻辑。由于是异步线程执行,所以不要在此通过napi接口操作JS对象。
- [](napi_env env, void* data) {
- StorageAsyncContext* asyncContext = (StorageAsyncContext*)data;
- auto itr = gKeyValueStorage.find(asyncContext->key);
- if (itr != gKeyValueStorage.end()) {
- strncpy_s(asyncContext->value, 127, itr->second.c_str(), itr->second.length());
- asyncContext->status = 0;
- } else {
- asyncContext->status = 1;
- }
- },
- // 回调2:此回调在上述异步回调执行完后执行,此时回到了JS线程来回调开发者传入的回调
- [](napi_env env, napi_status status, void* data) {
- StorageAsyncContext* asyncContext = (StorageAsyncContext*)data;
- napi_value result[2] = {0};
- if (!asyncContext->status) {
- napi_get_undefined(env, &result[0]);
- napi_create_string_utf8(env, asyncContext->value, strlen(asyncContext->value), &result[1]);
- } else {
- napi_value message = nullptr;
- napi_create_string_utf8(env, "key does not exist", NAPI_AUTO_LENGTH, &message);
- napi_create_error(env, nullptr, message, &result[0]);
- napi_get_undefined(env, &result[1]);
- }
- if (asyncContext->deferred) {
- // 如果走的是promise,那么判断回调1的结果
- if (!asyncContext->status) {
- // 回调1执行成功(status为1)时触发,也就是触发promise里then里面的回调
- napi_resolve_deferred(env, asyncContext->deferred, result[1]);
- } else {
- // 回调1执行失败(status为0)时触发,也就是触发promise里catch里面的回调
- napi_reject_deferred(env, asyncContext->deferred, result[0]);
- }
- } else {
- // 如果走的是callback,则通过napi_call_function调用callback回调返回结果
- napi_value callback = nullptr;
- napi_value returnVal;
- napi_get_reference_value(env, asyncContext->callbackRef, &callback);
- napi_call_function(env, nullptr, callback, 2, result, &returnVal);
- napi_delete_reference(env, asyncContext->callbackRef);
- }
- napi_delete_async_work(env, asyncContext->work);
- delete asyncContext;
- },
- (void*)asyncContext, &asyncContext->work);
- napi_queue_async_work(env, asyncContext->work);
-
- return result;
- }
-

4、js 示例代码
import storage from 'libstorage.so';
export default {
testGetSync() {
const name = storage.getSync('name');
console.log('name is ' + name);
},
testGet() {
storage.get('name')
.then(date => {
console.log('name is ' + data);
})
.catch(error => {
console.log('error: ' + error);
});
}
}
本示例展示了 on/off/once 订阅方法的实现,同时也包含了 C++ 与 js 对象通过 wrap 接口的绑定。NetServer 模块实现了一个网络服务。
export class NetServer {
function start(port: number): void;
function stop(): void;
function on('start' | 'stop', callback: Function): void;
function once('start' | 'stop', callback: Function): void;
function off('start' | 'stop', callback: Function): void;
}
完整代码参见:OpenHarmony/arkui_napi仓库 sample/native_module_netserver/
1、模块注册
- static napi_value NetServer::Export(napi_env env, napi_value exports)
- {
- const char className[] = "NetServer";
- napi_property_descriptor properties[] = {
- DECLARE_NAPI_FUNCTION("start", JS_Start),
- DECLARE_NAPI_FUNCTION("stop", JS_Stop),
- DECLARE_NAPI_FUNCTION("on", JS_On),
- DECLARE_NAPI_FUNCTION("once", JS_Once),
- DECLARE_NAPI_FUNCTION("off", JS_Off),
- };
- napi_value netServerClass = nullptr;
-
- napi_define_class(env, className, sizeof(className), JS_Constructor, nullptr, countof(properties), properties,
- &netServerClass);
-
- napi_set_named_property(env, exports, "NetServer", netServerClass);
-
- return exports;
- }
-

2、在构造函数中绑定 C++ 与 JS 对象
napi_value NetServer::JS_Constructor(napi_env env, napi_callback_info cbinfo)
{
napi_value thisVar = nullptr;
void* data = nullptr;
napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisVar, &data);
// C++ Native对象,准备与JS对象映射在一起
NetServer* netServer = new NetServer(env, thisVar);
// 使用napi_wrap将netServer与thisVar(即当前创建的这个JS对象)做绑定
napi_wrap(
env, thisVar, netServer,
// JS对象由引擎自动回收释放,当JS对象释放时触发该回调,在改回调中释放netServer
[](napi_env env, void* data, void* hint) {
printf("NetServer::Destructor\n");
NetServer* netServer = (NetServer*)data;
delete netServer;
},
nullptr, nullptr);
return thisVar;
}
3、从 JS 对象中取出 C++ 对象
napi_value NetServer::JS_Start(napi_env env, napi_callback_info cbinfo)
{
size_t argc = 1;
napi_value argv[1] = {0};
napi_value thisVar = nullptr;
void* data = nullptr;
napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, &data);
NetServer* netServer = nullptr;
// 通过napi_unwrap从thisVar中取出C++对象
napi_unwrap(env, thisVar, (void**)&netServer);
NAPI_ASSERT(env, argc >= 1, "requires 1 parameter");
napi_valuetype valueType;
napi_typeof(env, argv[0], &valueType);
NAPI_ASSERT(env, valueType == napi_number, "type mismatch for parameter 1");
int32_t port = 0;
napi_get_value_int32(env, argv[0], &port);
// 开启服务
netServer->Start(port);
napi_value result = nullptr;
napi_get_undefined(env, &result);
return result;
}
netServer->Start 后回调通过 on 注册的 start 事件。
int NetServer::Start(int port)
{
printf("NetServer::Start thread_id: %ld \n", uv_thread_self());
struct sockaddr_in addr;
int r;
uv_ip4_addr("0.0.0.0", port, &addr);
r = uv_tcp_init(loop_, &tcpServer_);
if (r) {
fprintf(stderr, "Socket creation error\n");
return 1;
}
r = uv_tcp_bind(&tcpServer_, (const struct sockaddr*)&addr, 0);
if (r) {
fprintf(stderr, "Bind error\n");
return 1;
}
r = uv_listen((uv_stream_t*)&tcpServer_, SOMAXCONN, OnConnection);
if (r) {
fprintf(stderr, "Listen error %s\n", uv_err_name(r));
return 1;
}
// 服务启动后触发“start”事件
Emit("start", nullptr);
return 0;
}
4、注册或释放(on/off/once)事件,以 on 为例
- napi_value NetServer::JS_On(napi_env env, napi_callback_info cbinfo)
- {
- size_t argc = 2;
- napi_value argv[2] = {0};
- napi_value thisVar = 0;
- void* data = nullptr;
- napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, &data);
-
- NetServer* netServer = nullptr;
- // 通过napi_unwrap取出NetServer指针
- napi_unwrap(env, thisVar, (void**)&netServer);
-
- NAPI_ASSERT(env, argc >= 2, "requires 2 parameter");
-
- // 参数类型校验
- napi_valuetype eventValueType;
- napi_typeof(env, argv[0], &eventValueType);
- NAPI_ASSERT(env, eventValueType == napi_string, "type mismatch for parameter 1");
-
- napi_valuetype eventHandleType;
- napi_typeof(env, argv[1], &eventHandleType);
- NAPI_ASSERT(env, eventHandleType == napi_function, "type mismatch for parameter 2");
-
- char type[64] = {0};
- size_t typeLen = 0;
-
- napi_get_value_string_utf8(env, argv[0], type, 63, &typeLen);
-
- // 注册事件handler
- netServer->On((const char*)type, argv[1]);
-
- napi_value result = nullptr;
- napi_get_undefined(env, &result);
- return result;
- }
-

5、js 示例代码
import { NetServer } from 'libnetserver.so';
export default {
testNetServer() {
var netServer = new NetServer();
netServer.on('start', (event) => {});
netServer.start(1000); // 端口号1000, start完成后回调上面注册的 “start” 回调
}
}
本示例介绍如何在非 JS 线程中回调 JS 应用的回调函数。例如 JS 应用中注册了某个 sensor 的监听,这个 sensor 的数据是由一个 SA 服务来上报的,当 SA 通过 IPC 调到客户端时,此时的执行线程是一个 IPC 通信线程,与应用的 JS 线程是两个不同的线程。这时就需要将执行 JS 回调的任务抛到 JS 线程中才能执行,否则会出现崩溃。
完整代码参见:OpenHarmony/arkui_napi仓库 sample/native_module_callback/
1、模块注册
如下,注册了 1 个接口 test,会传入一个参数,类型为包含一个参数的函数。
- /***********************************************
- * Module export and register
- ***********************************************/
- static napi_value CallbackExport(napi_env env, napi_value exports)
- {
- static napi_property_descriptor desc[] = {
- DECLARE_NAPI_FUNCTION("test", JSTest)
- };
- NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
- return exports;
- }
-
- // callback module define
- static napi_module callbackModule = {
- .nm_version = 1,
- .nm_flags = 0,
- .nm_filename = nullptr,
- .nm_register_func = CallbackExport,
- .nm_modname = "callback",
- .nm_priv = ((void*)0),
- .reserved = { 0 },
- };
-
- // callback module register
- extern "C" __attribute__((constructor)) void CallbackTestRegister()
- {
- napi_module_register(&callbackModule);
- }
-

2、获取 env 中的 loop,抛任务回 JS 线程
- #include <thread>
-
- #include "napi/native_api.h"
- #include "napi/native_node_api.h"
-
- #include "uv.h"
-
- struct CallbackContext {
- napi_env env = nullptr;
- napi_ref callbackRef = nullptr;
- int retData = 0;
- };
-
- void callbackTest(CallbackContext* context)
- {
- uv_loop_s* loop = nullptr;
- // 此处的env需要在注册JS回调时保存下来。从env中获取对应的JS线程的loop。
- napi_get_uv_event_loop(context->env, &loop);
-
- // 创建uv_work_t用于传递私有数据,注意回调完成后需要释放内存,此处省略生成回传数据的逻辑,传回int类型1。
- uv_work_t* work = new uv_work_t;
- context->retData = 1;
- work->data = (void*)context;
-
- // 调用libuv接口抛JS任务到loop中执行。
- uv_queue_work(
- loop,
- work,
- // 此回调在另一个普通线程中执行,用于处理异步任务,回调执行完后执行下面的回调。本场景下该回调不需要执行任务。
- [](uv_work_t* work) {},
- // 此回调会在env对应的JS线程中执行。
- [](uv_work_t* work, int status) {
- CallbackContext* context = (CallbackContext*)work->data;
- napi_handle_scope scope = nullptr;
- // 打开handle scope用于管理napi_value的生命周期,否则会内存泄露。
- napi_open_handle_scope(context->env, &scope);
- if (scope == nullptr) {
- return;
- }
-
- // 调用napi。
- napi_value callback = nullptr;
- napi_get_reference_value(context->env, context->callbackRef, &callback);
- napi_value retArg;
- napi_create_int32(context->env, context->retData, &retArg);
- napi_value ret;
- napi_call_function(context->env, nullptr, callback, 1, &retArg, &ret);
- napi_delete_reference(context->env, context->callbackRef);
-
- // 关闭handle scope释放napi_value。
- napi_close_handle_scope(context->env, scope);
-
- // 释放work指针。
- if (work != nullptr) {
- delete work;
- }
-
- delete context;
- }
- );
- }
-
- static napi_value JSTest(napi_env env, napi_callback_info info)
- {
- size_t argc = 1;
- napi_value argv[1] = { 0 };
- napi_value thisVar = nullptr;
- void* data = nullptr;
- napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
-
- // 获取第一个入参,即需要后续触发的回调函数
- napi_valuetype valueType = napi_undefined;
- napi_typeof(env, argv[0], &valueType);
- if (valueType != napi_function) {
- return nullptr;
- }
- // 存下env与回调函数,用于传递
- auto asyncContext = new CallbackContext();
- asyncContext->env = env;
- napi_create_reference(env, argv[0], 1, &asyncContext->callbackRef);
- // 模拟抛到非js线程执行逻辑
- std::thread testThread(callbackTest, asyncContext);
- testThread.detach();
-
- return nullptr;
- }
-

3、 js 示例代码
import callback from 'libcallback.so';
export default {
testcallback() {
callback.test((data) => {
console.error('test result = ' + data)
})
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。