当应用需要对网络状态进行长期管理监控时,当网络不好时弹窗提示用户当前网络状态差,或者网络连接中断时需要弹窗提示用户网络断开、任务异常终止等。
方案介绍
我们知道弹窗只能在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
});
}
}
}
最后
小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。
为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:
→【纯血版鸿蒙全套最新学习文档】希望这一份鸿蒙学习文档能够给大家带来帮助~
鸿蒙(HarmonyOS NEXT)最新学习路线
该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案
路线图适合人群:
IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术
2.学习视频+学习PDF文档
HarmonyOS Next 最新全套视频教程 (鸿蒙语法ArkTS、TypeScript、ArkUI教程……)
纯血版鸿蒙全套学习文档(面试、文档、全套视频等)
鸿蒙APP开发必备
总结
总的来说,华为鸿蒙不再兼容安卓,对程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,才能在这个变革的时代中立于不败之地。