鸿蒙OH实战开发:ArkTS多线程的多线程系列(二)基于Sendable共享对象实现跨线程通信及UI状态刷新


📚往期学习笔录📰:

📰 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?

📰 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~

📰 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?

📰 记录一场鸿蒙开发岗位面试经历~

📰 持续更新中……


主线程启动文件上传、下载、持久化等耗时操作时,往往都需要通过子线程完成相关操作,为了让UI体验更好,文件下载或者报错的过程中往往都需要提供进度条提示以提升用户感知。同时对于文件列表类型的上传、下载往往还会提供类似暂停\继续的能力,类似如下场景:

本案例将使用Sendable共享对象实现以下两个主要功能:

1、子线程的计算结果刷新UI(进度通知、下载结果通知)。

2、主线程控制子线程业务逻辑(暂停下载、接续下载)。

方案介绍

  1. 通过Sendable构建可跨线程共享的对象DownloadVideoInfo;

  2. 主线程通过构建new taskpool.Task(downloadVideo, this.dvi)将DownloadVideoInfo共享对象同步给子线程(this.dvi是DownloadVideoInfo对象实例,downloadVideo是通过@Concurrent修饰的多线程任务);

  3. 当点击启动时,通过taskpool.execute()方法启动子线程;

  4. 主线程通过的onReceiveData()注册下载进度更新回调,已实现下载进度的UI刷新。

    • 子线程在共享对象中更新下载进度,并通过taskpool.Task.sendData(“UPDATE_DOWNLOAD_PROGRESS”)通知主线程从共享对象中更新下载进度。

    • 主线程通过this.downloadTask.onReceiveData()方法注册子线程发送来的消息回调,此处通过监听"UPDATE_DOWNLOAD_PROGRESS"消息,实现UI进度条更新。

  5. 下载命令控制,暂停/继续下载时需要通过主线程想子线程发送消息,由于taskpool不支持该能力,因此通过共享对象实现此能力。

核心代码

Step1:构建@Sendable对象DownloadVideoInfo、VideoInfo、ActorInfo。此处需要注意,DownloadCommandEnum枚举需要使用const进行修饰

  • DownloadVideoInfo共享类中,使用了基础类型和其他共享对象作为属性,并携带成员方法。
// src\main\ets\model\DownloadVideoInfo.ets

import { DownloadStateEnum } from './DownloadStateEnum';

import { VideoInfo } from './VideoInfo';

@Sendable

export class DownloadVideoInfo {

videoUrl: string = "URL";

downloadProgress: number = 0;

command: DownloadCommandEnum = DownloadCommandEnum.CONTINUE_DOWNLOADING;

videoInfo : VideoInfo = new VideoInfo();

constructor(videoUrl : string) {

this.videoUrl = videoUrl;

}

getDownloadProgress(): number {

return this.downloadProgress;

}

increaseDownloadProgress20Percent() {

this.downloadProgress += 20;

}

}
  • 由于TaskPool没有主线程给子线程发送消息的接口,因此此处定义DownloadCommandEnum用来实现主线给子线程下发具体的Download命令(暂停下载、继续下载等)。

    枚举必须使用const进行修饰

// src\main\ets\model\DownloadCommandEnum.ets

export const enum DownloadCommandEnum {

STOP_DOWNLOADING,

CONTINUE_DOWNLOADING,

CANCEL_DOWNLOADING

}
  • VideoInfo共享类中的Array容器来自于@arkts.collections,容器类中的ActorInfo也必须是Sendable类型
// src\main\ets\model\VideoInfo.ets

import collections from '@arkts.collections';

@Sendable

export class VideoInfo {

long: number = 0;

actors : collections.Array<ActorInfo> = new collections.Array<ActorInfo>()

}

ActorInfo类必须是Sendable类型

// src\main\ets\model\ActorInfo.ets

import { Sex } from './SexEnum';

@Sendable

export class ActorInfo {

name : string = "";

sex : Sex = Sex.MALE;

...

}

Step2:将共享对象this.dvi作为Task的构造函数传入构造函数new taskpool.Task(downloadVideo, this.dvi),这样父、子线程就都将持有DownloadVideoInfo对象实例的引用。downloadData为组件入参。

@Concurrent

function downloadVideo(dvi : DownloadVideoInfo) {

...

}

@Component

struct DownloadComponent {

@Require downloadData !: DownloadData;

private dvi !: DownloadVideoInfo

private downloadTask !: taskpool.Task

aboutToAppear(): void {

this.dvi = new DownloadVideoInfo(this.downloadData.downloadUrl);

this.downloadTask = new taskpool.Task(downloadVideo, this.dvi)

}

...

}

Step3:点击启动按钮时,通过 taskpool.execute(this.downloadTask)启动子线程,并结合then方法,在子线程任务完成后,将状态UI显示内容置为“完成”

Button(this.buttonValue, { type: ButtonType.Normal, stateEffect: true, buttonStyle: ButtonStyleMode.TEXTUAL })

.onClick( ent => {

if (this.buttonValue === '完成') {

return;

} else {

this.start = !this.start;

// 通过点击按钮,实现对暂停下载和继续下载的命令控制

this.buttonValue = this.start ? '暂停' : '继续'

if (this.start) {

console.info("==== start task")

// 启动下载或者接续下载,整个任务始终使用一个共享变量。

this.dvi.command = DownloadCommandEnum.CONTINUE_DOWNLOADING;

this.task = taskpool.execute(this.downloadTask);

this.task.then(() => {

if(this.dvi.downloadProgress == 100) {

this.buttonValue = '完成'

this.downloadData.long = this.dvi.videoInfo.long;

this.showDetail = true;

}

})

} else {

// 暂停下载

this.dvi.command = DownloadCommandEnum.STOP_DOWNLOADING;

}

}

})

}

Step4:当子线程需要更新下载进度条数据,将下载进度设置到DownloadVideoInfo共享对象中,并给主线程发送"UPDATE_DOWNLOAD_PROGRESS"事件,主线程接收到事件后从共享对象中获取下载进度,并刷新UI。

4.1 子线程:模拟每间隔一秒将共享对象中的下载进度downloadProgress增加20,并通过sendDate接口,将更新下载进度消息(“UPDATE_DOWNLOAD_PROGRESS”)发送给主线程

@Concurrent

export function downloadVideo(dvi : DownloadVideoInfo) {

console.info("==== execute task")

let start : number;

while (dvi.downloadProgress < 100) {

start = Date.now();

while (Date.now() - start < 1000) {

// 模拟等待1秒

}

console.info("==== sendData from task")

dvi.increaseDownloadProgress20Percent();

taskpool.Task.sendData("UPDATE_DOWNLOAD_PROGRESS")

// 线程收到停止下载通知

if (dvi.command == DownloadCommandEnum.STOP_DOWNLOADING) {

return;

}

}

dvi.videoInfo.long = 120

}

4.2 主线程通过onReceiveData接收子线程消息,当判断消息是需要更新下载进度(“UPDATE_DOWNLOAD_PROGRESS”)时,将共享对象中的下载进度赋值给状态变量,更新UI。

struct DownloadComponent {

@Require downloadData !: DownloadData;

...

aboutToAppear(): void {

...

this.downloadTask.onReceiveData((msg : string) => {

switch (msg){

case "UPDATE_DOWNLOAD_PROGRESS" :

this.downloadData.downloadProgress = this.dvi.getDownloadProgress();

break;

default :

console.error("==== switch default");

}

})

}

...

Step5:下载过程中若主线程要给子线程发送消息(比如停止下载、继续下载)在taskpool中可以通过共享对象变量实现。当需要暂停或者继续时,将命令下发给共享对象DownloadVideoInfo.command属性,子线程中在业务合适的时机对参数就行判断,若条件满足则执行相关逻辑即可。主线程:

this.dvi.command = DownloadCommandEnum.STOP_DOWNLOADING;

子线程:此处子线程感知共享对象的下载命令变为STOP_DOWNLOADING时,结束子线程,在实际业务中需要根据业务诉求设置状态判断点以及业务处理逻辑。

// 线程收到停止下载通知

if (dvi.command == DownloadCommandEnum.STOP_DOWNLOADING) {

return;

}

当点击接续下载是,只需要在主线程再次启动子线程进行下载即可,前一个线程的下载状态会保存在共享对象中。此处需要将command的状态修改为接续下载CONTINUE_DOWNLOADING,否则子线程会退出。

if (this.start) {

this.dvi.command = DownloadCommandEnum.CONTINUE_DOWNLOADING;

this.task = taskpool.execute(this.downloadTask);

this.task.then(() => {

if (this.dvi.downloadProgress == 100) {

this.buttonValue = '完成'

this.downloadData.long = this.dvi.videoInfo.long;

this.showDetail = true;

}

}

补充的知识

此处的状态变量是通过@ObservedV2和@Trace进行标注的,因此在代码中看不到@State。@Request表示downloadData参数必须通过父组件赋值。DownloadData对象申明如下。当long和downloadProgress属性发送变化时,UI会跟随刷新,如前面代码提到的this.downloadData.long = this.dvi.videoInfo.long;和this.downloadData.downloadProgress = this.dvi.getDownloadProgress();

@ObservedV2

export class DownloadData {

videoName !: string;

downloadUrl !:string;

@Trace long !:number;

@Trace downloadProgress : number = 0;

constructor(videoName : string, downloadUrl : string) {

this.videoName = videoName;

this.downloadUrl = downloadUrl;

}

}
<think>好的,用户想在鸿蒙应用中实现自定义弹窗。我需要根据提供的引用资料来整理回答。首先,引用[3]提到dialog组件用于创建自定义弹窗,具体用法参考dialog API。然后引用[4]讨论了自定义弹窗的实现,可能涉及UI布局和事件处理。引用[2]提到弹窗控制器接口,如创建和销毁控制器,可能涉及ArkUI_NativeDialogAPI_x的结构体。引用[1]提到在worker子线程实现全局弹窗,需要注意线程间通信。 需要分步骤说明:第一步使用dialog组件创建基本弹窗,第步自定义布局,第三步控制弹窗显示与隐藏,第四步处理交互事件,第五步线程安全。每个步骤要结合引用中的API和示例,比如创建DialogController,设置自定义组件,使用@CustomDialog装饰器。另外,要注意兼容不同版本和开发范式,如ArkTS和类Web开发。最后生成相关问题,比如弹窗布局优化、动态内容加载、多线程处理等。确保所有数学公式用正确的LaTeX格式,但当前问题可能不涉及数学表达式,所以可能不需要。检查引用标识是否正确添加到每个引用的末尾。</think>在鸿蒙HarmonyOS实现自定义弹窗主要通过`dialog`组件和弹窗控制器完成,具体分为以下步骤: ### 一、基础弹窗创建 1. **使用`@CustomDialog`装饰器** 通过声明式UI定义弹窗内容,需继承`CustomDialogController`类: ```typescript @CustomDialog struct CustomDialogExample { controller: CustomDialogController build() { Column() { Text('自定义标题').fontSize(20) Button('关闭').onClick(() => { this.controller.close() }) }.padding(20) } } ``` 2. **弹窗控制器初始化** 通过`new CustomDialogController()`创建实例并绑定自定义弹窗组件[^3]: ```typescript @Component struct Index { dialogController: CustomDialogController = new CustomDialogController({ builder: CustomDialogExample(), cancel: () => console.log('弹窗关闭') }) build() { Button('打开弹窗').onClick(() => { this.dialogController.open() }) } } ``` --- ### 、高级功能实现 1. **动态内容传递** 通过构造函数参数实现动态数据注入: ```typescript @CustomDialog struct DynamicDialog { private message: string constructor(message: string) { this.message = message } //...构建逻辑中使用this.message } ``` 2. **多线程弹窗控制** 在Worker线程中通过`postMessage`与主线程通信,确保UI操作在主线程执行[^1]: ```typescript // worker线程 workerPort.postMessage({ type: 'SHOW_DIALOG', data: '警告信息' }) // 主线程 worker.onmessage = (msg) => { if (msg.type === 'SHOW_DIALOG') { showSystemDialog(msg.data) } } ``` --- ### 三、弹窗控制器API 1. **核心接口说明** 通过`ArkUI_NativeDialogAPI_x`结构体实现弹窗生命周期管理[^2]: ```c // Native层接口示例 napi_value CreateDialogController(napi_env env, napi_callback_info info) { // 创建控制器实例 OH_NativeDialog_CreateController(...); } ``` 2. **销毁资源管理** 显式调用`destroy()`方法释放弹窗资源: ```typescript dialogController.destroy() ``` --- ### 四、最佳实践建议 1. **视觉层级控制** 通过`zIndex`属性管理弹窗叠加顺序,建议值范围:`1000-9999` 2. **动效集成** 使用`transition`实现入场/退场动画: ```typescript .transition({ type: TransitionType.Insert, scale: { x: 0, y: 0 } }) .transition({ type: TransitionType.Delete, opacity: 0 }) ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值