三方庫移植之NAPI開發(fā)[4]異步調(diào)用:Callback&Promise

寫在開頭

  • 本文在 三方庫移植之NAPI開發(fā)[1]—Hello OpenHarmony NAPI 的基礎(chǔ)上修改hellonapi.cpp、index.ets辑莫,接著學(xué)習(xí)NAPI異步模型的Promise蜓氨、Callback方式筷频。
  • 本文共有三個示例甚亭,分別是Callback 異步接口示例白对、Promise 異步接口示例、規(guī)范異步接口示例简烘。在本文末尾的資源中提供了這三個示例的源代碼,讀者可以下載在開發(fā)板上運(yùn)行定枷。
  • 開發(fā)基于最新的OpenHarmony3.2Beta3版本及API9,標(biāo)準(zhǔn)系統(tǒng)開發(fā)板為潤和軟件DAYU200孤澎。

NAPI異步方式實(shí)現(xiàn)原理

  • 同步方式和異步方式
    同步方式,所有的代碼處理都在原生方法(主線程)中完成欠窒。
    異步方式覆旭,所有的代碼處理在多個線程中完成。

  • 實(shí)現(xiàn)NAPI異步方法的步驟
    1)立即返回一個臨時結(jié)果給js調(diào)用者
    2)另起線程完成異步業(yè)務(wù)邏輯的執(zhí)行
    3)通過callback或promise返回真正的結(jié)果

  • 異步工作項(xiàng)工作時序圖

  • 原生方法被調(diào)用時岖妄,原生方法完成數(shù)據(jù)接收型将、數(shù)據(jù)類型轉(zhuǎn)換存入上下文數(shù)據(jù)荐虐,之后創(chuàng)建異步工作項(xiàng)
  • 異步工作項(xiàng)會加入調(diào)度隊(duì)列,由異步工作線程池統(tǒng)一調(diào)度七兜,原生方法返回空值(Callback方式)或返回Promise對象(Promise方式)。
  • 異步方式依賴NAPI框架提供的napi_create_async_work()函數(shù)創(chuàng)建異步工作項(xiàng)
    napi_create_async_work()在foundation/arkui/napi/native_engine/native_node_api.cpp第71行
NAPI_EXTERN 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)

參數(shù)說明
[in] env: 傳入接口調(diào)用者的環(huán)境福扬,包含js引擎等腕铸,由框架提供,默認(rèn)情況下直接傳入即可铛碑。
[in] async_resource: 可選項(xiàng)狠裹,關(guān)聯(lián)async_hooks。
[in] async_resource_name: 異步資源標(biāo)識符汽烦,主要用于async_hooks API暴露斷言診斷信息涛菠。
[in] execute: 執(zhí)行業(yè)務(wù)邏輯計(jì)算函數(shù),由worker線程池調(diào)度執(zhí)行撇吞。在該函數(shù)中執(zhí)行IO俗冻、CPU密集型任務(wù),不阻塞主線程梢夯。
[in] complete: execute參數(shù)指定的函數(shù)執(zhí)行完成或取消后言疗,觸發(fā)執(zhí)行該函數(shù)。此函數(shù)在EventLoop線程中執(zhí)行颂砸。
[in] data: 用戶提供的上下文數(shù)據(jù)噪奄,用于傳遞數(shù)據(jù)死姚。
[out] result: napi_async_work*指針,用于返回當(dāng)前此處函數(shù)調(diào)用創(chuàng)建的異步工作項(xiàng)勤篮。 返回值:返回napi_ok表示轉(zhuǎn)換成功都毒,其他值失敗。

napi_create_async_work里有兩個回調(diào):

  • execute
    • execute函數(shù)用于執(zhí)行工作項(xiàng)的業(yè)務(wù)邏輯碰缔,異步工作項(xiàng)被調(diào)度后账劲,該函數(shù)從上下文數(shù)據(jù)中獲取輸入數(shù)據(jù),在worker線程中完成業(yè)務(wù)邏輯計(jì)算(不阻塞主線程)并將結(jié)果寫入上下文數(shù)據(jù)金抡。
    • 因?yàn)閑xecute函數(shù)不在JS線程中瀑焦,所以不允許execute函數(shù)調(diào)用napi的接口。業(yè)務(wù)邏輯的返回值可以返回到complete回調(diào)中處理梗肝。
  • complete
    • 業(yè)務(wù)邏輯處理execute函數(shù)執(zhí)行完成或被取消后榛瓮,觸發(fā)EventLoop執(zhí)行complete函數(shù),complete函數(shù)從上下文數(shù)據(jù)中獲取結(jié)果巫击,轉(zhuǎn)換為JS類型禀晓,調(diào)用JS回調(diào)函數(shù)通過Promise resolve()返回結(jié)果。
    • 可以調(diào)用napi的接口坝锰,將execute中的返回值封裝成JS對象返回粹懒。此回調(diào)在JS線程中執(zhí)行。
  • 管理簡單的異步操作的方法還有這些
    • napi_delete_async_work(napi_env env, napi_async_work work)
      刪除異步工作線程
    • napi_queue_async_work(napi_env env, napi_async_work work)
      將剛創(chuàng)建的異步工作項(xiàng)加到隊(duì)列(排隊(duì))顷级,由底層去調(diào)度執(zhí)行
    • napi_cancel_async_work(napi_env env, napi_async_work work)
      取消異步工作項(xiàng)

NAPI支持異步模型

  • OpenHarmony標(biāo)準(zhǔn)系統(tǒng)異步接口實(shí)現(xiàn)支持Promise方式和Callback方式凫乖。NAPI支持異步模型,提供了Promise愕把、Callback方式拣凹。
  • 標(biāo)準(zhǔn)系統(tǒng)異步接口實(shí)現(xiàn)規(guī)范要求,若引擎開啟Promise特性支持恨豁,則異步方法必須同時支持Callback方式和Promise方式嚣镜。
    • 由應(yīng)用開發(fā)者決定使用哪種方式,通過是否傳遞Callback函數(shù)區(qū)分異步方法是Callback方式還是Promise方式
    • 不傳遞Callback即為Promise方式(方法執(zhí)行結(jié)果為Promise實(shí)例對象)橘蜜,否則為Callback方式
  • Promise菊匿、Callback 異步模型都是 OHOS 標(biāo)準(zhǔn)異步模型。
  • Callback異步模型
    • 用戶在調(diào)用接口的時候计福,接口實(shí)現(xiàn)將異步執(zhí)行任務(wù)
    • 任務(wù)執(zhí)行結(jié)果以參數(shù)的形式提供給用戶注冊的回調(diào)函數(shù),這些參數(shù)的第一個是 Error 或 undefined 類型跌捆,分別表示執(zhí)行出錯與正常。
  • Promise異步模型
    • 對象的狀態(tài)不受外界影響象颖;
    • 一旦狀態(tài)改變了就不會再變佩厚,也就是說任何時候Promise都只有一種狀態(tài)。
  • ES6原生提供了Promise對象说订,Promise是異步編程的一種解決方案抄瓦,可以替代傳統(tǒng)的解決方案回調(diào)函數(shù)和事件潮瓶;
    • promise對象是一個異步操作的結(jié)果,提供了一些API使得異步執(zhí)行可以按照同步的流表示出來钙姊,避免了層層嵌套的回調(diào)函數(shù)毯辅,保證了回調(diào)是以異步的方式進(jìn)行調(diào)用的;
    • 用戶在調(diào)用這些接口的時候煞额,接口實(shí)現(xiàn)將異步執(zhí)行任務(wù)思恐,同時返回一個 Promise 對象,其代表異步操作的結(jié)果膊毁;
    • 在返回的結(jié)果的個數(shù)超過一個時胀莹,其以對象屬性的形式返回。
  • ES6:全稱ECMAScript 6.0婚温。ECMAScript 是JavaScript語言的國際標(biāo)準(zhǔn)嗜逻,JavaScript是ECMAScript的實(shí)現(xiàn)。

Callback 異步接口

Callback 異步接口示例代碼

hellonapi.cpp文件

#include <string.h>
#include <stdio.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"

// 用戶提供的上下文數(shù)據(jù)缭召,在原生方法(初始化數(shù)據(jù))、executeCB逆日、completeCB之間傳遞數(shù)據(jù)
struct AddonData {
  napi_async_work asyncWork = nullptr;
  napi_deferred deferred = nullptr;
  napi_ref callback = nullptr;
  double args[2] = {0};
  double result = 0;
};

// 業(yè)務(wù)邏輯處理函數(shù)嵌巷,由worker線程池調(diào)度執(zhí)行室抽。
static void addExecuteCB(napi_env env, void *data) {
  AddonData *addonData = (AddonData *)data;

  // 執(zhí)行復(fù)雜計(jì)算坪圾,不阻塞主線程。此處用一個加法簡單示意兽泄。
  addonData->result = addonData->args[0] + addonData->args[1];
}

// 業(yè)務(wù)邏輯處理完成回調(diào)函數(shù)漓概,在業(yè)務(wù)邏輯處理函數(shù)執(zhí)行完成或取消后觸發(fā),由EventLoop線程中執(zhí)行病梢。
static void addCallbackCompleteCB(napi_env env, napi_status status, void *data) {
  AddonData *addonData = (AddonData *)data;
  napi_value callback = nullptr;
  napi_get_reference_value(env, addonData->callback, &callback);
  napi_value undefined = nullptr;
  napi_get_undefined(env, &undefined);
  napi_value result = nullptr;
  napi_create_double(env, addonData->result, &result);
  napi_value callbackResult = nullptr;

  // 執(zhí)行回調(diào)函數(shù)
  napi_call_function(env, undefined, callback, 1, &result, &callbackResult);

  // 刪除napi_ref對象
  if (addonData->callback != nullptr) {
    napi_delete_reference(env, addonData->callback);
  }

  // 刪除異步工作項(xiàng)
  napi_delete_async_work(env, addonData->asyncWork);
  delete addonData;
}

static napi_value addCallback(napi_env env, napi_callback_info info) {
  // 獲取3個參數(shù)胃珍,值的類型是js類型(napi_value)
  size_t argc = 3;
  napi_value args[3];
  napi_value thisArg = nullptr;
  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));

  // 獲取并判斷js參數(shù)類型
  napi_valuetype valuetype0;
  NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
  napi_valuetype valuetype1;
  NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
  if (valuetype0 != napi_number || valuetype1 != napi_number) {
    napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
    return NULL;
  }

  napi_valuetype valuetype2;
  NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2));
  if (valuetype2 != napi_function) {
    napi_throw_type_error(env, nullptr, "Callback function expected.");
    return NULL;
  }

  // 異步工作項(xiàng)上下文用戶數(shù)據(jù),傳遞到異步工作項(xiàng)的execute蜓陌、complete中傳遞數(shù)據(jù)
  auto addonData = new AddonData{
      .asyncWork = nullptr,
  };

  // 將接收到的參數(shù)傳入用戶自定義上下文數(shù)據(jù)
  NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
  NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));
  NAPI_CALL(env, napi_create_reference(env, args[2], 1, &addonData->callback));

  // 創(chuàng)建async work觅彰,創(chuàng)建成功后通過最后一個參數(shù)接收async work的handle
  napi_value resourceName = nullptr;
  napi_create_string_utf8(env, "addCallback", NAPI_AUTO_LENGTH, &resourceName);
  napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addCallbackCompleteCB, (void *)addonData,
                         &addonData->asyncWork);

  // 將剛創(chuàng)建的async work加到隊(duì)列,由底層去調(diào)度執(zhí)行
  napi_queue_async_work(env, addonData->asyncWork);

  // 原生方法返回空對象
  napi_value result = 0;
  NAPI_CALL(env, napi_get_null(env, &result));
  return result;
}

// napi_addon_register_func
static napi_value registerFunc(napi_env env, napi_value exports) {
  static napi_property_descriptor desc[] = {
      DECLARE_NAPI_FUNCTION("addCallback", addCallback),
  };
  NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
  return exports;
}

// 定義napi_module钮热,指定當(dāng)前NAPI模塊對應(yīng)的模塊名
//以及模塊注冊對外接口的處理函數(shù)填抬,具體擴(kuò)展的接口在該函數(shù)中聲明
// nm_modname: 模塊名稱,對應(yīng)eTS代碼為import nm_modname from '@ohos.ohos_shared_library_name'
//示例對應(yīng)eTS代碼為:import hellonapi from '@ohos.hellonapi'
static napi_module hellonapiModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = registerFunc, // 模塊對外接口注冊函數(shù)
    .nm_modname = "hellonapi",  // 自定義模塊名
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

// 模塊定義好后隧期,調(diào)用NAPI提供的模塊注冊函數(shù)napi_module_register(napi_module* mod)函數(shù)注冊到系統(tǒng)中飒责。
// register module擅憔,設(shè)備啟動時自動調(diào)用此constructor函數(shù),把模塊定義的模塊注冊到系統(tǒng)中
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()
{
    napi_module_register(&hellonapiModule);
}

index.ets

import prompt from '@system.prompt';
import hellonapi from '@ohos.hellonapi'

@Entry
@Component
struct TestAdd {
  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
      Button("hellonapi.addCallback(x, y, callback)").margin(10).fontSize(20).onClick(() => {
        let num1 = 123, num2 = 456
        hellonapi.addCallback(num1, num2, (result) => {
          prompt.showToast({ message: `hellonapi.addCallback(${num1}, ${num2}) = ${result}` })
        })
      })
    }
    .width('100%')
    .height('100%')
  }
}

@ohos.hellonapi.d.ts

declare namespace hellonapi {

    function addCallback(num1: number, num2: number, callback:(result: number) => void): void;
    /**
     * 
     *
     * @since 9
     * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
     */

}
export default hellonapi;

主線程:獲取JS傳入?yún)?shù)

獲取JS傳入?yún)?shù)在異步工作項(xiàng)工作時序圖中位置芥喇,在圖中用紅框標(biāo)記如下

  // 獲取并判斷js參數(shù)類型
  napi_valuetype valuetype0;
  NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
// 使用napi_typeof接口進(jìn)行參數(shù)類型的判斷
  napi_valuetype valuetype1;
  NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));

// 如果valuetype2調(diào)用的不是數(shù)據(jù)類型武通,則拋出異趁樱“Wrong arguments. 2 numbers expected.”
  if (valuetype0 != napi_number || valuetype1 != napi_number) {
    napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
    return NULL;
  }

  napi_valuetype valuetype2;
  NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2));

// 如果valuetype2調(diào)用的不是function類型(callback)力九,則拋出異忱ㄉ祝“Callback function expected”
  if (valuetype2 != napi_function) {
    napi_throw_type_error(env, nullptr, "Callback function expected.");
    return NULL;
  }
  • 使用napi_typeof接口進(jìn)行參數(shù)類型的判斷
  • NAPI_CALL()是用來調(diào)用NAPI中的API的。

主線程:初始化上下文數(shù)據(jù)

初始化上下文數(shù)據(jù)在異步工作項(xiàng)工作時序圖中位置,在圖中用紅框標(biāo)記如下

  • 異步方法需要在不同線程中傳遞各種業(yè)務(wù)數(shù)據(jù)上下文數(shù)據(jù)),就需要定義一個結(jié)構(gòu)體保存這些被傳遞的信息孵奶。用于在主線程方法载绿、Work線程、EventLoop線程之間傳遞數(shù)據(jù)。

struct 結(jié)構(gòu)體名(也就是可選標(biāo)記名){ 成員變量;}灌诅;//使用分號即舌;表示定義結(jié)束盯仪。

  • 本示例定義的上下文數(shù)據(jù)包含:異步工作項(xiàng)對象回調(diào)函數(shù)滞伟、2個參數(shù)(加數(shù)、被加數(shù))、業(yè)務(wù)邏輯處理結(jié)果等4個屬性。
// 定義異步工作項(xiàng)上下文數(shù)據(jù)
// 用戶提供的上下文數(shù)據(jù)狈惫,用于在主線程方法菱肖、Work線程、EventLoop線程之間傳遞數(shù)據(jù)。
struct AddonData {
  napi_async_work asyncWork = nullptr;   //異步工作對象asyncWork
  napi_ref callback = nullptr;           //回調(diào)函數(shù)callback
  double args[2] = {0};                  //2個輸入?yún)?shù)
  double result = 0;                     //業(yè)務(wù)邏輯處理結(jié)果result(返回值)
};
  • OpenHarmony的NAPI框架將ECMAScript標(biāo)準(zhǔn)中定義的Boolean、Null棒坏、Undefined徽诲、Number、BigInt、String、Symbol和Object八種數(shù)據(jù)類型Function類型袄友,都已統(tǒng)一封裝為napi_value類型,故可如獲取數(shù)據(jù)類型的參數(shù)一樣獲取Function類型的參數(shù)。

Function是JavaScript提供的一種引用類型,通過Function類型創(chuàng)建Function對象。
在JavaScript中,函數(shù)也是以對象的形式存在的篮赢,每個函數(shù)都是一個Function對象寥茫。

  • 定義好結(jié)構(gòu)體后,接著我們將接收到的3個參數(shù)(加數(shù)被加數(shù)回調(diào)函數(shù))轉(zhuǎn)換存入上下文數(shù)據(jù)完成初始化上下文數(shù)據(jù)
    • number類型的(加數(shù)搞乏、被加數(shù))轉(zhuǎn)換為double直接存入柏卤。
    • Function類型的參數(shù)(回調(diào)函數(shù))怎么處理?不能直接存入napi_value類型。
      • 因?yàn)闋可娴絅API對象生命周期管理問題。napi_value類型引用對象的生命周期在原生方法退出后結(jié)束唬血,后面在work線程無法獲取其值望蜡。
      • NAPI提供了一種生命期限長于原生方法的對象引用類型—— napi_ref,所以調(diào)用napi_create_reference()函數(shù)將接收到的napi_value類型的回調(diào)函數(shù)參數(shù)callback轉(zhuǎn)換為napi_ref類型拷恨。napi_create_reference()函數(shù)定義如下:
NAPI_EXTERN napi_status napi_create_reference(napi_env env,
                                              napi_value value,
                                              uint32_t initial_refcount,
                                              napi_ref* result);

參數(shù)說明:
[in] env: 傳入接口調(diào)用者的環(huán)境脖律,包含js引擎等,由框架提供酸茴,默認(rèn)情況下直接傳入即可息拜。
[in] value: 需要創(chuàng)建一個引用的napi_value對象
[in] initial_refcount: 初始化引用次數(shù)崎页。
[out] result: 指針挑辆,指向新創(chuàng)建的napi_ref對象间唉。 返回值:返回napi_ok表示轉(zhuǎn)換成功率触,其他值失敗。

  • napi_ref引用對象在原生方法退出后不自動回收,由用戶管理napi_ref類型對象的生命周期伞梯。
    • 用戶管理napi_ref類型對象的生命周期的方法有
      • napi_create_reference() : 將napi_value包裝成napi_ref引用對象
      • napi_get_reference_value() : 從napi_ref引用對象中取得napi_value
      • napi_delete_reference() :刪除napi_ref引用對象
    • 通過napi_create_reference()方法將napi_value創(chuàng)建一個napi_ref皱炉,這個napi_ref是可以跨作用域傳遞的悯搔,然后在需要用到的地方用napi_get_reference_value()方法將napi_ref還原為napi_value靡馁,用完后再用napi_delete_reference()方法刪除引用對象以便釋放相關(guān)內(nèi)存資源。
static napi_value addAsyncCallback(napi_env env, napi_callback_info info) {
// NAPI定義API方法時的接收參數(shù)為(napi_env, napi_callback_info)
// 其中napi_callback_info為上下文的信息。

  size_t argc = 3;   // 有3個參數(shù)(`加數(shù)`、`被加數(shù)`、`回調(diào)函數(shù)`)到上下文中
  napi_value args[3];
  napi_value thisArg = nullptr;
  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));
// NAPI提供了napi_get_cb_info()方法可從napi_callback_info中獲取參數(shù)列表、this及其他數(shù)據(jù)敷硅。

  ...
  // 異步工作項(xiàng)上下文用戶數(shù)據(jù)功咒,傳遞到異步工作項(xiàng)的execute、complete中傳遞數(shù)據(jù)
  // 創(chuàng)建結(jié)構(gòu)體addonData用于保存各種需要在異步線程中傳遞的數(shù)據(jù)信息
  auto addonData = new AddonData{
      .asyncWork = nullptr,
  };

  // 將接收到的3個參數(shù)(`加數(shù)`绞蹦、`被加數(shù)`力奋、`回調(diào)函數(shù)`)傳入用戶自定義上下文數(shù)據(jù)
  NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));

  // NAPI_CALL()是用來調(diào)用NAPI中的API的
  // NAPI提供napi_get_value_double方法將JS類型double值轉(zhuǎn)換為C++類型的double值
  NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));  

  NAPI_CALL(env, napi_create_reference(env, args[2], 1, &addonData->callback));
  //調(diào)用napi_create_reference()函數(shù)將接收到的napi_value類型的回調(diào)函數(shù)callback轉(zhuǎn)換為napi_ref類型,將napi_value包裝成napi_ref引用對象幽七。并保存到asyncContext上下文數(shù)據(jù)中景殷,以便后續(xù)在C++異步線程中能夠回調(diào)該js fuction類型。
  //參數(shù)解釋如下
     // env: 傳入接口調(diào)用者的環(huán)境,包含js引擎等猿挚,由框架提供咐旧,默認(rèn)情況下直接傳入即可
     // args[2]: 引用的napi_value對象 (加數(shù)和被加數(shù))
     // 1:初始化引用1次
     // &addonData->callback: 指向新創(chuàng)建的napi_ref 對象(callback) 
  ...
}
  • NAPI_CALL()是用來調(diào)用NAPI中的API的。

主線程:創(chuàng)建異步工作項(xiàng)

創(chuàng)建異步工作項(xiàng)在異步工作項(xiàng)工作時序圖中位置绩蜻,在圖中用紅框標(biāo)記如下

  • 第一步:在創(chuàng)建異步工作項(xiàng)前铣墨,分別聲明addExecuteCB、addAsyncCompleteCB這2個函數(shù)办绝,分別用作于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)函數(shù)的execute伊约、complete參數(shù)。
  • 第二步:利用NAPI框架提供的napi_create_async_work()函數(shù)創(chuàng)建異步工作項(xiàng)八秃,將addExecuteCB、addAsyncCompleteCB這2個函數(shù)存入上下文數(shù)據(jù)的asyncWork屬性
// 業(yè)務(wù)邏輯處理函數(shù)肉盹,由異步work線程池統(tǒng)一調(diào)度
static void addExecuteCB(napi_env env, void *data) {
}

// 業(yè)務(wù)邏輯處理完成回調(diào)函數(shù)昔驱,在業(yè)務(wù)邏輯處理函數(shù)執(zhí)行完成或取消后觸發(fā)。
static void addAsyncCompleteCB(napi_env env, napi_status status, void *data) {
}

static napi_value addAsyncCallback(napi_env env, napi_callback_info info) {
  ...
    // 創(chuàng)建async work上忍,創(chuàng)建成功后通過最后一個參數(shù)接收async work的handle
    napi_value resourceName = nullptr;

// 根據(jù)UTF8編碼格式的 C/C++字符串 創(chuàng)建一個 JS字符串對象.
// 傳入的參數(shù)是Javascript值類型骤肛,被NAPI框架封裝成統(tǒng)一的唯一類型——napi_value類型,為了能夠進(jìn)行計(jì)算窍蓝,我們需要獲取其對應(yīng)在C/C++中的類型的值腋颠。將C/C++ utf8類型的值轉(zhuǎn)為node_value類型,返回給JS代碼
    napi_create_string_utf8(env, "addCallback", NAPI_AUTO_LENGTH, &resourceName); //參數(shù)說明如下
    //env: 傳入接口調(diào)用者的環(huán)境吓笙,包含js引擎等淑玫,由框架提供,默認(rèn)情況下直接傳入即可
    //addCallback:定義的上下文信息中的addCallback對象
    //NAPI_AUTO_LENGTH:字符長度
    //resourceName:創(chuàng)建的napi_value對象

   // 異步方式依賴NAPI框架提供的napi_create_async_work()函數(shù)創(chuàng)建異步工作項(xiàng)
   napi_create_async_work(env, nullptr, resourceName,   addExecuteCB  ,  addCallbackCompleteCB  ,   (void *)addonData,&addonData->asyncWork);
   //參數(shù)說明如下
   //env: 傳入接口調(diào)用者的環(huán)境面睛,包含js引擎等絮蒿,由框架提供,默認(rèn)情況下直接傳入即可叁鉴。
   //第二個參數(shù)是nullptr
   //resourceName: 定義的上下文信息中的addCallback對象(異步資源標(biāo)識符)土涝,主要用于async_hooks API暴露斷言診斷信息。
   //addExecuteCB:執(zhí)行業(yè)務(wù)邏輯計(jì)算函數(shù)幌墓,由worker線程池調(diào)度執(zhí)行但壮。在該函數(shù)中執(zhí)行IO、CPU密集型任務(wù)常侣,不阻塞主線程蜡饵。
   //addCallbackCompleteCB: execute參數(shù)指定的函數(shù)執(zhí)行完成或取消后,觸發(fā)執(zhí)行該函數(shù)胳施。此函數(shù)在EventLoop線程中執(zhí)行验残。
   //(void *)addonData: 用戶提供的上下文數(shù)據(jù),用于傳遞數(shù)據(jù)。
   //&addonData->asyncWork: 用于返回當(dāng)前此處函數(shù)調(diào)用創(chuàng)建的異步工作項(xiàng)您没。 返回值:返回napi_ok表示轉(zhuǎn)換成功鸟召,其他值失敗。
  ...
}

主線程:異步工作項(xiàng)加入隊(duì)列氨鹏,等待調(diào)度

異步工作項(xiàng)加入隊(duì)列欧募,等待調(diào)度在異步工作項(xiàng)工作時序圖中位置,在圖中用紅框標(biāo)記如下

static napi_value addAsyncCallback(napi_env env, napi_callback_info info) {
  ...
  // 將剛創(chuàng)建的異步工作項(xiàng)(async work)加到隊(duì)列仆抵,由work thread調(diào)度執(zhí)行
  napi_queue_async_work(env, addonData->asyncWork);
  // 其中asyncWork是上下文數(shù)據(jù)中創(chuàng)建的異步工作對象跟继,用于管理異步工作線程。
  ...
}

主線程:原生方法返回臨時返回值

  • 調(diào)用napi_queue_async_work()將異步工作項(xiàng)加入調(diào)度隊(duì)列镣丑,由異步work線程池統(tǒng)一調(diào)度舔糖,原生方法返回空值退出。

  • 用戶在調(diào)用接口的時候莺匠,接口實(shí)現(xiàn)將異步執(zhí)行任務(wù),任務(wù)執(zhí)行結(jié)果以參數(shù)的形式提供給用戶注冊的回調(diào)函數(shù)(callback),這些參數(shù)的第一個是 Error 或 undefined 類型金吗,分別表示執(zhí)行出錯與正常。

static napi_value addAsyncCallback(napi_env env, napi_callback_info info) {
  ...
  // 為異步方法創(chuàng)建臨時返回值,在此處原生方法臨時返回值是一個空對象
  napi_value result = 0;
  // callback接口返回參數(shù)為void趣竣,用napi_get_null()構(gòu)造一個空對象的返回值即可摇庙。
  NAPI_CALL(env, napi_get_null(env, &result));
  return result;
}

work線程:執(zhí)行業(yè)務(wù)邏輯、把計(jì)算結(jié)果寫入上下文數(shù)據(jù)

執(zhí)行業(yè)務(wù)邏輯遥缕、把計(jì)算結(jié)果寫入上下文數(shù)據(jù)在異步工作項(xiàng)工作時序圖中位置卫袒,在圖中用紅框標(biāo)記如下

創(chuàng)建異步工作項(xiàng)前,聲明了addExecuteCB這個函數(shù)单匣,用作于napi_create_async_work()函數(shù)的execute參數(shù)夕凝。

  • execute函數(shù)在異步工作項(xiàng)被調(diào)度后在work線程中執(zhí)行

    • 不阻塞主線程(不阻塞UI界面)
    • 可執(zhí)行IO、CPU密集型等任務(wù)户秤。
  • 執(zhí)行業(yè)務(wù)邏輯:業(yè)務(wù)邏輯計(jì)算是一個簡單的加法迹冤,并把計(jì)算結(jié)果存入上下文數(shù)據(jù)的result屬性

  • 把計(jì)算結(jié)果寫入上下文數(shù)據(jù):把execute函數(shù)的結(jié)構(gòu)體指向上下文數(shù)據(jù)中結(jié)構(gòu)體。

// 業(yè)務(wù)邏輯處理函數(shù)虎忌,由worker線程池調(diào)度執(zhí)行泡徙。
static void addExecuteCB(napi_env env, void *data) {

 // 把計(jì)算結(jié)果寫入上下文數(shù)據(jù),把a(bǔ)ddonData指向AddonData
  AddonData *addonData = (AddonData *)data;

  // 執(zhí)行業(yè)務(wù)邏輯膜蠢,
  // 不阻塞主線程堪藐。此處是一個加法
  addonData->result = addonData->args[0] + addonData->args[1];
}

EventLoop線程:把上下文中的結(jié)果轉(zhuǎn)為JS類型、調(diào)用JS回調(diào)函數(shù)

把上下文中的結(jié)果轉(zhuǎn)為JS類型挑围、調(diào)用JS回調(diào)函數(shù)在異步工作項(xiàng)工作時序圖中位置礁竞,在圖中用紅框標(biāo)記如下

  • 創(chuàng)建異步工作項(xiàng)前,聲明addAsyncCompleteCB這個函數(shù)杉辙,用作于napi_create_async_work()函數(shù)的complete參數(shù)模捂。
    • 第一步:addAsyncCompleteCB從接收到的上下文數(shù)據(jù)中獲取結(jié)果,調(diào)用napi_call_function()方法執(zhí)行JS回調(diào)函數(shù)返回?cái)?shù)據(jù)給JS。
    • 第二步: 釋放(刪除)過程中創(chuàng)建的napi_ref引用對象狂男、異步工作項(xiàng)等對象综看。
// 業(yè)務(wù)邏輯處理完成回調(diào)函數(shù),在業(yè)務(wù)邏輯處理函數(shù)執(zhí)行完成或取消后觸發(fā)岖食,由EventLoop線程中執(zhí)行红碑。
static void addCallbackCompleteCB(napi_env env, napi_status status, void *data) {
//所有的接口調(diào)用返回一個napi_status類型的狀態(tài)碼,用來表明接口調(diào)用成功或者失敗

  AddonData *addonData = (AddonData *)data;

  napi_value callback = nullptr;
  //從napi_ref引用對象中取得napi_value
  napi_get_reference_value(env, addonData->callback, &callback);
  napi_value undefined = nullptr;
  napi_get_undefined(env, &undefined);
  napi_value result = nullptr;
  napi_create_double(env, addonData->result, &result);
  napi_value callbackResult = nullptr;
  // 執(zhí)行回調(diào)函數(shù)
  napi_call_function(env, undefined, callback, 1, &result, &callbackResult);

  // 刪除napi_ref對象
  if (addonData->callback != nullptr) {
    napi_delete_reference(env, addonData->callback);
  }

  // 刪除異步工作項(xiàng)
  napi_delete_async_work(env, addonData->asyncWork);
  delete addonData;
}
  • NAPI框架提供了napi_call_function()函數(shù)供擴(kuò)展Natvie代碼(C/C++代碼)調(diào)用JS函數(shù)泡垃,用于執(zhí)行回調(diào)函數(shù)等場景析珊。函數(shù)定義如下:
// Methods to work with Functions
NAPI_EXTERN napi_status napi_call_function(napi_env env,
                                           napi_value recv,
                                           napi_value func,
                                           size_t argc,
                                           const napi_value* argv,
                                           napi_value* result)

參數(shù)說明:
[in] env: 傳入接口調(diào)用者的環(huán)境,包含js引擎等蔑穴,由框架提供忠寻,默認(rèn)情況下直接傳入即可。
[in] recv: 傳給被調(diào)用的this對象存和。
[in] func: 被調(diào)用的函數(shù).
[in] argc: 函數(shù)參數(shù)個數(shù)(對應(yīng)函數(shù)數(shù)組的長度)奕剃。
[in] argv: 函數(shù)參數(shù)數(shù)組.
[out] result: func函數(shù)執(zhí)行的返回值。 返回值:返回napi_ok表示轉(zhuǎn)換成功哑姚,其他值失敗祭饭。

  • 因?qū)ο笊芷诠芾韱栴}芜茵,上下文數(shù)據(jù)的callback屬性的類型為napi_ref叙量,需要調(diào)用napi_get_reference_value()函數(shù)獲取其指向的napi_value對象值才調(diào)用napi_call_function()函數(shù)。 napi_get_reference_value函數(shù)定義:
// Attempts to get a referenced value. If the reference is weak,
// the value might no longer be available, in that case the call
// is still successful but the result is nullptr.
NAPI_EXTERN napi_status napi_get_reference_value(napi_env env, 
                                                 napi_ref ref,  
                                                 napi_value* result)

參數(shù)說明:
[in] env: 傳入接口調(diào)用者的環(huán)境九串,包含js引擎等绞佩,由框架提供,默認(rèn)情況下直接傳入即可猪钮。
[in] ref: napi_ref對象
[out] result: napi_ref引用的napi_value對象品山。 返回值:返回napi_ok表示轉(zhuǎn)換成功,其他值失敗烤低。

  • 執(zhí)行回調(diào)函數(shù)是為了在異步操作之后調(diào)用JS函數(shù)

  • napi_delete_reference()用于刪除上下文數(shù)據(jù)中定義的napi_ref對象callback肘交。napi_ref引用對象在原生方法退出后不自動回收,由用戶管理napi_ref類型對象的生命周期扑馁。

  • napi_delete_async_work()用于刪除異步工作線程,在異步調(diào)用的結(jié)尾釋放async_work和相關(guān)業(yè)務(wù)數(shù)據(jù)的內(nèi)存

Callback異步接口總結(jié)

以下圖片為個人總結(jié)涯呻,可以在文末下載清晰的圖片,下載之后推薦到[diagrams]腻要。

Promise異步接口

Promise異步接口示例代碼

hellonapi.cpp

#include <string.h>
#include<stdio.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"

// 用戶提供的上下文數(shù)據(jù)复罐,在原生方法(初始化數(shù)據(jù))、executeCB雄家、completeCB之間傳遞數(shù)據(jù)
struct AddonData {
  napi_async_work asyncWork = nullptr;
  napi_deferred deferred = nullptr;

  napi_ref callback = nullptr;

  double args[2] = {0};

  double result = 0;

};

// 業(yè)務(wù)邏輯處理函數(shù)效诅,由worker線程池調(diào)度執(zhí)行。
static void addExecuteCB(napi_env env, void *data) {

  AddonData *addonData = (AddonData *)data;

  // 執(zhí)行復(fù)雜計(jì)算,不阻塞主線程乱投。此處用一個加法簡單示意咽笼。
  addonData->result = addonData->args[0] + addonData->args[1];

  // addonData->result = addonData->args[0] + addonData[1];
}

static void addPromiseCompleteCB(napi_env env, napi_status status, void *data) {
  AddonData *addonData = (AddonData *)data;
  napi_value result = nullptr;
  napi_create_double(env, addonData->result, &result);
  napi_resolve_deferred(env, addonData->deferred, result);

     // 刪除napi_ref對象
     if (addonData->callback != nullptr) {
     napi_delete_reference(env, addonData->callback);
     }

  // 刪除異步工作項(xiàng)
  napi_delete_async_work(env, addonData->asyncWork);
  delete addonData;
  addonData = nullptr;
}

static napi_value addPromise(napi_env env, napi_callback_info info) {
  // 獲取2個參數(shù),值的類型是js類型(napi_value)
  size_t argc = 2;
  napi_value args[2];
  napi_value thisArg = nullptr;
  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));

  // 獲取并判斷js參數(shù)類型
  napi_valuetype valuetype0;
  NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
  napi_valuetype valuetype1;
  NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
  if (valuetype0 != napi_number || valuetype1 != napi_number) {
    napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
    return NULL;
  }

  // 創(chuàng)建promise
  napi_value promise = nullptr;
  napi_deferred deferred = nullptr;
  NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));

  // 異步工作項(xiàng)上下文用戶數(shù)據(jù)篡腌,傳遞到異步工作項(xiàng)的execute褐荷、complete之間傳遞數(shù)據(jù)
  auto addonData = new AddonData{
      .asyncWork = nullptr,
      .deferred = deferred,
  };

  // 將接收到的參數(shù)傳入
  NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
  NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));

  // 創(chuàng)建async work,創(chuàng)建成功后通過最后一個參數(shù)(addonData->asyncWork)返回async work的handle
  napi_value resourceName = nullptr;
  napi_create_string_utf8(env, "addAsyncCallback", NAPI_AUTO_LENGTH, &resourceName);
  napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addPromiseCompleteCB, (void *)addonData,
                         &addonData->asyncWork);

  // 將剛創(chuàng)建的async work加到隊(duì)列嘹悼,由底層去調(diào)度執(zhí)行
  napi_queue_async_work(env, addonData->asyncWork);

  // 原生方法返回promise
  return promise;

}

// napi_addon_register_func
//2.指定模塊注冊對外接口的處理函數(shù)叛甫,具體擴(kuò)展的接口在該函數(shù)中聲明
static napi_value registerFunc(napi_env env, napi_value exports)
{
    static napi_property_descriptor desc[] = {
        { "addPromise", nullptr, addPromise, nullptr, nullptr, nullptr, napi_default, nullptr }
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;   
}

// 1.先定義napi_module,指定當(dāng)前NAPI模塊對應(yīng)的模塊名
//以及模塊注冊對外接口的處理函數(shù)杨伙,具體擴(kuò)展的接口在該函數(shù)中聲明
// nm_modname: 模塊名稱其监,對應(yīng)eTS代碼為import nm_modname from '@ohos.ohos_shared_library_name'
//示例對應(yīng)eTS代碼為:import hellonapi from '@ohos.hellonapi'
static napi_module hellonapiModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = registerFunc, // 模塊對外接口注冊函數(shù)
    .nm_modname = "hellonapi",  // 自定義模塊名
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

//3.模塊定義好后,調(diào)用NAPI提供的模塊注冊函數(shù)napi_module_register(napi_module* mod)函數(shù)注冊到系統(tǒng)中限匣。
// register module抖苦,設(shè)備啟動時自動調(diào)用此constructor函數(shù),把模塊定義的模塊注冊到系統(tǒng)中
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()
{
    napi_module_register(&hellonapiModule);
}

index.ets

import prompt from '@system.prompt';
import hellonapi from '@ohos.hellonapi'

@Entry
@Component
struct TestAdd {
  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
      Button("hellonapi.addPromise(x, y).then(...)").margin(1).fontSize(20).onClick(() => {
        let num1 = 123, num2 = 456
        hellonapi.addPromise(num1, num2).then((result) => {
          prompt.showToast({ message: `hellonapi.addPromise(${num1}, ${num2}) = ${result}` })
        })
      })
    }
    .width('100%')
    .height('100%')
  }
}

@ohos.hellonapi.d.ts

declare namespace hellonapi {

    function addPromise(num1: number, num2: number): Promise<number>;
    /**
     * 
     *
     * @since 9
     * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
     */

}
export default hellonapi;

創(chuàng)建Promise

  • Promise整體處理流程和Callback方式一樣,在此小節(jié)只討論P(yáng)romise不同于Callback的部分

  • 首先創(chuàng)建Promise米死,NAPI框架中提供了napi_create_promise()函數(shù)用于創(chuàng)建Promise锌历,調(diào)用該函數(shù)輸出2個對象——deferred、promise峦筒。

    • promise用于原生方法返回究西,deferred傳入異步工作項(xiàng)的上下文數(shù)據(jù)。complete函數(shù)中物喷,應(yīng)用napi_resolve_deferred()函數(shù) 或 napi_reject_deferred() 函數(shù)返回?cái)?shù)據(jù)卤材。

函數(shù)定義如下:

napi_status napi_create_promise(napi_env env,
                                napi_deferred* deferred,
                                napi_value* promise);

參數(shù)說明:

[in] env: 傳入接口調(diào)用者的環(huán)境,包含js引擎等峦失,由框架提供扇丛,默認(rèn)情況下直接傳入即可。
[out] deferred: 返回接收剛創(chuàng)建的deferred對象尉辑,關(guān)聯(lián)Promise對象帆精,后面使用napi_resolve_deferred() 或 napi_reject_deferred() 返回?cái)?shù)據(jù)。
[out] promise: 關(guān)聯(lián)上面deferred對象的JS Promise對象 返回值:返回napi_ok表示轉(zhuǎn)換成功隧魄,其他值失敗卓练。

static napi_value addPromise(napi_env env, napi_callback_info info) {
  // 創(chuàng)建promise
  napi_value promise = nullptr;
  napi_deferred deferred = nullptr;

// 創(chuàng)建promise對象。promise用于返回promise對象給js調(diào)用者
  NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));

  ...

  // 返回promise
  return promise;
}

初始化上下文數(shù)據(jù)

  • 定義一個上下文數(shù)據(jù)結(jié)構(gòu)堤器,用于保存和傳遞數(shù)據(jù)昆庇。Promise方式加上deferred屬性。
// 用戶提供的上下文數(shù)據(jù)闸溃,在原生方法(初始化數(shù)據(jù))整吆、executeCB拱撵、completeCB之間傳遞數(shù)據(jù)
struct AddonData {
  ...
  napi_deferred deferred = nullptr;
  double args[2] = {0};
  ...
};

static napi_value addPromise(napi_env env, napi_callback_info info) {
  // 獲取2個參數(shù),值的類型是js類型(napi_value)
  size_t argc = 2;
  napi_value args[2];
  napi_value thisArg = nullptr;
  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));
  ...
  // 創(chuàng)建promise
  napi_value promise = nullptr;
  napi_deferred deferred = nullptr;
  NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));
  ...
  // 將接收到的參數(shù)傳入
  NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
  NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));
  ...
}
  • Callback方式在addCallback傳入的是三個參數(shù)表蝙,Promise方式在addPromise傳入的是兩個參數(shù)拴测。

創(chuàng)建異步工作項(xiàng)

  • 同Callback方式一樣在創(chuàng)建異步工作項(xiàng)前,分別聲明2個函數(shù)府蛇,分別用作于napi_create_async_work()函數(shù)的execute集索、complete參數(shù)。

  • 異步工作項(xiàng)創(chuàng)建OK后汇跨,將其存入上下文數(shù)據(jù)的asyncWork屬性务荆,并調(diào)用napi_queue_async_work()將異步工作項(xiàng)加入調(diào)度隊(duì)列,由異步work線程池統(tǒng)一調(diào)度穷遂,原生方法返回Promise對象退出函匕。

// 用戶提供的上下文數(shù)據(jù),在原生方法(初始化數(shù)據(jù))蚪黑、executeCB盅惜、completeCB之間傳遞數(shù)據(jù)
struct AddonData {
  napi_async_work asyncWork = nullptr;
  napi_deferred deferred = nullptr;
  double args[2] = {0};
  double result = 0;
};

static napi_value addPromise(napi_env env, napi_callback_info info) {
  ...
  // 創(chuàng)建async work,創(chuàng)建成功后通過最后一個參數(shù)(addonData->asyncWork)用于后續(xù)在C++的異步線程中返回真正的計(jì)算結(jié)果
  napi_value resourceName = nullptr;
  napi_create_string_utf8(env, "addAsyncCallback", NAPI_AUTO_LENGTH, &resourceName);
  napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addPromiseCompleteCB, (void *)addonData,
                         &addonData->asyncWork);

  // 將剛創(chuàng)建的async work加到隊(duì)列忌穿,由底層去調(diào)度執(zhí)行
  napi_queue_async_work(env, addonData->asyncWork);

  // 原生方法返回promise
  return promise;
}

execute 回調(diào)處理

此處完全同Callback方式抒寂,無需修改。

// 業(yè)務(wù)邏輯處理函數(shù)掠剑,由worker線程池調(diào)度執(zhí)行屈芜。
static void addExecuteCB(napi_env env, void *data) {
  AddonData *addonData = (AddonData *)data;

  // 執(zhí)行復(fù)雜計(jì)算,不阻塞主線程澡腾。此處用一個加法簡單示意沸伏。
  addonData->result = addonData->args[0] + addonData->args[1];
}

complete 回調(diào)處理

  • 調(diào)用NAPI提供的napi_resolve_deferred() 或 napi_reject_deferred() 返回?cái)?shù)據(jù)糕珊。之后釋放過程中創(chuàng)建的napi_ref引用對象动分、異步工作項(xiàng)等對象。
static void addPromiseCompleteCB(napi_env env, napi_status status, void *data) {
  AddonData *addonData = (AddonData *)data;
  napi_value result = nullptr;
  napi_create_double(env, addonData->result, &result);
  napi_resolve_deferred(env, addonData->deferred, result);

     // 刪除napi_ref對象
     if (addonData->callback != nullptr) {
     napi_delete_reference(env, addonData->callback);
     }

  // 刪除異步工作項(xiàng)
  napi_delete_async_work(env, addonData->asyncWork);
  delete addonData;
  addonData = nullptr;
}

規(guī)范異步接口

hellonapi.cpp

#include <string.h>
#include<stdio.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"

struct AddonData {
  napi_async_work asyncWork = nullptr;
  napi_deferred deferred = nullptr;
  napi_ref callback = nullptr;
  double args[2] = {0};
  double result = 0;
};

// 業(yè)務(wù)邏輯處理函數(shù)红选,由worker線程池調(diào)度執(zhí)行澜公。
static void addExecuteCB(napi_env env, void *data) {
  AddonData *addonData = (AddonData *)data;

  // 執(zhí)行復(fù)雜計(jì)算,不阻塞主線程喇肋。此處用一個加法簡單示意坟乾。
  addonData->result = addonData->args[0] + addonData->args[1];
}

// 業(yè)務(wù)邏輯處理完成回調(diào)函數(shù),在業(yè)務(wù)邏輯處理函數(shù)執(zhí)行完成或取消后觸發(fā)蝶防,由EventLoop線程中執(zhí)行甚侣。
static void addCallbackCompleteCB(napi_env env, napi_status status, void *data) {
  AddonData *addonData = (AddonData *)data;
  napi_value callback = nullptr;
  napi_get_reference_value(env, addonData->callback, &callback);
  napi_value undefined = nullptr;
  napi_get_undefined(env, &undefined);
  napi_value result = nullptr;
  napi_create_double(env, addonData->result, &result);
  napi_value callbackResult = nullptr;

  // 執(zhí)行回調(diào)函數(shù)
  napi_call_function(env, undefined, callback, 1, &result, &callbackResult);

  // 刪除napi_ref對象
  if (addonData->callback != nullptr) {
    napi_delete_reference(env, addonData->callback);
  }

  // 刪除異步工作項(xiàng)
  napi_delete_async_work(env, addonData->asyncWork);
  delete addonData;
}

static void addPromiseCompleteCB(napi_env env, napi_status status, void *data) {
  AddonData *addonData = (AddonData *)data;
  napi_value result = nullptr;
  napi_create_double(env, addonData->result, &result);
  napi_resolve_deferred(env, addonData->deferred, result);

  // 刪除napi_ref對象
  if (addonData->callback != nullptr) {
    napi_delete_reference(env, addonData->callback);
  }

  // 刪除異步工作項(xiàng)
  napi_delete_async_work(env, addonData->asyncWork);
  delete addonData;
}

static napi_value addAsync(napi_env env, napi_callback_info info) {
  // 獲取3個參數(shù),值的類型是js類型(napi_value)
  size_t argc = 3;
  napi_value args[3];
  napi_value thisArg = nullptr;
  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));

  // 獲取并判斷js參數(shù)類型
  napi_valuetype valuetype0;
  NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
  napi_valuetype valuetype1;
  NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
  if (valuetype0 != napi_number || valuetype1 != napi_number) {
    napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
    return NULL;
  }

  // 異步工作項(xiàng)上下文用戶數(shù)據(jù)间学,傳遞到異步工作項(xiàng)的execute殷费、complete中傳遞數(shù)據(jù)
  auto addonData = new AddonData{
      .asyncWork = nullptr,
  };

  if (argc == 2) {
    // 創(chuàng)建promise
    napi_value promise = nullptr;
    napi_deferred deferred = nullptr;
    NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));
    addonData->deferred = deferred;

    // 將接收到的參數(shù)傳入
    NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
    NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));

    // 創(chuàng)建async work印荔,創(chuàng)建成功后通過最后一個參數(shù)(addonData->asyncWork)返回async work的handle
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "addPromise", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addPromiseCompleteCB, (void *)addonData,
                           &addonData->asyncWork);

    // 將剛創(chuàng)建的async work加到隊(duì)列,由底層去調(diào)度執(zhí)行
    napi_queue_async_work(env, addonData->asyncWork);

    // 返回promise
    return promise;
  } else {
    napi_valuetype valuetype2;
    NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2));
    if (valuetype2 != napi_function) {
      napi_throw_type_error(env, nullptr, "Callback function expected.");
      return NULL;
    }

    // 將接收到的參數(shù)傳入用戶自定義上下文數(shù)據(jù)
    NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
    NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));
    NAPI_CALL(env, napi_create_reference(env, args[2], 1, &addonData->callback));

    // 創(chuàng)建async work详羡,創(chuàng)建成功后通過最后一個參數(shù)接收async work的handle
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "addCallback", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addCallbackCompleteCB, (void *)addonData,
                           &addonData->asyncWork);

    // 將剛創(chuàng)建的async work加到隊(duì)列仍律,由底層去調(diào)度執(zhí)行
    napi_queue_async_work(env, addonData->asyncWork);

    // 原生方法返回空對象
    napi_value result = 0;
    NAPI_CALL(env, napi_get_null(env, &result));
    return result;
  }
}

// napi_addon_register_func
static napi_value registerFunc(napi_env env, napi_value exports) {
  static napi_property_descriptor desc[] = {

      DECLARE_NAPI_FUNCTION("addAsync", addAsync),
  };
  NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
  return exports;
}

// 1.先定義napi_module,指定當(dāng)前NAPI模塊對應(yīng)的模塊名
//以及模塊注冊對外接口的處理函數(shù)实柠,具體擴(kuò)展的接口在該函數(shù)中聲明
// nm_modname: 模塊名稱水泉,對應(yīng)eTS代碼為import nm_modname from '@ohos.ohos_shared_library_name'
//示例對應(yīng)eTS代碼為:import hellonapi from '@ohos.hellonapi'
static napi_module hellonapiModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = registerFunc, // 模塊對外接口注冊函數(shù)
    .nm_modname = "hellonapi",  // 自定義模塊名
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

//3.模塊定義好后,調(diào)用NAPI提供的模塊注冊函數(shù)napi_module_register(napi_module* mod)函數(shù)注冊到系統(tǒng)中窒盐。
// register module草则,設(shè)備啟動時自動調(diào)用此constructor函數(shù),把模塊定義的模塊注冊到系統(tǒng)中
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()
{
    napi_module_register(&hellonapiModule);
}

index.ets

import prompt from '@system.prompt';
import hellonapi from '@ohos.hellonapi'

@Entry
@Component
struct TestAdd {
  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {

      Button("hellonapi.addAsync(x, y, callback)").margin(10).fontSize(20).onClick(() => {
        let num1 = 123, num2 = 456
        hellonapi.addAsync(num1, num2, (result) => {
          prompt.showToast({ message: `hellonapi.addAsync(${num1}, ${num2}) = ${result}` })
        })
      })

      Button("hellonapi.addAsync(x, y).then(...)").margin(10).fontSize(20).onClick(() => {
        let num1 = 123, num2 = 456
        hellonapi.addAsync(num1, num2).then((result) => {
          prompt.showToast({ message: `hellonapi.addAsync(${num1}, ${num2}) = ${result}` })
        })
      })
    }
    .width('100%')
    .height('100%')
  }
}

@ohos.hellonapi.d.ts

declare namespace hellonapi {

    function addAsync(num1: number, num2: number, callback:(result: number) => void): void;
    function addAsync(num1: number, num2: number): Promise<number>;
    /**
     * 
     *
     * @since 9
     * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
     */

}
export default hellonapi;

異步方法和同步方法.ts接口文件

同步方法

  • 同步方法調(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 hellonapi {
    function add(num1: number, num2: number): number;
    /**
     * 
     *
     * @since 9
     * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
     */

}
export default hellonapi;

異步方法

  • 異步方法調(diào)用整個過程不會阻礙調(diào)用者的工作塔鳍。

  • 命名:動詞或動詞+名詞

  • 格式:

    • 無參:方法名([回調(diào)函數(shù)])
    • 有參:方法名(必填參數(shù)[, 可選參數(shù)][, 回調(diào)函數(shù)])
  • 返回值

    • 若回調(diào)函數(shù)非空伯铣,則返回void
    • 若回調(diào)函數(shù)為空,則返回Promise實(shí)例對象
  • 聲明文件模板

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 模塊名;
  • 示例
declare namespace hellonapi {

    function addAsync(num1: number, num2: number, callback:(result: number) => void): void;
    function addAsync(num1: number, num2: number): Promise<number>;
    /**
     * 
     *
     * @since 9
     * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
     */

}
export default hellonapi;   

NAPI中的數(shù)據(jù)類型

  • NAPI使用的數(shù)據(jù)類型和Node.js N-API保持一致掌唾。OpenHarmony的NAPI(Native API)組件是一套對外接口基于Node.js N-API規(guī)范開發(fā)的原生模塊擴(kuò)展開發(fā)框架放前。
    通過查看foundation/arkui/napi/interfaces/inner_api/napi/native_node_api.h(編寫NAPI拓展模塊hellonapi.cpp需要包含的頭文件)可以知道OpenHarmony基本的NAPI數(shù)據(jù)類型。

include <js_native_api.h>中的js_native_api.h在ohos3.2beta3版本源碼目錄下路徑為prebuilts/build-tools/common/nodejs/node-v12.18.4-linux-x64/include/node/js_native_api_types.h糯彬。

然后再分析prebuilts/build-tools/common/nodejs/node-v12.18.4-linux-x64/include/node/js_native_api_types.h和third_party/node/src/js_native_api_types.h內(nèi)容的差別凭语。

兩者內(nèi)容一致,可以推測OpenHarmony中基本的NAPI數(shù)據(jù)類型和Node.js N-API中的保持一致撩扒。而接口名方面似扔,napi提供的接口名與三方Node.js一致,目前支持部分接口搓谆,詳情見libnapi.ndk.json文件

// JSVM API types are all opaque pointers for ABI stability
// typedef undefined structs instead of void* for compile time type safety
typedef struct napi_env__* napi_env;
typedef struct napi_value__* napi_value;
typedef struct napi_ref__* napi_ref;
typedef struct napi_handle_scope__* napi_handle_scope;
typedef struct napi_escapable_handle_scope__* napi_escapable_handle_scope;
typedef struct napi_callback_info__* napi_callback_info;
typedef struct napi_deferred__* napi_deferred;

預(yù)處理器發(fā)現(xiàn) #include 指令后炒辉,就會尋找指令后面<>中的文件名,并把這個文件的內(nèi)容包含到當(dāng)前文件中泉手。被包含文件中的文本將替換源代碼文件中的#include 指令

  • 以typedef struct napi_env__* napi_env為例黔寇,搜遍Node.js的源碼都找不到napi_value__定義,那這個定義是什么意思呢斩萌?c語言中缝裤,允許定義一個沒有定義的結(jié)構(gòu)體的指針状囱。所以napi_value其實(shí)就是一個一級指針。他不需要類型信息倘是。

typedef作用就是定義類型別名

關(guān)于NAPI標(biāo)準(zhǔn)庫中導(dǎo)出的符號列表

  • NAPI它基于Node.js N-API規(guī)范開發(fā)亭枷,因此可參考Node.js N-API了解NAPI標(biāo)準(zhǔn)庫中符號列表。本文以3.2beta3源碼中的node三方庫為例搀崭,從third_party/node/README.OpenSource中可得知3.2beta3移植的node版本為14.19.1叨粘,因此可參考的Node.js N-API鏈接為14.19.1版本,如下:https://nodejs.org/docs/latest-v14.x/api/n-api.html

  • 標(biāo)準(zhǔn)庫中導(dǎo)出的符號列表

符號類型 符號名 備注
FUNC napi_module_register
FUNC napi_get_last_error_info
FUNC napi_throw
FUNC napi_throw_error
FUNC napi_throw_type_error
FUNC napi_throw_range_error
FUNC napi_is_error
FUNC napi_create_error
FUNC napi_create_type_error
FUNC napi_create_range_error
FUNC napi_get_and_clear_last_exception
FUNC napi_is_exception_pending
FUNC napi_fatal_error
FUNC napi_open_handle_scope
FUNC napi_close_handle_scope
FUNC napi_open_escapable_handle_scope
FUNC napi_close_escapable_handle_scope
FUNC napi_escape_handle
FUNC napi_create_reference
FUNC napi_delete_reference
FUNC napi_reference_ref
FUNC napi_reference_unref
FUNC napi_get_reference_value
FUNC napi_create_array
FUNC napi_create_array_with_length
FUNC napi_create_arraybuffer
FUNC napi_create_external
FUNC napi_create_external_arraybuffer
FUNC napi_create_object
FUNC napi_create_symbol
FUNC napi_create_typedarray
FUNC napi_create_dataview
FUNC napi_create_int32
FUNC napi_create_uint32
FUNC napi_create_int64
FUNC napi_create_double
FUNC napi_create_string_latin1
FUNC napi_create_string_utf8
FUNC napi_get_array_length
FUNC napi_get_arraybuffer_info
FUNC napi_get_prototype
FUNC napi_get_typedarray_info
FUNC napi_get_dataview_info
FUNC napi_get_value_bool
FUNC napi_get_value_double
FUNC napi_get_value_external
FUNC napi_get_value_int32
FUNC napi_get_value_int64
FUNC napi_get_value_string_latin1
FUNC napi_get_value_string_utf8
FUNC napi_get_value_uint32
FUNC napi_get_boolean
FUNC napi_get_global
FUNC napi_get_null
FUNC napi_get_undefined
FUNC napi_coerce_to_bool
FUNC napi_coerce_to_number
FUNC napi_coerce_to_object
FUNC napi_coerce_to_string
FUNC napi_typeof
FUNC napi_instanceof
FUNC napi_is_array
FUNC napi_is_arraybuffer
FUNC napi_is_typedarray
FUNC napi_is_dataview
FUNC napi_is_date
FUNC napi_strict_equals
FUNC napi_get_property_names
FUNC napi_set_property
FUNC napi_get_property
FUNC napi_has_property
FUNC napi_delete_property
FUNC napi_has_own_property
FUNC napi_set_named_property
FUNC napi_get_named_property
FUNC napi_has_named_property
FUNC napi_set_element
FUNC napi_get_element
FUNC napi_has_element
FUNC napi_delete_element
FUNC napi_define_properties
FUNC napi_call_function
FUNC napi_create_function
FUNC napi_get_cb_info
FUNC napi_get_new_target
FUNC napi_new_instance
FUNC napi_define_class
FUNC napi_wrap
FUNC napi_unwrap
FUNC napi_remove_wrap
FUNC napi_create_async_work
FUNC napi_delete_async_work
FUNC napi_queue_async_work
FUNC napi_cancel_async_work
FUNC napi_get_node_version
FUNC napi_get_version
FUNC napi_create_promise
FUNC napi_resolve_deferred
FUNC napi_reject_deferred
FUNC napi_is_promise
FUNC napi_run_script
FUNC napi_get_uv_event_loop

Native API接口說明

符號類型 符號名 備注
FUNC napi_run_script_path 運(yùn)行JavaScript文件

關(guān)于鏡像文件的編譯

  • 初次編譯OpenHarmony標(biāo)準(zhǔn)系統(tǒng)鏡像時瘤睹,會完整的編譯出boot_linux.img升敲、config.cfgMiniLoaderAll.bin轰传、parameter.txt驴党、ramdisk.imgresource.img获茬、system.img港庄、uboot.imgupdater.img恕曲、userdata.img鹏氧、vendor.img文件
  • 后面自己修改源碼(不涉及內(nèi)核源碼)后編譯,可以燒錄自己編譯的system.img佩谣、vendor.img把还、updater.imguserdata.img茸俭、ramdisk.img

寫在最后

  • 如果你覺得這篇內(nèi)容對你還蠻有幫助吊履,我想邀請你幫我三個小忙:
  • 點(diǎn)贊,轉(zhuǎn)發(fā)调鬓,有你們的 『點(diǎn)贊和評論』艇炎,才是我創(chuàng)造的動力。
  • 關(guān)注小編袖迎,同時可以期待后續(xù)文章ing??冕臭,不定期分享原創(chuàng)知識腺晾。
  • 想要獲取更多完整鴻蒙最新學(xué)習(xí)知識點(diǎn)燕锥,請移步前往小編:https://gitee.com/MNxiaona/733GH/blob/master/jianshu
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市悯蝉,隨后出現(xiàn)的幾起案子归形,更是在濱河造成了極大的恐慌,老刑警劉巖鼻由,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暇榴,死亡現(xiàn)場離奇詭異厚棵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蔼紧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門婆硬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人叹话,你說我怎么就攤上這事不同⌒鲎牛” “怎么了?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵谐区,是天一觀的道長。 經(jīng)常有香客問我逻卖,道長宋列,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任评也,我火速辦了婚禮炼杖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘盗迟。我一直安慰自己嘹叫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布诈乒。 她就那樣靜靜地躺著罩扇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怕磨。 梳的紋絲不亂的頭發(fā)上喂饥,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天,我揣著相機(jī)與錄音肠鲫,去河邊找鬼员帮。 笑死,一個胖子當(dāng)著我的面吹牛导饲,可吹牛的內(nèi)容都是我干的捞高。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼渣锦,長吁一口氣:“原來是場噩夢啊……” “哼硝岗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起袋毙,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤型檀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后听盖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胀溺,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡裂七,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了仓坞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片背零。...
    茶點(diǎn)故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖无埃,靈堂內(nèi)的尸體忽然破棺而出捉兴,到底是詐尸還是另有隱情,我是刑警寧澤录语,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布倍啥,位于F島的核電站,受9級特大地震影響澎埠,放射性物質(zhì)發(fā)生泄漏虽缕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一蒲稳、第九天 我趴在偏房一處隱蔽的房頂上張望氮趋。 院中可真熱鬧,春花似錦江耀、人聲如沸剩胁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昵观。三九已至,卻和暖如春舌稀,著一層夾襖步出監(jiān)牢的瞬間啊犬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工壁查, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留觉至,地道東北人。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓睡腿,卻偏偏與公主長得像语御,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子席怪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容