【鸿蒙实战开发】ArkTS多线程的多线程系列(五):通过子线程实现全局弹窗

101 篇文章 0 订阅
101 篇文章 0 订阅

当应用需要对网络状态进行长期管理监控时,当网络不好时弹窗提示用户当前网络状态差,或者网络连接中断时需要弹窗提示用户网络断开、任务异常终止等。

方案介绍

我们知道弹窗只能在UI主线程弹出,因此最简单的处理方法是将需要弹窗的类型、弹窗内容都通过子线程发送到主线程,这样主线程再根据不同弹窗类型进行弹窗。但是这样主线程就会有大量的弹窗代码,业务耦合严重

因此我们将要弹出什么样的窗的权利下放给子线程,这样子线程就可以根据自己的需要构建弹窗,主线程只需要提供弹窗的统一调用即可,这样就实现了弹窗开发与主线程开发的解耦(主线程不需要感知子线程需要弹什么样的框)。此种方法仅限于系统类型弹窗(如AlertDialog,Toast等)。但是,对于自定义弹窗,由于@Buidler方法无法通过sendable传递,因此需要通过在主线程构建统一的自定义弹窗框架,子线程将弹窗参数传递到主线程。

DialogBuilder为Sendable类型的共享接口,提供了showDialog方法,用于弹窗。DialogBuilderWrapper是一个包装类,将DialogBuilder包装后以实现从子线程向主线程的数据传递。

  • 方案的优势:主线程不感知DialogBuilder具体实现类的实现,在主线程中只需要通过调用DialogBuilder的接口showDialog方法构建弹窗。子线程可以独立/按需构建各种弹窗对象,虽然最终的弹窗还是在主线程完成,但是主线程不再感知弹窗的细节。

  • 方案的遗憾:对于非系统类弹窗(自定义弹窗),由于@Builder方法无法在Sendable类中使用,因此子线程只能构建出自定义弹窗的参数(customerDialogParam),将构建参数传递给主线程,在抓线程中显示实现弹窗。

核心代码

Step1:构建Senbable共享类型的DialogBuilder接口,接口提供showDialog方法入参为uiContext,用于弹窗构建并弹窗。

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

type ISendable = lang.ISendable;

export interface DialogBuilder extends ISendable {

showDialog(uiContext: UIContext): void;

}

Step2:以弹出AlertDialog为例,AlertDialogBuilder需要实现DialogBuilder,并在showDialog方法中使用uiContext完成弹窗的构建。

@Sendable

export class ToastDialogBuilder implements DialogBuilder {

showDialog(uiContext: UIContext): void {

uiContext.getPromptAction().showToast({

message: 'this toast was built by worker',

duration: 2000

})

}

}

Step3:实现一个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;

}

}

}

...

从代码中可以看出,在postMessageWithSharedSendable方法中,传递的参数不是之前定义的AlertDialogBuilder共享数据类型,而是DialogBuilderWrapper,这是因为在dialogWorker.onmessage方法中,无法将mesEvent.data as 为Sendable类型,因此在传递Sendable对象类型时,需要再其外面增加一层封装。

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;

}

}

注意

DialogBuilderWrapper对象不是Sendable类型的,因此不需要提供get/set方法,因为方法是无法完成序列化的。

DialogBuilderWrapper对象里面的dialogBuilder和customerDialogParam是Sendable类型,因此他们提供的方法跨线程依旧可以调用。

Step4:启动DialogWorker,并将他放到AppStorage中,以便后续页面需要使用此Worker时可以方便获取。因为在弹窗时希望弹窗的逻辑与UI是解耦的,因此在Abiiltiy的onWindowStage回调中启动Worker。

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) {

// 如果是系统弹框,将uiContext传入buidler方法,完成弹窗构建。

let dialogBuilder = dialogBuilderWrapper.dialogBuilder

if (dialogBuilder) {

// 调用具体的DialogBuilder构建弹窗

dialogBuilder.showDialog((await windowStage.getMainWindow()).getUIContext())

}

} else {

// 如果是自定义弹窗,则在主线程获取弹窗Builder,并将子线程传递来的弹窗参数传递给Builder

if (dialogBuilderWrapper.customerDialogParam) {

let uiContext = (await windowStage.getMainWindow()).getUIContext()

let promptAction = uiContext.getPromptAction()

let contentNode = new ComponentContent(uiContext, getCustomerDialogBuilder(), dialogBuilderWrapper.customerDialogParam);

// 全局弹出自定义弹窗

promptAction.openCustomDialog(contentNode);

}

}

}

...

}

...

}

Step5:对于自定义弹窗,需要提前构建好组件Builder函数,通过CustomerDialogParam定义自定义弹窗内的具体样式和内容。(这个不是本重点,可以参考其他弹窗类文章进行参考)

@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})

}

Step6:在页面中通过按钮模拟弹窗。

@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

});

}

}

}

鸿蒙全栈开发全新学习指南

为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大厂APP实战项目开发】

本路线共分为四个阶段

第一阶段:鸿蒙初中级开发必备技能

在这里插入图片描述

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:gitee.com/MNxiaona/733GH

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:gitee.com/MNxiaona/733GH

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值