往期知识点整理
- 鸿蒙(HarmonyOS)北向开发知识点记录~
- 鸿蒙实战开发】ArkTS多线程的多线程系列(一):ArkTS多线能力入门
- 【鸿蒙实战开发】ArkTS多线程的多线程系列(二):基于Sendable共享对象实现跨线程通信及UI状态刷新
- 【鸿蒙实战开发】ArkTS多线性的多线程系列(三):基于单例实现跨线程缓存
- 【鸿蒙实战开发】ArkTS多线程的多线程系列(四):基于生产者-消费者实现多线程协同
- 【鸿蒙实战开发】ArkTS多线程的多线程系列(五):通过子线程实现全局弹窗
- 持续更新中……
当应用需要对网络状态进行长期管理监控时,当网络不好时弹窗提示用户当前网络状态差,或者网络连接中断时需要弹窗提示用户网络断开、任务异常终止等。
方案介绍
我们知道弹窗只能在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
});
}
}
}
总是有很多小伙伴反馈说:OpenHarmony开发不知道学习哪些技术?不知道需要重点掌握哪些OpenHarmony开发知识点? 为了解决大家这些学习烦恼。在这准备了一份很实用的鸿蒙全栈开发学习路线与学习文档给大家用来跟着学习。
针对一些列因素,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植……等)技术知识点。
《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN/733GH/overview
如何快速入门?
1.基本概念
2.构建第一个ArkTS应用
3.……
开发基础知识:
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.……
鸿蒙开发面试真题(含参考答案):https://gitcode.com/HarmonyOS_MN/733GH/overview
OpenHarmony 开发环境搭建
《OpenHarmony源码解析》:https://gitcode.com/HarmonyOS_MN/733GH/overview
- 搭建开发环境
- Windows 开发环境的搭建
- Ubuntu 开发环境搭建
- Linux 与 Windows 之间的文件共享
- ……
- 系统架构分析
- 构建子系统
- 启动流程
- 子系统
- 分布式任务调度子系统
- 分布式通信子系统
- 驱动子系统
- ……