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 modulestatic 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 registerextern "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还是callbackif (asyncContext->callbackRef == nullptr) {// 创建promisenapi_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);// 注册事件handlernetServer-> 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 definestatic 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 registerextern "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)
})
}
}
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70047792/viewspace-2998834/ ,如需转载,请注明出处,否则将追究法律责任。