OpenHarmony 源碼解析之JavaScript API框架(NAPI)
1.NAPI概念
1.1 JS API概念
JS API: JavaScript Application Programming Interface, JavaScript應用程序編程接口。
1.2 JS API實現方式
OpenHarmony上JS API實現方式有三種,分別是:JSI機制、Channel機制、NAPI機制。
JSI機制:L0~L1設備支持。
Channel機制:L3設備支持。
NAPI機制:目前僅L2設備支持,后續須推廣到L3~L5設備。

1.3 NAPI概念
一句話概括NAPI,就是L2設備上的 JS API實現方式。
2.NAPI機制介紹
2.1 實現原則
優先封裝異步方法!同步方法可待社區反饋需要時再行添加。
若引擎開啟Promise特性支持,則異步方法必須同時支持Callback方式和Promise方式。使用哪種方式由應用開發者決定,以是否傳遞Callback進行區分。不傳遞Callback即為Promise方式,方法執行結果為Promise實例對象。
- L0到L1設備上受限于硬件水平,只實現Callback方式的異步方法;
- L2到L5設備上,必須實現同時支持Callback方式和Promise方式的異步方法。
2.2 異步編程模型
2.2.1Promise 異步模型
Promise 異步模型是 OHOS 標準異步模型之一。
Promise對象: ES6原生提供了Promise對象,Promise是異步編程的一種解決方案,可以替代傳統的解決方案–回調函數和事件。promise對象是一個異步操作的結果,提供了一些API使得異步執行可以按照同步的流表示出來,避免了層層嵌套的回調函數,保證了回調是以異步的方式進行調用的。用戶在調用這些接口的時候,接口實現將異步執行任務,同時返回一個 Promise 對象,其代表異步操作的結果。在返回的結果的個數超過一個時,其以對象屬性的形式返回。
Promise特點 作為對象,Promise有兩個特點:(1)對象的狀態不受外界影響;(2)一旦狀態改變了就不會再變,也就是說任何時候Promise都只有一種狀態。
2.2.2Callback 異步模型
Callback 異步模型是 OHOS 標準異步模型之一。用戶在調用這些接口的時候,接口實現將異步執行任務。任務執行結果以參數的形式提供給用戶注冊的回調函數。這些參數的第一個是 Error 或 undefined 類型,分別表示執行出錯與正常。
2.2 實現步驟
2.2.1 模塊注冊
API集合按業務功能進行模塊劃分。開發者使用前須import對應的模塊。
命名:@ohos.模塊名
注意:
- 模塊名須唯一,由ACE團隊統一維護,子系統新增模塊時須向ACE團隊申請。
- 模塊名最好是單個名詞。實在不行,也可以由多個名詞組成,但必須遵循小駝峰命名規則。
- 一個模塊,一個聲明文件(*.d.ts)。聲明文件命名遵循@ohos.模塊名.d.ts,文件名全小寫,單詞間無分割。
N-API通過注冊函數進行模塊的注冊,其接受一個全局變量參數,全局變量結構體中定義了模塊名及模塊初始化函數。在模塊的初始化中,我們可以定義模塊需要暴露的方法及屬性。
示例:
- 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++類對應的JavaScript類,包括JS類名、JS構造函數 */
- 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),
- };
- /* 設置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 對應 4,以此類推
- * @sysCap 系統能力
- * @devices 支持設備
- * @import 導入模塊
- * @permission 權限列表
- */
- 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實現
JS API 調用流程如下圖所示:

接口定義
- /**入參**
- napi_env:表示一個上下文的變量;
- napi_callback_info:傳遞給回調函數的一個封裝的數據類型,可以用于獲取有關調用時的上下文信息,也可以用于設置回調函數的返回值;
- **返回值**
- 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 同步回調
同步方法調用之后,將阻塞住JS線程直至獲取到返回值。
命名:動詞+Sync或動詞+名詞+Sync
格式:
- 無參:方法名()
- 有參:方法名Sync(必填參數[, 可選參數])
返回值
- 有
聲明文件模板
- declare namespace 模塊名
- {
- /**
- * 方法描述
- * @note 特殊說明
- * @since (可選,方法支持版本與模塊不一致時需標明)
- * @sysCap 系統能力
- * @devices 支持設備 (可選,支持設備類型與模塊不一致時需標明)
- * @param 參數 參數說明(可選,沒有參數或參數用interface包含時不需要標明)
- * @return 返回值說明(可選,沒有返回值或返回值用interface包含時不需要標明)
- */
- // 無參
- function 方法名Sync(): 返回值類型;
- // 有參
- function 方法名Sync(必填參數: 參數類型, options?: 可選參數類型): 返回值類型;
- interface 可選參數類型 {
- 參數名: 參數類型;
- }
- }
- 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參數描述
- * @since 6
- * @sysCap ACE Enginge
- */
- default: string;
- }
- }
- export default storage;
實現
- static napi_value GetSync(napi_env env, napi_callback_info info)
- {
- size_t requireArgc = 1;
- size_t argc = 2; //參數個數
- napi_value argv[2] = { 0 }; //參數定義
- napi_value thisVar = nullptr; //JS對象的this參數
- void* data = nullptr; //回調數據指針
- /* 根據環境變量獲取參數 */
- 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) {
- /* 根據JS字符串獲取對應的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;
- /* 根據JS對象獲取與之綁定的原生對象實例 */
- napi_unwrap(env, thisVar, (void**)&objectInfo);
- auto itr = g_keyValueStorage.find(key);
- napi_value result = nullptr; // JS字符串對象
- if (itr != g_keyValueStorage.end()) {
- /* 根據UTF8編碼格式的 C/C++字符串 創建一個 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 異步回調
異步方法調用整個過程不會阻礙調用者的工作。
命名:動詞或動詞+名詞
格式:
- 無參:方法名([回調函數])
- 有參:方法名(必填參數[, 可選參數][, 回調函數])
返回值
- 若回調函數非空,則返回void
- 若回調函數為空,則返回Promise實例對象
聲明文件模板
- declare namespace 模塊名 {
- /**
- * 方法描述
- * @note 特殊說明
- * @since (可選,方法支持版本與模塊不一致時需標明)
- * @sysCap 系統能力
- * @devices 支持設備 (可選,支持設備類型與模塊不一致時需標明)
- * @param 參數 參數說明(可選,沒有參數或參數用interface包含時不需要標明)
- */
- // 無參
- function 方法名(callback: AsyncCallback<結果數據類型>): void;
- function 方法名(): Promise<結果數據類型>;
- // 有參
- function 方法名(必填參數: 參數類型, callback: AsyncCallback<結果數據類型>): void;
- function 方法名(必填參數: 參數類型, options: 可選參數類型, callback: AsyncCallback<結果數據類型>): void;
- function 方法名(必填參數: 參數類型, options?: 可選參數類型): Promise<結果數據類型>;
- interface 可選參數類型 {
- 參數名: 參數類型;
- }
- }
- 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;
實現
異步回調流程如下圖所示:

- static napi_value Get(napi_env env, napi_callback_info info)
- {
- size_t requireArgc = 1;
- size_t argc = 3; //參數個數
- napi_value argv[3] = { 0 }; //參數定義
- napi_value thisVar = nullptr; //JS對象的this參數
- void* data = nullptr; //回調數據指針
- /* 根據環境變量獲取參數 */
- napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
- NAPI_ASSERT(env, argc >= requireArgc, "requires 1 parameter");
- /* 異步接口上下文,用于接收JS接口傳進來的環境變量、參數、回調函數、接口返回值等*/
- 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)) {
- /* 根據JS字符串獲取對應的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) {
- /* 根據JS對象參數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方式異步調用,創建延遲對象、JS Promise對象,使二者進行關聯 */
- napi_create_promise(env, &asyncContext->deferred, &result);
- } else {
- /* Callback方式異步調用,不需要返回Promise對象,返回一個JS未定義值 */
- napi_get_undefined(env, &result);
- }
- /* 根據JS對象獲取與之綁定的原生對象實例 */
- napi_unwrap(env, thisVar, (void**)&asyncContext->objectInfo);
- napi_value resource = nullptr;
- napi_create_string_utf8(env, "JSStorageGet", NAPI_AUTO_LENGTH, &resource); //獲取JS異步資源名稱
- /* 創建異步工作 */
- napi_create_async_work(
- env, nullptr, resource,
- /* 執行異步邏輯的原生函數 */
- [](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; //失敗
- }
- },
- /* 異步函數執行完成或者取消后,需要執行的后處理函數 */
- [](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) {
- /* 異步函數執行成功后,執行成功后處理函數 */
- napi_resolve_deferred(env, asyncContext->deferred, result[1]);
- } else {
- /* 異步函數執行失敗后,執行失敗后處理函數 */
- 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);
- }
- /* 異步回調完成后進行資源釋放 */
- napi_delete_async_work(env, asyncContext->work);
- delete asyncContext;
- },
- /* 用戶數據上下文,此數據傳遞給異步執行函數與后處理函數 */
- (void*)asyncContext,
- /* 生成的異步工作*/
- &asyncContext->work);
- napi_queue_async_work(env, asyncContext->work); //異步工作入隊列,排隊執行
- return result;
- }
3. 應用代碼示例
JS應用引用NAPI接口時,須先引用接口定義的對應模塊,才能進行接口的調用。
- 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) );
- }
- }