OpenHarmony 源碼解析之JavaScript API框架(NAPI)
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
1.NAPI概念
1.1 JS API概念
JS API: JavaScript Application Programming Interface, JavaScript應(yīng)用程序編程接口。
1.2 JS API實現(xiàn)方式
OpenHarmony上JS API實現(xiàn)方式有三種,分別是:JSI機(jī)制、Channel機(jī)制、NAPI機(jī)制。
JSI機(jī)制:L0~L1設(shè)備支持。
Channel機(jī)制:L3設(shè)備支持。
NAPI機(jī)制:目前僅L2設(shè)備支持,后續(xù)須推廣到L3~L5設(shè)備。

1.3 NAPI概念
一句話概括NAPI,就是L2設(shè)備上的 JS API實現(xiàn)方式。
2.NAPI機(jī)制介紹
2.1 實現(xiàn)原則
優(yōu)先封裝異步方法!同步方法可待社區(qū)反饋需要時再行添加。
若引擎開啟Promise特性支持,則異步方法必須同時支持Callback方式和Promise方式。使用哪種方式由應(yīng)用開發(fā)者決定,以是否傳遞Callback進(jìn)行區(qū)分。不傳遞Callback即為Promise方式,方法執(zhí)行結(jié)果為Promise實例對象。
- L0到L1設(shè)備上受限于硬件水平,只實現(xiàn)Callback方式的異步方法;
- L2到L5設(shè)備上,必須實現(xiàn)同時支持Callback方式和Promise方式的異步方法。
2.2 異步編程模型
2.2.1Promise 異步模型
Promise 異步模型是 OHOS 標(biāo)準(zhǔn)異步模型之一。
Promise對象: ES6原生提供了Promise對象,Promise是異步編程的一種解決方案,可以替代傳統(tǒng)的解決方案–回調(diào)函數(shù)和事件。promise對象是一個異步操作的結(jié)果,提供了一些API使得異步執(zhí)行可以按照同步的流表示出來,避免了層層嵌套的回調(diào)函數(shù),保證了回調(diào)是以異步的方式進(jìn)行調(diào)用的。用戶在調(diào)用這些接口的時候,接口實現(xiàn)將異步執(zhí)行任務(wù),同時返回一個 Promise 對象,其代表異步操作的結(jié)果。在返回的結(jié)果的個數(shù)超過一個時,其以對象屬性的形式返回。
Promise特點 作為對象,Promise有兩個特點:(1)對象的狀態(tài)不受外界影響;(2)一旦狀態(tài)改變了就不會再變,也就是說任何時候Promise都只有一種狀態(tài)。
2.2.2Callback 異步模型
Callback 異步模型是 OHOS 標(biāo)準(zhǔn)異步模型之一。用戶在調(diào)用這些接口的時候,接口實現(xiàn)將異步執(zhí)行任務(wù)。任務(wù)執(zhí)行結(jié)果以參數(shù)的形式提供給用戶注冊的回調(diào)函數(shù)。這些參數(shù)的第一個是 Error 或 undefined 類型,分別表示執(zhí)行出錯與正常。
2.2 實現(xiàn)步驟
2.2.1 模塊注冊
API集合按業(yè)務(wù)功能進(jìn)行模塊劃分。開發(fā)者使用前須import對應(yīng)的模塊。
命名:@ohos.模塊名
注意:
- 模塊名須唯一,由ACE團(tuán)隊統(tǒng)一維護(hù),子系統(tǒng)新增模塊時須向ACE團(tuán)隊申請。
- 模塊名最好是單個名詞。實在不行,也可以由多個名詞組成,但必須遵循小駝峰命名規(guī)則。
- 一個模塊,一個聲明文件(*.d.ts)。聲明文件命名遵循@ohos.模塊名.d.ts,文件名全小寫,單詞間無分割。
N-API通過注冊函數(shù)進(jìn)行模塊的注冊,其接受一個全局變量參數(shù),全局變量結(jié)構(gòu)體中定義了模塊名及模塊初始化函數(shù)。在模塊的初始化中,我們可以定義模塊需要暴露的方法及屬性。
示例:
- static napi_value StorageExport(napi_env env, napi_value exports)
- {
- const char* storageClassName = "Storage";
- napi_value storageClass = nullptr;
- /* 定義模塊需要對外暴露的方法 */
- static napi_property_descriptor storageDesc[] = {
- DECLARE_NAPI_FUNCTION("get", JSStorageGet),
- DECLARE_NAPI_FUNCTION("getSync", JSStorageGetSync),
- };
- /* 定義C++類對應(yīng)的JavaScript類,包括JS類名、JS構(gòu)造函數(shù) */
- napi_define_class(env, storageClassName, strlen(storageClassName), JSStorageConstructor, nullptr,
- sizeof(storageDesc) / sizeof(storageDesc[0]), storageDesc, &storageClass);
- /* 定義模塊需要對外暴露的屬性 */
- static napi_property_descriptor desc[] = {
- DECLARE_NAPI_PROPERTY("Storage", storageClass),
- };
- /* 設(shè)置exports對象屬性 */
- napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
- return exports;
- }
模塊定義
- static napi_module storageModule = {
- .nm_version = 1,
- .nm_flags = 0,
- .nm_filename = nullptr,
- .nm_register_func = StorageExport,
- .nm_modname = "storage",
- .nm_priv = ((void*)0),
- .reserved = { 0 },
- };
模塊注冊
- extern "C" __attribute__((constructor)) void StorageRegister()
- {
- napi_module_register(&storageModule);
- }
2.2.2 NAPI聲明
聲明文件模板
@ohos.模塊名.d.ts文件:
- /**
- * 模塊描述
- * @since API版本號,IT Release3 對應(yīng) 4,以此類推
- * @sysCap 系統(tǒng)能力
- * @devices 支持設(shè)備
- * @import 導(dǎo)入模塊
- * @permission 權(quán)限列表
- */
- declare namespace 模塊名 {
- // 在此處定義功能方法
- }
- export default 模塊名;
示例:
聲明文件@ohos.storage.d.ts
- /**
- * 存儲
- * @since 3
- * @sysCap ACE Engine
- * @devices phone, tablet, tv, wearable, liteWearable, smartVision
- * @import import storage from '@ohos.storage';
- * @permission N/A
- */
- declare namespace storage {
- // 在此處定義功能方法
- }
- export default storage;
2.2.2 NAPI實現(xiàn)
JS API 調(diào)用流程如下圖所示:

接口定義
- /**入?yún)?*
- napi_env:表示一個上下文的變量;
- napi_callback_info:傳遞給回調(diào)函數(shù)的一個封裝的數(shù)據(jù)類型,可以用于獲取有關(guān)調(diào)用時的上下文信息,也可以用于設(shè)置回調(diào)函數(shù)的返回值;
- **返回值**
- napi_value:對所有js的基本值的一個密閉封裝,就是表示一個基本值;
- */
- static napi_value Get(napi_env env, napi_callback_info info);
- static napi_value GetSync(napi_env env, napi_callback_info info);
2.2.2.1 同步回調(diào)
同步方法調(diào)用之后,將阻塞住JS線程直至獲取到返回值。
命名:動詞+Sync或動詞+名詞+Sync
格式:
- 無參:方法名()
- 有參:方法名Sync(必填參數(shù)[, 可選參數(shù)])
返回值
- 有
聲明文件模板
- declare namespace 模塊名
- {
- /**
- * 方法描述
- * @note 特殊說明
- * @since (可選,方法支持版本與模塊不一致時需標(biāo)明)
- * @sysCap 系統(tǒng)能力
- * @devices 支持設(shè)備 (可選,支持設(shè)備類型與模塊不一致時需標(biāo)明)
- * @param 參數(shù) 參數(shù)說明(可選,沒有參數(shù)或參數(shù)用interface包含時不需要標(biāo)明)
- * @return 返回值說明(可選,沒有返回值或返回值用interface包含時不需要標(biāo)明)
- */
- // 無參
- function 方法名Sync(): 返回值類型;
- // 有參
- function 方法名Sync(必填參數(shù): 參數(shù)類型, options?: 可選參數(shù)類型): 返回值類型;
- interface 可選參數(shù)類型 {
- 參數(shù)名: 參數(shù)類型;
- }
- }
- export default 模塊名;
示例
聲明
- declare namespace storage {
- /**
- * getSync方法描述
- * @since 6
- * @sysCap ACE Enginge
- * @param key key值說明
- * @return 返回值說明
- */
- function getSync(key: string, options?: GetStorageOptions): string;
- interface GetStorageOptions {
- /**
- * default參數(shù)描述
- * @since 6
- * @sysCap ACE Enginge
- */
- default: string;
- }
- }
- export default storage;
實現(xiàn)
- static napi_value GetSync(napi_env env, napi_callback_info info)
- {
- size_t requireArgc = 1;
- size_t argc = 2; //參數(shù)個數(shù)
- napi_value argv[2] = { 0 }; //參數(shù)定義
- napi_value thisVar = nullptr; //JS對象的this參數(shù)
- void* data = nullptr; //回調(diào)數(shù)據(jù)指針
- /* 根據(jù)環(huán)境變量獲取參數(shù) */
- napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
- NAPI_ASSERT(env, argc >= requireArgc, "requires 1 parameter");
- char key[KEY_BUFFER_SIZE] = { 0 };
- size_t keyLen = 0;
- char value[VALUE_BUFFER_SIZE] = { 0 };
- size_t valueLen = 0;
- for (size_t i = 0; i < argc; i++) {
- napi_valuetype valueType = napi_undefined;
- napi_typeof(env, argv[i], &valueType);
- if (i == 0 && valueType == napi_string) {
- /* 根據(jù)JS字符串獲取對應(yīng)的UTF8編碼格式的C/C++字符串 */
- napi_get_value_string_utf8(env, argv[i], key, KEY_BUFFER_SIZE, &keyLen);
- } else if (i == 1 && valueType == napi_string) {
- napi_get_value_string_utf8(env, argv[i], value, VALUE_BUFFER_SIZE, &valueLen);
- break;
- } else {
- NAPI_ASSERT(env, false, "type mismatch");
- }
- }
- StorageObjectInfo* objectInfo = nullptr;
- /* 根據(jù)JS對象獲取與之綁定的原生對象實例 */
- napi_unwrap(env, thisVar, (void**)&objectInfo);
- auto itr = g_keyValueStorage.find(key);
- napi_value result = nullptr; // JS字符串對象
- if (itr != g_keyValueStorage.end()) {
- /* 根據(jù)UTF8編碼格式的 C/C++字符串 創(chuàng)建一個 JS字符串對象 */
- napi_create_string_utf8(env, itr->second.c_str(), itr->second.length(), &result);
- } else if (valueLen > 0) {
- napi_create_string_utf8(env, value, valueLen, &result);
- } else {
- objectInfo->Emit(nullptr, "error");
- NAPI_ASSERT(env, false, "key does not exist");
- }
- return result; //返回JS對象
- }
2.2.2.2 異步回調(diào)
異步方法調(diào)用整個過程不會阻礙調(diào)用者的工作。
命名:動詞或動詞+名詞
格式:
- 無參:方法名([回調(diào)函數(shù)])
- 有參:方法名(必填參數(shù)[, 可選參數(shù)][, 回調(diào)函數(shù)])
返回值
- 若回調(diào)函數(shù)非空,則返回void
- 若回調(diào)函數(shù)為空,則返回Promise實例對象
聲明文件模板
- declare namespace 模塊名 {
- /**
- * 方法描述
- * @note 特殊說明
- * @since (可選,方法支持版本與模塊不一致時需標(biāo)明)
- * @sysCap 系統(tǒng)能力
- * @devices 支持設(shè)備 (可選,支持設(shè)備類型與模塊不一致時需標(biāo)明)
- * @param 參數(shù) 參數(shù)說明(可選,沒有參數(shù)或參數(shù)用interface包含時不需要標(biāo)明)
- */
- // 無參
- function 方法名(callback: AsyncCallback<結(jié)果數(shù)據(jù)類型>): void;
- function 方法名(): Promise<結(jié)果數(shù)據(jù)類型>;
- // 有參
- function 方法名(必填參數(shù): 參數(shù)類型, callback: AsyncCallback<結(jié)果數(shù)據(jù)類型>): void;
- function 方法名(必填參數(shù): 參數(shù)類型, options: 可選參數(shù)類型, callback: AsyncCallback<結(jié)果數(shù)據(jù)類型>): void;
- function 方法名(必填參數(shù): 參數(shù)類型, options?: 可選參數(shù)類型): Promise<結(jié)果數(shù)據(jù)類型>;
- interface 可選參數(shù)類型 {
- 參數(shù)名: 參數(shù)類型;
- }
- }
- export default 模塊名;
示例:
聲明
- import { AsyncCallback } from './basic';
- declare namespace storage {
- /**
- * get方法描述
- * @note N/A
- * @since 5
- * @sysCap ACE Engine
- * @devices phone, tablet, tv, wearable
- * @param key key值說明
- */
- function get(key: string, callback: AsyncCallback<string>): void;
- function get(key: string, options: GetStorageOptions, callback: AsyncCallback<string>): void;
- function get(key: string, options?: GetStorageOptions): Promise<string>;
- interface GetStorageOptions {
- default: string;
- }
- }
- export default storage;
實現(xiàn)
異步回調(diào)流程如下圖所示:

- static napi_value Get(napi_env env, napi_callback_info info)
- {
- size_t requireArgc = 1;
- size_t argc = 3; //參數(shù)個數(shù)
- napi_value argv[3] = { 0 }; //參數(shù)定義
- napi_value thisVar = nullptr; //JS對象的this參數(shù)
- void* data = nullptr; //回調(diào)數(shù)據(jù)指針
- /* 根據(jù)環(huán)境變量獲取參數(shù) */
- napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
- NAPI_ASSERT(env, argc >= requireArgc, "requires 1 parameter");
- /* 異步接口上下文,用于接收J(rèn)S接口傳進(jìn)來的環(huán)境變量、參數(shù)、回調(diào)函數(shù)、接口返回值等*/
- auto asyncContext = new StorageAsyncContext();
- asyncContext->env = env;
- for (size_t i = 0; i < argc; i++) {
- napi_valuetype valueType = napi_undefined;
- napi_typeof(env, argv[i], &valueType);
- if ((i == 0) && (valueType == napi_string)) {
- /* 根據(jù)JS字符串獲取對應(yīng)的UTF8編碼格式的C/C++字符串 */
- napi_get_value_string_utf8(env, argv[i], asyncContext->key, KEY_BUFFER_SIZE, &asyncContext->keyLen);
- } else if (valueType == napi_string) {
- napi_get_value_string_utf8(env, argv[i], asyncContext->value, VALUE_BUFFER_SIZE, &asyncContext->valueLen);
- } else if (valueType == napi_function) {
- /* 根據(jù)JS對象參數(shù)argv[i]新建引用 */
- napi_create_reference(env, argv[i], 1, &asyncContext->callbackRef);
- break;
- } else {
- NAPI_ASSERT(env, false, "type mismatch");
- }
- }
- napi_value result = nullptr;
- if (asyncContext->callbackRef == nullptr) {
- /* Promise方式異步調(diào)用,創(chuàng)建延遲對象、JS Promise對象,使二者進(jìn)行關(guān)聯(lián) */
- napi_create_promise(env, &asyncContext->deferred, &result);
- } else {
- /* Callback方式異步調(diào)用,不需要返回Promise對象,返回一個JS未定義值 */
- napi_get_undefined(env, &result);
- }
- /* 根據(jù)JS對象獲取與之綁定的原生對象實例 */
- napi_unwrap(env, thisVar, (void**)&asyncContext->objectInfo);
- napi_value resource = nullptr;
- napi_create_string_utf8(env, "JSStorageGet", NAPI_AUTO_LENGTH, &resource); //獲取JS異步資源名稱
- /* 創(chuàng)建異步工作 */
- napi_create_async_work(
- env, nullptr, resource,
- /* 執(zhí)行異步邏輯的原生函數(shù) */
- [](napi_env env, void* data) {
- StorageAsyncContext* asyncContext = (StorageAsyncContext*)data;
- auto itr = g_keyValueStorage.find(asyncContext->key);
- if (itr != g_keyValueStorage.end()) {
- if (strncpy_s(asyncContext->value, VALUE_BUFFER_SIZE, itr->second.c_str(), itr->second.length()) ==
- -1) {
- asyncContext->status = 1; //失敗
- } else {
- asyncContext->status = 0; //成功
- }
- } else {
- asyncContext->status = 1; //失敗
- }
- },
- /* 異步函數(shù)執(zhí)行完成或者取消后,需要執(zhí)行的后處理函數(shù) */
- [](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]);
- asyncContext->objectInfo->Emit(nullptr, "error");
- }
- if (asyncContext->deferred) {
- if (!asyncContext->status) {
- /* 異步函數(shù)執(zhí)行成功后,執(zhí)行成功后處理函數(shù) */
- napi_resolve_deferred(env, asyncContext->deferred, result[1]);
- } else {
- /* 異步函數(shù)執(zhí)行失敗后,執(zhí)行失敗后處理函數(shù) */
- napi_reject_deferred(env, asyncContext->deferred, result[0]);
- }
- } else {
- napi_value callback = nullptr;
- napi_get_reference_value(env, asyncContext->callbackRef, &callback);
- napi_call_function(env, nullptr, callback, sizeof(result) / sizeof(result[0]), result, nullptr);
- napi_delete_reference(env, asyncContext->callbackRef);
- }
- /* 異步回調(diào)完成后進(jìn)行資源釋放 */
- napi_delete_async_work(env, asyncContext->work);
- delete asyncContext;
- },
- /* 用戶數(shù)據(jù)上下文,此數(shù)據(jù)傳遞給異步執(zhí)行函數(shù)與后處理函數(shù) */
- (void*)asyncContext,
- /* 生成的異步工作*/
- &asyncContext->work);
- napi_queue_async_work(env, asyncContext->work); //異步工作入隊列,排隊執(zhí)行
- return result;
- }
3. 應(yīng)用代碼示例
JS應(yīng)用引用NAPI接口時,須先引用接口定義的對應(yīng)模塊,才能進(jìn)行接口的調(diào)用。
- import storage from '@ohos.storage'
- export default {
- testGetSync() {
- //同步接口
- var 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) );
- }
- }
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)