在HarmonyOS的应用开发中,弹窗通常需要在UI主线程中展示。然而,当应用程序需要从子线程触发弹窗时,直接在主线程中处理所有的弹窗逻辑会导致代码耦合度高,难以维护。本文将介绍一种设计模式,该模式允许子线程负责构建弹窗,而主线程只负责弹窗的展示,从而达到解耦的目的。
方案概述
目标
- 解耦弹窗逻辑与主线程。
- 子线程能够根据需要构建弹窗对象。
- 主线程只需负责统一的弹窗调用。
实现思路
- 定义
DialogBuilder
接口:这是一个共享接口,允许子线程构建弹窗逻辑。 - 实现特定弹窗构建器:例如
AlertDialogBuilder
和ToastDialogBuilder
。 - 创建
DialogWorker
:子线程,用于接收主线程的消息并构建弹窗。 - 构建
DialogBuilderWrapper
:包装DialogBuilder
,便于在子线程和主线程之间传递。 - 统一弹窗展示:主线程根据收到的
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
});
}
}
}
结论
通过上述的设计和实现,我们成功地将弹窗逻辑从业务代码中分离出来,使得子线程可以根据需要构建弹窗,而主线程仅负责展示这些弹窗。这不仅提高了代码的可维护性,也使应用更加灵活和易于扩展。