Native API 在 HarmonyOS 應用工程中的使用指導
HarmonyOS 的應用必須用 js 來橋接 native。需要使用 倉中提供的 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;
具體實現
完整程式碼參見倉下路徑: 倉庫 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;
}
具體實現
完整程式碼參見: 倉庫 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 執行緒中才能執行,否則會出現崩潰。
具體實現
完整程式碼參見: 倉庫 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)
})
}
}
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70009402/viewspace-2998834/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- HarmonyOS NEXT應用開發—在Native側實現進度通知功能
- HarmonyOS 應用事件打點開發指導事件
- HarmonyOS:NativeWindow 開發指導
- 淺析HMS Core開放能力在HarmonyOS中的應用
- 在 HarmonyOS 上使用 ArkUI 實現計步器應用UI
- IoC在ASP.NET Web API中的應用ASP.NETWebAPI
- 在 Laravel 應用中構建 GraphQL APILaravelAPI
- HarmonyOS電話服務開發指導
- PDM系統在飼料工程設計中的應用
- 雲原生 Cloud Native 在企業中的應用與發展趨勢Cloud
- API資料介面在電子商務中的應用API
- 前沿探索|AI 在 API 開發測試中的應用AIAPI
- Android應用開發中如何使用隱藏的APIAndroidAPI
- HarmonyOS音訊開發指導:使用AudioRenderer開發音訊播放功能音訊
- 商品API資料在電商中的應用與實現API
- API介面在電商商品資料獲取中的應用API
- HarmonyOS音訊開發指導:使用OpenSL ES開發音訊播放功能音訊
- pickle模組 collections模組在物件導向中的應用物件
- 揭秘|Axway API在銀行業的應用API行業
- 淘寶詳情API介面在各種應用中的作用性API
- 為什麼在BI應用中,指標管理是重中之重指標
- 如何使用DevEco Studio建立Native C++應用devC++
- HarmonyOS:使用本地真機執行應用/服務
- 在spring boot3中使用native imageSpring Boot
- wxPython使用指導Python
- Git使用指導Git
- 使用 AI 在醫療影像分析中的應用探索AI
- harmonyOS應用-TableLayout佈局
- redis在nodejs中的應用RedisNodeJS
- redis在python中的應用RedisPython
- Refs 在React中的應用React
- HMM在NLP中的應用HMM
- MQTT 在 Elixir 中的應用MQQT
- 優雅地使用TypeScript開發React Native應用TypeScriptReact Native
- WWDC 2018:車載(CarPlay)在音訊和導航 APP 中的應用音訊APP
- 室內電子地圖在室內定位導航中的應用地圖
- HarmonyOS:儲存你的應用資料
- HarmonyOS:給您的應用新增通知(1)