在HarmonyOS的应用开发中,弹窗通常需要在UI主线程中展示。然而,当应用程序需要从子线程触发弹窗时,直接在主线程中处理所有的弹窗逻辑会导致代码耦合度高,难以维护。本文将介绍一种设计模式,该模式允许子线程负责构建弹窗,而主线程只负责弹窗的展示,从而达到解耦的目的。

方案概述

目标

  • 解耦弹窗逻辑与主线程。
  • 子线程能够根据需要构建弹窗对象。
  • 主线程只需负责统一的弹窗调用。

HarmonyOS开发之异步弹窗_子线程

实现思路

  1. 定义DialogBuilder接口:这是一个共享接口,允许子线程构建弹窗逻辑。
  2. 实现特定弹窗构建器:例如AlertDialogBuilderToastDialogBuilder
  3. 创建DialogWorker:子线程,用于接收主线程的消息并构建弹窗。
  4. 构建DialogBuilderWrapper:包装DialogBuilder,便于在子线程和主线程之间传递。
  5. 统一弹窗展示:主线程根据收到的DialogBuilderWrapper展示弹窗。

核心代码实现

第一步: 定义 DialogBuilder 接口

import { lang } from '@kit.ArkTS';

type ISendable = lang.ISendable;

export interface DialogBuilder extends ISendable {
  showDialog(uiContext: UIContext): void;
}

第二步: 实现 ToastDialogBuilder

@Sendable
export class ToastDialogBuilder implements DialogBuilder {
  showDialog(uiContext: UIContext): void {
    uiContext.getPromptAction().showToast({
      message: 'this toast was built by worker',
      duration: 2000
    });
  }
}

第三部: 实现 DialogWorker

// ./src/main/ets/show_dialog/workers/DialogWorker.ets

...

workerPort.onmessage = (e: MessageEvents) => {
  switch (e.data) {
    case "showAlertDialog": {
      let alertDialogBuilder = new AlertDialogBuilder();
      let builderWrapper: DialogBuilderWrapper = new DialogBuilderWrapper(DialogBuilderWrapper.SYSTEM_DIALOG, alertDialogBuilder);
      workerPort.postMessageWithSharedSendable(builderWrapper);
      break;
    }

    case "showToastDialog": {
      let toastDialogBuilder = new ToastDialogBuilder();
      let builderWrapper : DialogBuilderWrapper = new DialogBuilderWrapper(DialogBuilderWrapper.SYSTEM_DIALOG, toastDialogBuilder);
      workerPort.postMessageWithSharedSendable(builderWrapper);
      break;
    }

    case "showCustomerDialog" : {
      let builderWrapper : DialogBuilderWrapper = new DialogBuilderWrapper(DialogBuilderWrapper.CUSTOMER_DIALOG, null, new CustomerDialogParam("this content is from worker"));
      workerPort.postMessageWithSharedSendable(builderWrapper);
      break;
    }

  }
}

...

第四步: 创建 DialogBuilderWrapper

export class DialogBuilderWrapper {
  static SYSTEM_DIALOG : string = "systemDialog";
  static CUSTOMER_DIALOG : string = "customerDialog";
  dialogType = "systemDialog";
  customerDialogParam !: CustomerDialogParam | undefined;
  dialogBuilder !: DialogBuilder | null;

  constructor(dialogType : string, dialogBuilder : DialogBuilder | null, customerDialogParam ?: CustomerDialogParam) {
    this.dialogType = dialogType;
    this.dialogBuilder = dialogBuilder;
    this.customerDialogParam = customerDialogParam;
  }
}

第五步: 在主线程中处理弹窗展示

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 启动Worker
    let dialogWorker : worker.ThreadWorker = new worker.ThreadWorker('entry/ets/show_dialog/workers/DialogWorker.ets');
    // 将Worker存入AppStorage
    AppStorage.setOrCreate("dialogWorker", dialogWorker);
    dialogWorker.onmessage = async (msgEvent : MessageEvents) => {
      let dialogBuilderWrapper = msgEvent.data as DialogBuilderWrapper;
      if (dialogBuilderWrapper.dialogType == DialogBuilderWrapper.SYSTEM_DIALOG) {
        let dialogBuilder = dialogBuilderWrapper.dialogBuilder;
        if (dialogBuilder) {
          dialogBuilder.showDialog((await windowStage.getMainWindow()).getUIContext());
        }
      } else {
        if (dialogBuilderWrapper.customerDialogParam) {
          let uiContext = (await windowStage.getMainWindow()).getUIContext();
          let promptAction = uiContext.getPromptAction();
          let contentNode = new ComponentContent(uiContext, getCustomerDialogBuilder(), dialogBuilderWrapper.customerDialogParam);
          promptAction.openCustomDialog(contentNode);
        }
      }
    };
  }
}

第六步: 自定义弹窗参数和构建器

@Sendable
export class CustomerDialogParam {
  private message : string = "";

  constructor(msg: string) {
    this.message = msg;
  }

  getMessage() {
    return this.message;
  }
}

export function getCustomerDialogBuilder() {
  return wrapBuilder(buildText);
}

@Builder
function buildText(params: CustomerDialogParam) {
  Column() {
    Text(params.getMessage())
      .fontSize(25)
      .fontWeight(FontWeight.Bold)
  }.backgroundColor('#FFF0F0F0')
    .margin({bottom: 36})
}

第七部: 页面中的按钮触发弹窗

@Component
export struct ShowDialogFromWorkerPage {
  private dialogWorker : worker.ThreadWorker | undefined = AppStorage.get("dialogWorker");

  build() {
    NavDestination() {
      Column() {
        Text('worker子线程弹窗')
        Button('弹AlertDialog').onClick(event => {
          this.showDialogFromWorker("showAlertDialog");
        })
        Button('弹Toast').onClick(event => {
          this.showDialogFromWorker("showToastDialog");
        })
        Button('弹自定义弹窗').onClick(event => {
          this.showDialogFromWorker("showCustomerDialog");
        })
      }
      .justifyContent(FlexAlign.SpaceEvenly)
      .height('100%')
      .width('100%')
    }
    .hideTitleBar(true)
  }

  private showDialogFromWorker(dialogType : string) {
    try {
      this.dialogWorker?.postMessage(dialogType);
    } catch (error) {
      promptAction.showToast({
        message: 'Worker instance is not running, maybe worker is terminated when PostMessage',
        duration: 2000
      });
    }
  }
}

结论

通过上述的设计和实现,我们成功地将弹窗逻辑从业务代码中分离出来,使得子线程可以根据需要构建弹窗,而主线程仅负责展示这些弹窗。这不仅提高了代码的可维护性,也使应用更加灵活和易于扩展。