OpenHarmony napi異步調(diào)用ets的方法

一、Worker線程native c++直接給UI線程發(fā)送消息

??本節(jié)寫一個讓native c++端向UI線程發(fā)消息postMessage的方法。
??為了方便測試,最簡單事例是UI頁面有一個按鈕,按下后向Worker線程發(fā)消息{type: "NATIVE_POST_MESSAGE"}讓W(xué)orker線程調(diào)度native C++:

//\entry\src\main\ets\pages\Index.ets
@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    Row() {
      Column() {
        Button(('native post return ui thread'))
          .width('100%')
          .onClick(() => {
            TestWorker.getInstance().postMessage({type: "NATIVE_POST_MESSAGE"})
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

??UI線程側(cè)的管理器如下:

//\entry\src\main\ets\workers\TestWorker.ets
import worker from '@ohos.worker';
import { hilog } from '@kit.PerformanceAnalysisKit';

export class TestWorker {
  public threadWorker: worker.ThreadWorker;

  private constructor() {
    this.threadWorker = new worker.ThreadWorker("entry/ets/workers/TestWorkerHandler.ts");
    this.threadWorker.onerror = (e) => {
      let msg = e.message;
      let filename = e.filename;
      let lineno = e.lineno;
      let colno = e.colno;
      hilog.error(0x0001, "TestWorker", `TestWorker Error ${msg} ${filename} ${lineno} ${colno}`);
    };

    this.threadWorker.onmessage = (msg) => {
      hilog.info(0x0001, "TestWorker", `UIThread get message <${msg.data.type}>`);
    }
  }

  public static getInstance(): worker.ThreadWorker {
    if (AppStorage.Get("app_key_test_worker") == null) {
      AppStorage.setOrCreate("app_key_test_worker", new TestWorker);
    }
    let testWorker = AppStorage.Get("app_key_test_worker") as TestWorker;
    return testWorker.threadWorker;
  }
}

??管理器只是方便單例化管理,外加接收Worker線程發(fā)送回給UI線程的消息(注意上面按鈕是發(fā)送給Worker線程的,不是發(fā)送給這里的)。
??Worker線程定義如下:

//\entry\src\main\ets\workers\TestWorkerHandler.ets
import worker from '@ohos.worker';
import { hilog } from '@kit.PerformanceAnalysisKit';

import testNapi from 'libentry.so';

const workerPort = worker.workerPort

workerPort.onmessage = (e)=> {
  hilog.info(0x0001, "TestWorker", `TestWorkerThread get message <${e.data.type}>`);
  switch (e.data.type)
  {
  case "NATIVE_POST_MESSAGE":
    testNapi.post_message_to_ui_thread();
    break;
  }
}

export {workerPort}

??這里判斷假如收到UI線程發(fā)送出的NATIVE_POST_MESSAGE消息,則調(diào)度native c++的post_message_to_ui_thread方法(這個是自定義的)。
??注意在文件末尾聲明了export {workerPort},不然native調(diào)用napi是無法獲取workerPort這個變量的。
??然后聲明接口:

//\entry\src\main\cpp\types\libentry\Index.d.ts
export const post_message_to_ui_thread: () => void;
//\entry\src\main\cpp\napi_init.cpp
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        { "post_message_to_ui_thread", nullptr, PostMessageToUIThread, nullptr, nullptr, nullptr, napi_default, nullptr }
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

??在native c++中我們要做相當(dāng)于在TestWorkerHandler.ets調(diào)用workerPort.postMessage({type:"i'm message from native C++", args:11454})這件事。
??在實現(xiàn)之前先聲明一個判斷變量是否獲取成功的方法,單純靠napi_status判斷無法得知調(diào)用是否成功:

bool loadVariantSuccess(napi_env env, napi_value value)
{
    napi_valuetype type;
    napi_status status = napi_typeof(env, value, &type);
    
    return status == napi_ok && type != napi_undefined;
}

??首先加載文件:

static napi_value PostMessageToUIThread(napi_env env, napi_callback_info info)
{
    napi_value result = nullptr;
    
    //1. 使用napi_load_module_with_info加載TestWorkerHandler.ets
    napi_value module;

    //#include "bundle/native_interface_bundle.h" and link libbundle_ndk.z.so
    OH_NativeBundle_ApplicationInfo appInfo = OH_NativeBundle_GetCurrentApplicationInfo();
    std::string module_info = std::string(appInfo.bundleName) + "/entry";
    free(appInfo.bundleName);

    napi_status status = napi_load_module_with_info(env, "entry/src/main/ets/workers/TestWorkerHandler", module_info.c_str(), &module);
    
    if (status != napi_ok)
    {
        ((void)OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "TestWorker", "load module error"));
        return result;
    }

??然后從文件中獲取export的workerPort變量:

    //2. 獲取文件中定義并export的workerPort變量
    napi_value workerPortEtsStr;
    std::string funcStr = "workerPort";
    napi_create_string_utf8(env, funcStr.c_str(), funcStr.size(), &workerPortEtsStr);
    //2. 使用napi_get_property獲取DEFAULT變量
    napi_value vWorkerPort;
    status = napi_get_property(env, module, workerPortEtsStr, &vWorkerPort);
    
    if (status != napi_ok || !loadVariantSuccess(env, vWorkerPort))
    {
        ((void)OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "TestWorker", "get property error"));
        return result;
    }

??從變量中獲取postMessage方法:

    //3. 從變量中獲取postMessage方法
    napi_value fPostMessage;
    status = napi_get_named_property(env, vWorkerPort, "postMessage", &fPostMessage);
    
    if (status != napi_ok || !loadVariantSuccess(env, fPostMessage))
    {
        ((void)OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "TestWorker", "get function error"));
        return result;
    }

??構(gòu)造消息對象:

    //4. 構(gòu)造一個對象,相當(dāng)于在ets中聲明{type: "i'm message from native C++", args:114514}
    napi_value messageObj;
    
    napi_value returnEtsStr;
    std::string returnStr = "i'm message from native C++";
    napi_create_string_utf8(env, returnStr.c_str(), returnStr.size(), &returnEtsStr);
    
    napi_value args;
    napi_create_int32(env, 114514, &args);
    
    const char *keysArray[] = {"type", "args"};
    const napi_value valueArray[] = {returnEtsStr, args};

    status = napi_create_object_with_named_properties(env, &messageObj, sizeof(keysArray) / sizeof(keysArray[0]), keysArray, valueArray);
    
    if (status != napi_ok)
    {
        ((void)OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "TestWorker", "create obj error"));
        return result;
    }

??調(diào)用postMessage方法:

    //5. 調(diào)用workerPort.postMessage方法
    status = napi_call_function(env, nullptr, fPostMessage, 1, &messageObj, nullptr);
    
    if (status != napi_ok)
    {
        ((void)OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "TestWorker", "call function error"));
        return result;
    }
    
    return result;
}

二、Worker native 間接調(diào)度ets方法

??假設(shè)有一個窗口管理模塊,暴露出設(shè)置窗口大小的接口:

//\entry\src\main\ets\utils\WindowUtils.ets
import { hilog } from '@kit.PerformanceAnalysisKit';

export class WindowUtils {
  static setScreenResolution(width: number, height: number, fullscreen: boolean) {
    hilog.info(0x0001, "TestWorker", `WindowUtils set ${width}x${height} fullscreen:${fullscreen}`);
  }
}

??直接export方法自然也是可行的,不過讓ets調(diào)用是可以異步的,對于一些耗時操作尤其有用。
??先定義一個消息類型,當(dāng)UI線程接收到:

{
    type : "RUN_ON_UI_THREAD_JS",
    funcName : "WindowUtils.setScreenResolution",
    args : [1920, 1080, true]
}

??代表要調(diào)用方法并傳入?yún)?shù),需要先到線程控制模塊中聲明map用于交互:

//\entry\src\main\ets\workers\TestWorker.ets
import { WindowUtils } from '../utils/WindowUtils'

export class TestWorker {
  public threadWorker: worker.ThreadWorker;

  public mModules: Record<string, ESObject> = {
    "WindowUtils": WindowUtils,
  };

??接收到消息時調(diào)用函數(shù):

this.threadWorker.onmessage = (msg) => {
  hilog.info(0x0001, "TestWorker", `UIThread get message <${msg.data.type}>`);

  switch (msg.data.type)
  {
    case "RUN_ON_UI_THREAD_JS":
      this.dispatch(msg.data);
      break;
  }
}

??調(diào)用函數(shù)的實現(xiàn)為:

private dispatch(data: ESObject)
{
  const tokens: ESObject = data.funcName.split(".");
  //先獲取模塊,既WindowUtils
  const mod: ESObject = this.mModules[tokens[0]];
  if (!!mod) {
    //從模塊中獲取方法setScreenResolution
    const func: ESObject = mod[tokens[1]];
    let argsArr: Array<ESObject> = data.args;

    //調(diào)用函數(shù)
    if (!!func) {
      let result: ESObject = func(...argsArr);
    }
  }
}

??native中也需要相應(yīng)改變,在上一節(jié)的第四步構(gòu)造傳入函數(shù)階段:

    //4. 構(gòu)造一個對象
    napi_value messageObj;
    
    napi_value typeEtsStr;
    std::string returnStr = "RUN_ON_UI_THREAD_JS";
    napi_create_string_utf8(env, returnStr.c_str(), returnStr.size(), &typeEtsStr);
    
    napi_value funcNameEtsStr;
    std::string funcNameStr = "WindowUtils.setScreenResolution";
    napi_create_string_utf8(env, funcNameStr.c_str(), funcNameStr.size(), &funcNameEtsStr);
    
    napi_value args;
    napi_create_array(env, &args);
    
    napi_value args0;
    napi_create_int32(env, 1920, &args0);
    napi_value args1;
    napi_create_int32(env, 1080, &args1);
    
    const bool trueValue = true;
    napi_value args2;
    napi_get_boolean(env, trueValue, &args2);
    
    napi_set_element(env, args, 0, args0);
    napi_set_element(env, args, 1, args1);
    napi_set_element(env, args, 2, args2);
    
    const char *keysArray[] = {"type", "funcName", "args"};
    const napi_value valueArray[] = {typeEtsStr, funcNameEtsStr, args};

    status = napi_create_object_with_named_properties(env, &messageObj, sizeof(keysArray) / sizeof(keysArray[0]), keysArray, valueArray);
    
    if (status != napi_ok)
    {
        ((void)OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "TestWorker", "create obj error"));
        return result;
    }

三、異步操作

??假設(shè)設(shè)置窗口大小是一個耗時操作,我這里用等待5s來模擬:

export class WindowUtils {
  static async setScreenResolution(width: number, height: number, fullscreen: boolean) {
    hilog.info(0x0001, "TestWorker", `WindowUtils set ${width}x${height} fullscreen:${fullscreen}`);

    const sleep = (ms: number) => new Promise<number>(resolve => setTimeout(resolve, ms));
    await sleep(5000);

    hilog.info(0x0001, "TestWorker", `WindowUtils set resolution end`);
  }
}

??在消息中我們加入一個新參數(shù)timeoutMs用于設(shè)置當(dāng)前操作是否超時,我們假定為50ms:

//napi_init.cpp PostMessageToUIThread
napi_value timeoutMs;
napi_create_int32(env, 50, &timeoutMs);

const char *keysArray[] = {"type", "funcName", "args", "timeoutMs"};
const napi_value valueArray[] = {typeEtsStr, funcNameEtsStr, args, timeoutMs};

??在dispatch中判斷,假如等待時間大于等于0,按async函數(shù)處理,否則按一般函數(shù)處理:

//TestWork.ets private async dispatch(data: ESObject)
if (!!func)
{
  if (data.timeoutMs >= 0)
  {
    let timeout: Promise<number> = new Promise((_, reject) => {
      setTimeout(() => {
        reject(new Error("Async call timeout"));
      }, data.timeoutMs);
    });

    let succ: ESObject = func(...argsArr);

    Promise.race([succ, timeout]).then((result: ESObject) => {
      hilog.info(0x0001, "TestWorker", `UI Thread async success`);
    }).catch((reason: ESObject) => {
      hilog.error(0x0001, "TestWorker", `UI Thread async timeout`);
    });
  }
  else
  {
    let result: ESObject = func(...argsArr);
  }
}

??上述邏輯是,設(shè)置一個計時的Promise timeout,到達指定時間報錯,async方法本身也返回一個Promise命名為succ,Promise.race([succ, timeout])看哪個Promise先執(zhí)行完畢,沒報錯執(zhí)行then,有報錯執(zhí)行catch。
??上述寫法中我是報log,正常生產(chǎn)環(huán)境下,可以調(diào)用回調(diào)函數(shù)。

引用:https://developer.huawei.com/consumer/cn/doc/best-practices-V5/bpta-complex-type-pass-V5#section1521193811224

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容