Native API在HarmonyOS应用工程中的使用指导
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命名规则
so命名必须符合以下规则:
● 每个模块对应一个so。
● 如模块名为hello,则so的名字为libhello.so,napi_module中nm_modname字段应为hello,大小写与模块名保持一致,应用使用时写作:import hello from 'libhello.so'。
js对象线程限制
ark引擎会对js对象线程使用进行保护,使用不当会引起应用crash,因此需要遵循如下原则:
● napi接口只能在js线程使用。
● env与线程绑定,不能跨线程使用。native侧js对象只能在创建时的线程使用,即与线程所持有的env绑定。
头文件引入限制
在使用napi的对象和方法时需要引用"napi/native_api.h"。否则在只引入三方库头文件时,会出现接口无法找到的编译报错。
napi_create_async_work接口说明
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 模块展示了同步和异步方法的封装。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);
});
}
}
NetServer 模块——native与js对象绑定
模块简介
本示例展示了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线程中回调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)
})
}
}
Native API在HarmonyOS应用工程中的使用指导的更多相关文章
- Native Application 开发详解(直接在程序中调用 ntdll.dll 中的 Native API,有内存小、速度快、安全、API丰富等8大优点)
文章目录: 1. 引子: 2. Native Application Demo 展示: 3. Native Application 简介: 4. Native Ap ...
- Unity在Android和iOS中如何调用Native API
本文主要是对unity中如何在Android和iOS中调用Native API进行介绍. 首先unity支持在C#中调用C++ dll,这样可以在Android和iOS中提供C++接口在unity中调 ...
- 把 Reative Native 47 版本集成到已有的 Native iOS 工程中
一.搭建开发环境 http://reactnative.cn/docs/0.46/getting-started.html#content 二.创建一个模板 运行以下命令,创建一个最新版本的 reac ...
- react native 之 在现有的iOS工程中集成react native
在现有的iOS工程中集成react native, 或者说将react native引入到iOS 项目,是RN和iOS混合开发的必经之路 参考官网教程:https://reactnative.cn/d ...
- 调用其他VBA工程中的过程和函数以及API函数
Excel VBA中,同一个应用程序下面包括多个工作簿,每个工作簿都有自己独立的VBAProject 在同一个VBA工程中,使用Call即可调用其他模块中的过程和函数,例如: Call Module2 ...
- 【Unity与Android】02-在Unity导出的Android工程中接入Google Admob广告
我在上一篇文章 [Unity与Android]01-Unity与Android交互通信的简易实现) 中介绍了Unity与Android通讯的基本方法. 这一篇开始进入应用阶段,这次要介绍的是如何在An ...
- unity导出工程导入到iOS原生工程中详细步骤
一直想抽空整理一下unity原生工程导入iOS原生工程中的详细步骤.做iOS+vuforia+unity开发这么长时间了.从最初的小小白到现在的小白.中间趟过了好多的坑.也有一些的小小收货.做一个喜欢 ...
- iOS开发--Swift 如何完成工程中Swift和OC的混编桥接(Cocoapods同样适用)
由于SDK现在大部分都是OC版本, 所以假如你是一名主要以Swift语言进行开发的开发者, 就要面临如何让OC和Swift兼容在一个工程中, 如果你没有进行过这样的操作, 会感觉异常的茫然, 不用担心 ...
- step6----->往工程中添加spring boot项目------->修改pom.xml使得我的project是基于spring boot的,而非直接基于spring framework
文章内容概述: spring项目组其实有多个projects,如spring IO platform用于管理external dependencies的版本,通过定义BOM(bill of mater ...
- node-webkit教程(9)native api 之Tray(托盘)
node-webkit教程(9)native api 之Tray(托盘) 文/玄魂 目录 node-webkit教程(9)native api 之Tray(托盘) 前言 9.1 Tray简介 9.2 ...
随机推荐
- adb monkey 有哪些参数?
adb monkey 是 Android Debug Bridge (ADB) 工具中的一个命令,用于执行随机事件来对 Android 应用进行压力测试.以下是 adb monkey 命令的一些常用参 ...
- Java this关键字使用 详解+ 证明
1 package com.bytezero.thistest; 2 /** 3 * 4 * @Description 5 * @author Bytezero·zhenglei! Email:420 ...
- XWAF安装遇到的坑
存在的问题:需要的编译环境没有安装配置好的话出现下面的问题: 1.error: Microsoft Visual C++ 14.0 or greater is required. Get it wit ...
- 内存缓存 Gcache VS Caffeine源码详解
转一篇.后续再尝试自己实践一下
- SSRF概述
SSRF(service side request forgery) 1.攻击的目标: 从外网无法访问的内部系统 2.形成的原因: 大部分是由服务器端提供了从其他服务器应用获取数据的功能.且没有对目标 ...
- C# DiagnosticSource and DiagnosticListener
class Program { private static readonly DiagnosticSource testDiagnosticListener = new DiagnosticList ...
- Oracle中表字段有使用Oracle关键字的一定要趁早改!!!
一.问题由来 现在进行项目改造,数据库需要迁移,由原来的使用GBase数据库改为使用Oracle数据库,今天测试人员在测试时后台报了一个异常. 把SQL语句单独复制出来进行查询,还是报错,仔细分析原因 ...
- vscode 格式化 vue 和 js代码 vetur prettier beautify
这个文档 不涉及eslint 只专注自动格式化 格式化个性化需求: js中 自动去分号 js中 双引号变单引号 最大空换行数 是2 vue template中 属性自动折行 vue 的自动格式化 需要 ...
- 记一个很好用的轻量级翻译软件 copytranslator
软件下载主页: https://gitee.com/ylzheng/CopyTranslator/wikis/windows 可以设置始终置顶,监听剪切板,翻译起来非常方便
- FFmpeg命令行之ffprobe
一.简述 ffprobe是ffmpeg命令行工具中相对简单的,此命令是用来查看媒体文件格式的工具. 二.命令格式 在命令行中输入如下格式的命令: ffprobe [文件名] 三.使用ffprobe查看 ...