《解压Zip文件》二、启动一个Worker指南
HarmonyOS @ohos.worker (启动一个Worker) 使用指南:多线程实战详解
模块类型:系统内置模块
关键词:Worker、多线程、后台任务、线程通信、ArkTS
效果
一、为什么需要 Worker?
在 HarmonyOS 应用中,主线程(UI 线程)负责界面渲染和用户交互。当执行耗时操作(如大文件解压、复杂计算、数据解析)时,主线程会被阻塞,导致界面卡顿甚至 ANR(应用无响应)。
@ohos.worker模块提供了多线程能力,允许开发者将耗时任务放到独立的 Worker 线程中执行,主线程保持流畅。
工作原理图
┌─────────────────┐ postMessage() ┌─────────────────┐ │ │ ─────────────────────► │ │ │ 主线程 (UI) │ │ Worker 线程 │ │ │ ◄───────────────────── │ (后台执行) │ └─────────────────┘ onmessage └─────────────────┘ │ 负责:UI 渲染 │ 负责:耗时任务 │ 用户交互 │ 文件操作 │ 事件处理 │ 数据计算线程通信模型
Worker 线程与主线程之间通过消息机制进行通信:
- 主线程通过
postMessage()向 Worker 发送数据 - Worker 线程通过
workerPort.postMessage()向主线程返回结果 - 双方通过
onmessage回调接收对方的消息
二、核心 API 一览
2.1 主线程侧 API
importworker,{MessageEvents}from'@ohos.worker';| 方法/属性 | 说明 |
|---|---|
new worker.ThreadWorker(scriptPath) | 创建 Worker 线程实例 |
worker.postMessage(data) | 向 Worker 线程发送消息 |
worker.onmessage | 接收 Worker 线程返回的消息 |
worker.onmessageerror | 接收反序列化失败的消息 |
worker.onerror | 接收 Worker 线程异常事件 |
worker.terminate() | 销毁 Worker 线程,释放资源 |
2.2 Worker 线程侧 API
import{MessageEvents,ErrorEvent,ThreadWorkerGlobalScope,worker}from'@kit.ArkTS';| 方法/属性 | 说明 |
|---|---|
worker.workerPort | 获取 Worker 线程的全局作用域 |
workerPort.postMessage(data) | 向主线程发送消息 |
workerPort.onmessage | 接收主线程发来的消息 |
workerPort.onmessageerror | 接收反序列化失败的消息 |
workerPort.onerror | 接收 Worker 运行异常 |
workerPort.close() | 关闭 Worker 线程(线程内部调用) |
三、完整实战示例
3.1 场景:后台执行文件解压
本示例演示一个完整的 Worker 使用流程:主线程发起解压请求,Worker 在后台执行解压,完成后通知主线程。
3.2 步骤一:配置 Worker 入口
在entry/build-profile.json5中声明 Worker 文件路径:
{ "apiType": "stageMode", "buildOption": { "sourceOption": { "workers": [ "./src/main/ets/workers/UnzipWorker.ets" ] } } }重要:必须在
build-profile.json5中注册 Worker 文件,否则编译时不会打包该文件,运行时会报错找不到脚本。
3.3 步骤二:编写 Worker 线程代码
文件路径:entry/src/main/ets/workers/UnzipWorker.ets
import{ErrorEvent,MessageEvents,ThreadWorkerGlobalScope,worker}from'@kit.ArkTS';importzlibfrom'@ohos.zlib';import{BusinessError}from'@ohos.base';import{hilog}from'@kit.PerformanceAnalysisKit';constTAG='UnzipWorker';// ✅ 定义消息协议接口,避免 any 类型错误interfaceWorkerRequest{zipPath:string;extractPath:string;taskId:number;}// 获取 Worker 线程的全局通信端口constworkerPort:ThreadWorkerGlobalScope=worker.workerPort;// 监听主线程发来的消息workerPort.onmessage=(e:MessageEvents)=>{// ✅ 必须显式类型转换,ArkTS 禁止使用隐式 anyconstdata:WorkerRequest=e.dataasWorkerRequest;constzipPath:string=data.zipPath;constextractPath:string=data.extractPath;consttaskId:number=data.taskId;hilog.info(0x0001,TAG,`收到解压任务:${zipPath}`);constoptions:zlib.Options={level:zlib.CompressLevel.COMPRESS_LEVEL_DEFAULT_COMPRESSION};try{zlib.decompressFile(zipPath,extractPath,options,(err:BusinessError)=>{if(err!==null&&err.code!==0){// 解压失败,通知主线程workerPort.postMessage({taskId:taskId,status:'error',message:`解压失败:${err.message}`});}else{// 解压成功,通知主线程workerPort.postMessage({taskId:taskId,status:'success',message:'解压完成'});}});}catch(err){constbizErr=errasBusinessError;workerPort.postMessage({taskId:taskId,status:'error',message:`异常:${bizErr.message}`});}};// 监听消息反序列化错误workerPort.onmessageerror=(e:MessageEvents)=>{hilog.error(0x0001,TAG,'消息反序列化失败');};// 监听 Worker 运行异常workerPort.onerror=(e:ErrorEvent)=>{hilog.error(0x0001,TAG,`Worker 异常:${e.message}`);};3.4 步骤三:在主线程中使用 Worker
importworker,{MessageEvents}from'@ohos.worker';import{PromptAction}from'@kit.ArkUI';import{Context}from'@kit.AbilityKit';interfaceUnzipResult{taskId:number;status:'success'|'error';message:string;}functionstartUnzipInWorker(context:Context,zipPath:string,extractPath:string,promptAction:PromptAction):void{// 1. 创建 Worker 实例(路径相对于 entry/src/main/ets/)constunzipWorker=newworker.ThreadWorker('entry/ets/workers/UnzipWorker.ets');// 2. 监听 Worker 返回的消息unzipWorker.onmessage=(e:MessageEvents):void=>{constresult=e.dataasUnzipResult;if(result.status==='success'){promptAction.showToast({message:result.message});}else{promptAction.showToast({message:result.message});}// 3. 使用完毕后销毁 Worker,释放资源unzipWorker.terminate();};// 3. 监听 Worker 异常unzipWorker.onerror=(e:ErrorEvent):void=>{promptAction.showToast({message:`Worker 错误:${e.message}`});unzipWorker.terminate();};// 4. 向 Worker 发送解压任务unzipWorker.postMessage({zipPath:zipPath,extractPath:extractPath,taskId:Date.now()});}四、消息传递的数据类型
Worker 线程与主线程之间传递的数据通过结构化克隆算法进行序列化,支持以下类型:
| 支持的类型 | 示例 |
|---|---|
| 基本类型 | string、number、boolean、null、undefined |
| 对象与数组 | { name: 'test' }、[1, 2, 3] |
| ArrayBuffer | 二进制数据传输 |
| Map / Set | 集合类型 |
| Date / RegExp | 内置对象 |
不支持的类型(会抛出序列化异常):
- 函数(Function)
- DOM 节点
- Symbol
- WeakMap / WeakSet
- 包含循环引用的对象
五、Worker 生命周期管理
5.1 创建阶段
constmyWorker=newworker.ThreadWorker('entry/ets/workers/MyWorker.ets');创建时传入的路径格式为:{moduleName}/ets/workers/{fileName}.ets
5.2 运行阶段
// 发送消息myWorker.postMessage({action:'start',payload:data});// 接收消息myWorker.onmessage=(e:MessageEvents)=>{/* 处理结果 */};5.3 销毁阶段
// 主线程销毁 WorkermyWorker.terminate();// Worker 线程自行关闭(在 Worker 内部调用)workerPort.close();最佳实践:Worker 使用完毕后务必调用
terminate()销毁,否则线程会持续占用系统资源。
六、最佳实践
6.1 何时使用 Worker
| 适合使用 Worker | 不适合使用 Worker |
|---|---|
| 文件压缩/解压 | 简单的数据赋值 |
| 大文件读写 | UI 状态更新 |
| 图片处理/编码 | 路由导航 |
| 复杂数据计算 | 快速同步操作 |
| JSON 大文件解析 | 事件监听注册 |
6.2 错误处理规范
// ✅ 推荐:完整的错误处理链myWorker.onmessage=(e:MessageEvents)=>{constresult=e.dataasTaskResult;if(result.status==='error'){// 处理业务错误}myWorker.terminate();// 确保销毁};myWorker.onerror=(e:ErrorEvent)=>{// 处理线程异常myWorker.terminate();};6.3 多 Worker 管理
如果需要同时处理多个任务,可以创建多个 Worker 实例:
// 并行解压多个文件for(constzipFileofzipFiles){constw=newworker.ThreadWorker('entry/ets/workers/UnzipWorker.ets');w.onmessage=(e:MessageEvents)=>{// 处理各自的结果w.terminate();};w.postMessage({zipPath:zipFile.path,extractPath:zipFile.out});}七、常见问题
Q1:Worker 文件找不到?
确保已在build-profile.json5的sourceOption.workers中注册路径。
Q2:postMessage 报错 “DataCloneError”?
检查传递的数据是否包含不支持的类型(如函数、Symbol 等)。
Q3:Worker 中能否访问 UI?
不能。Worker 线程无法操作 UI 组件,只能通过消息将结果传回主线程,由主线程更新 UI。
Q4:Worker 中能否使用 async/await?
可以。Worker 线程支持异步操作,但需注意 Worker 的生命周期管理。
Q5:报错 “Use explicit types instead of any, unknown” (arkts-no-any-unknown)?
这是 ArkTS 的严格类型检查规则。MessageEvents的data属性返回类型为隐式any,在 ArkTS 中必须显式声明类型:
// ❌ 错误:隐式 anyworkerPort.onmessage=(e:MessageEvents)=>{constdata=e.data;// 编译报错 arkts-no-any-unknownconstpath=data.zipPath;};// ✅ 正确:定义接口 + 显式类型转换interfaceWorkerRequest{zipPath:string;extractPath:string;}workerPort.onmessage=(e:MessageEvents)=>{constdata:WorkerRequest=e.dataasWorkerRequest;// 显式类型constpath:string=data.zipPath;};经验提示:主线程接收 Worker 消息时同样需要显式类型转换,如
const resp = e.data as WorkerResponse;