☰
HarmonyOS 7 QuickDock 闪控窗开发实录 04:Background Tasks Kit × Stage:前后台任务切换与窗口异常恢复【鸿蒙心迹】
2026/10/5 0:36:51 网站建设 项目流程

03 把 QuickDock 的拖动、侧边暂存和位置持久化做稳定以后,窗口这条线终于没有太多悬念。

第四篇开始处理真正复杂的业务状态:

应用进入后台 两个任务并存 不同任务后台策略不同 当前闪控窗切换展示任务 Float Surface 中途丢失 回前台后恢复

这一篇我故意没有写成“应用后台后任务继续跑”。

因为 HarmonyOS 的后台任务是受系统调度和场景约束的。当前 Background Tasks Kit 提供短时任务、长时任务等机制,不同业务要选择匹配的后台模式;官方最佳实践里也明确用长时任务保障视频导出、上传、下载等长耗时业务在后台持续执行。

所以 QuickDock 04 把两个任务拆开:

compress_assets_01 本地计算 88% SUSPENDED_POLICY upload_release_02 数据传输 61% RUNNING_BACKGROUND

当前闪控窗显示的是:

upload_release_02

本轮统一数据:

taskId: float_20261002_04 taskCount: 2 job0: compress_assets_01 job0Progress: 88% job0State: SUSPENDED_POLICY job1: upload_release_02 job1Progress: 61% job1State: RUNNING_BACKGROUND activeJob: upload_release_02 mode: FLOAT_VIEW backgroundTaskMode: DATA_TRANSFER backgroundDuration: 107s switchCount: 3 recoveryReason: FLOAT_SURFACE_LOST rebuildCount: 1 rebuildCost: 29ms listenerCount: 1 restoredPosition: x=732, y=128 status: RECOVERED

一、前后台切换以后,最不该做的是“所有任务一视同仁”

如果 TaskRegistry 里有两个任务:

本地压缩 数据上传

应用进入后台时不能简单:

全部继续

也不能:

全部暂停

应该由任务类型决定策略。

QuickDock 增加:

policy/ └── BackgroundTaskPolicy.ets

先把业务类型抽象出来:

exporttypeQuickTaskKind='LOCAL_COMPUTE'|'DATA_TRANSFER'exporttypeBackgroundDecision='KEEP_RUNNING'|'SUSPEND_POLICY'exportclassBackgroundTaskPolicy{resolve(kind:QuickTaskKind):BackgroundDecision{if(kind==='DATA_TRANSFER'){return'KEEP_RUNNING'}return'SUSPEND_POLICY'}}

这是项目自己的决策层。

真正申请 HarmonyOS Background Tasks Kit 能力的代码仍然放在 Adapter / Service 里,不把系统调用散到页面。

二、为什么上传任务可以继续,压缩任务这里选择暂停

当前 Background Tasks Kit 的长时任务模式包含数据传输等明确场景;官方文档也强调后台任务需要按实际业务选择合适机制,避免无限制占用资源。

所以 QuickDock 本轮对:

upload_release_02

采用:

DATA_TRANSFER

后台策略。

而本地压缩:

compress_assets_01

在当前 Phone Demo 里没有强行声明“后台无限执行”,而是:

RUNNING → SUSPENDED_POLICY

回前台再恢复。

这个设计比文章里一句“后台继续压缩”更符合系统约束。

如果未来切到符合其他后台模式的设备与场景,再由 Policy 决定是否允许继续。

三、TaskRegistry 从单任务升级成两任务,但 Float View 仍然只显示一个 activeJob

这一篇正式把 Store 从单任务升级成 Registry:

exportinterfaceRegistryTask{taskId:stringjobId:stringtitle:stringkind:QuickTaskKind progress:numberstate:'RUNNING'|'RUNNING_BACKGROUND'|'SUSPENDED_POLICY'|'COMPLETED'}exportclassTaskRegistry{privatetasks:Map<string,RegistryTask>=newMap()privateactiveJobId:string=''setActive(jobId:string):void{if(!this.tasks.has(jobId)){return}this.activeJobId=jobId}}

两个任务可以同时存在。

但标准闪控窗当前只显示:

activeJob

否则 320×184vp 的小窗很快会变成完整任务管理器。

四、进入后台时,activeJob 也可能切换

进入后台前:

activeJob: compress_assets_01

但压缩任务被策略挂起以后,继续显示一个:

88% SUSPENDED_POLICY

意义不大。

所以 QuickDock 自动切到:

upload_release_02 61% RUNNING_BACKGROUND

这次:

switchCount=3

包含:

用户手动切一次 后台策略切一次 前台恢复切一次

显示任务切换,不等于业务任务迁移。

只是 Float View 选择不同 Snapshot 显示。

五、Stage 生命周期只触发策略,不直接改任务内部实现

UIAbility:

onBackground onForeground

不应该直接:

upload.start() compress.pause()

而是通知 Coordinator:

exportclassForegroundBackgroundCoordinator{constructor(privateregistry:TaskRegistry,privatepolicy:BackgroundTaskPolicy){}onBackground():void{this.registry.all().forEach((task:RegistryTask)=>{constdecision=this.policy.resolve(task.kind)if(decision==='KEEP_RUNNING'){this.registry.markBackgroundRunning(task.jobId)}else{this.registry.suspendByPolicy(task.jobId)}})}}

生命周期只是“系统环境发生变化”的信号。

真正任务怎么处理,由业务 Policy 和对应 Service 决定。

六、后台数据传输使用系统能力,Window 只是状态出口

upload_release_02进入:

RUNNING_BACKGROUND

以后,QuickDock 会通过 Background Tasks Kit 的适配层申请匹配的数据传输能力。

这里最重要的一条仍然是:

Background Task != Float View

即使闪控窗短暂不可见,上传任务仍然由后台任务机制管理。

反过来,即使 Float View 还显示,系统也不意味着允许任意本地计算无限在后台跑。

这两个能力不能互相替代。

七、这一篇专门模拟一次 FLOAT_SURFACE_LOST

前两篇都是正常显示路径。

04 我主动做一个异常:

任务仍在 Store 正常 Float View Surface 丢失

异常标记:

FLOAT_SURFACE_LOST

关键规则:

重建显示层,不重建 TaskRegistry。

所以FloatRecoveryCoordinator:

exportclassFloatRecoveryCoordinator{privaterebuildCount:number=0asyncrebuild(reason:string):Promise<void>{constsnapshot=TaskRegistry.shared().activeSnapshot()constposition=WindowSessionStore.shared().lastPosition()awaitFloatViewAdapterFactory.current().rebuildFromSnapshot(snapshot,position)this.rebuildCount++}}

任务不会重新开始。

上传仍然是:

61%

窗口只是重新读取当前 Snapshot。

八、异常恢复后 listener 必须仍然只有 1

窗口重建最容易带来的副作用是:

旧 listener 没解绑 新窗口又注册一个

下一次进度变化就会收到两份 UI Update。

所以恢复以后检查:

listenerCount=1

本轮:

rebuildCount=1 listenerCount=1

如果变成 2,恢复不能记成成功。

九、位置恢复继续复用第三篇数据

窗口 Surface 重建后:

x=732 y=128

不是重新回默认左上角。

第三篇做的WindowSessionStore在这里直接复用。

恢复顺序:

TaskRegistry 当前 activeJob → WindowSessionStore lastPosition → Adapter rebuild → bind single listener → show

因此 03 的位置工程并不是独立小功能,而是 04 异常恢复真正依赖的基础。

十、前后台期间的 107 秒,到底发生了什么

当前回归数据:

backgroundDuration=107s

在这段时间里:

upload_release_02 36% → 61% RUNNING_BACKGROUND compress_assets_01 88% SUSPENDED_POLICY

回前台后:

compress_assets_01 允许重新 RUNNING

如果用户切回压缩任务,窗口继续从 88% 显示,不会从头开始。

这就是 TaskRegistry 独立存在的价值。

十一、DevEco 图里要同时看到策略和恢复

开发图:

本轮 HiLog:

taskId= float_20261002_04 onBackground tasks=2 policy upload_release_02 DATA_TRANSFER RUNNING_BACKGROUND policy compress_assets_01 SUSPENDED_POLICY FLOAT_SURFACE_LOST rebuild from TaskStore position x=732 y=128 rebuildCount=1 rebuildCost=29ms listenerCount=1 status=RECOVERED

这套日志能明确区分:

任务策略问题

和:

窗口恢复问题

十二、运行图第一次出现两个任务

最终运行图:

任务列表:

upload_release_02 61% RUNNING_BACKGROUND compress_assets_01 88% SUSPENDED_POLICY

当前 Float View:

activeJob: upload_release_02

异常恢复:

FLOAT_SURFACE_LOST → rebuild → RECOVERED

位置:

732 / 128

所有数据与 DevEco、日志保持一致。

十三、为什么本地压缩不偷偷放 Worker 就宣称“后台无限继续”

HarmonyOS 当前并发指导确实建议长耗时、常驻计算把工作放到 Worker,避免阻塞 UI 主线程。

但:

Worker

解决的是:

不要阻塞 UI

不是:

应用进后台后系统一定允许无限执行

后台生命周期和系统调度仍然要遵守 Background Tasks Kit 的规则。

所以 QuickDock 把两个问题分开:

线程执行模型 和 后台运行资格

这条边界对技术文章很重要,否则很容易把“子线程”误写成“后台保活”。

十四、多任务切换也不能复制任务对象

现在 Registry 有两个 Task。

Float View 切换显示时,只改变:

activeJobId

不会把任务内容复制进 WindowState。

否则:

TaskRegistry 61% WindowState 58%

又会重回第二篇解决过的状态分裂。

所以多任务以后仍然保持:

TaskRegistry = 业务事实源 Window = 当前 activeJob 的投影

十五、应用回前台后,恢复顺序也必须固定

前台恢复流程:

onForeground → 重新评估 BackgroundTaskPolicy → compress_assets_01 SUSPENDED_POLICY → RUNNING → upload_release_02 继续 RUNNING → 检查 Float Surface → 如果已丢失 rebuild → 恢复 activeJob

不能一回前台就先新建窗口。

因为窗口真正需要展示的是评估后的最新 TaskRegistry。

十六、后台任务失败和 Float Surface 丢失是两类异常

这一篇还专门区分:

TASK_FAILED

与:

FLOAT_SURFACE_LOST

前者:

任务业务失败 → Store = FAILED → UI 显示失败

后者:

任务仍正常 → 只重建窗口

如果把两者都叫:

ERROR

后面根本无法判断到底要不要重启任务。

十七、RECOVERED 代表什么

最终状态:

RECOVERED

至少代表:

后台数据任务继续 本地计算遵守策略挂起 两个任务状态没有丢 Float Surface 可以重建 位置恢复正确 listener 仍然只有 1

不只是“窗口又出现了”。

十八、第四篇最后做了六组压力测试

第一组,进入后台 107 秒,上传继续、压缩挂起。

第二组,回前台以后压缩恢复,不重新生成 taskId。

第三组,切换 activeJob 三次,进度分别保持。

第四组,模拟一次FLOAT_SURFACE_LOST,只重建 UI。

第五组,重建后位置仍是 732 / 128。

第六组,恢复后 listenerCount=1,没有重复订阅。

六组通过以后,本轮才记成:

RECOVERED

十九、下一轮 05 会把“偶发异常”升级成资源治理

做到 04,QuickDock 已经拥有:

Float View Floating Ball Drag Edge Stow Preferences Background Task Multi Task Recovery

能力开始多起来以后,新的问题会变成:

重复创建 重复 listener Window 没释放 Ball 没释放 Timer 没释放 TaskRegistry 任务已经结束但 UI 还在

05 会专门做生命周期冲突和资源治理。

06 最后再跑 25 轮完整回归,检查:

窗口实例 listener timer 内存 切换耗时 任务状态

不再加新功能。

二十、任务 Registry 必须保存“为什么被挂起”,不能只有一个 PAUSED

compress_assets_01在后台变成:

SUSPENDED_POLICY

我没有写成普通:

PAUSED

因为这两个语义不同。

用户点击暂停:

PAUSED_BY_USER

系统策略不允许当前场景继续:

SUSPENDED_POLICY

两者恢复条件也不同。

用户暂停的任务不能因为回前台就自动恢复;策略挂起的任务则可以在环境允许后恢复。

所以最终状态枚举更细:

exporttypeRegistryTaskState='RUNNING'|'RUNNING_BACKGROUND'|'PAUSED_BY_USER'|'SUSPENDED_POLICY'|'COMPLETED'|'FAILED'

04 当前压缩任务明确是:

SUSPENDED_POLICY

而不是用户主动暂停。

这条区别会继续影响 05 的生命周期治理。

二十一、后台任务申请失败时,要回退,而不是伪装成 RUNNING_BACKGROUND

数据上传任务计划申请:

DATA_TRANSFER

但系统能力申请仍然可能失败。

比如:

参数不合法 系统调度拒绝 能力不可用

这时绝对不能:

UI 仍显示 RUNNING_BACKGROUND

QuickDock 的处理是:

request background capability → success → RUNNING_BACKGROUND request failed → BACKGROUND_DENIED → 根据业务决定暂停或回主应用

UI 会显示明确的“后台能力不可用”,而不是继续画一个假的 61% 进度。

当前本轮是成功路径,所以最终是:

RUNNING_BACKGROUND

但失败分支已经存在。

二十二、activeJob 切换不能改变 TaskRegistry 的排序和生命周期

闪控窗当前只显示一个任务。

从:

compress_assets_01

切到:

upload_release_02

只是改变:

activeJobId

不会:

暂停原任务 重排 Registry 重建 Task Runner

因此switchCount=3只表示显示焦点切换次数。

这和“任务切换”这个词很容易混淆。

更准确地说:

QuickDock 切换的是“当前展示任务”,不是“系统只允许一条任务活着”。

多任务模型如果没有这条边界,用户每点一次任务卡片就可能意外影响业务运行。

二十三、Float Surface 异常恢复还要防止重复 rebuild

如果FLOAT_SURFACE_LOST连续上报两次,第一次 rebuild 还没完成,第二次又进来,会出现:

rebuildCount=2

甚至重复订阅 listener。

所以 Coordinator 增加恢复锁:

exportclassFloatRecoveryCoordinator{privaterebuilding:boolean=falseasyncrebuild():Promise<void>{if(this.rebuilding){return}this.rebuilding=truetry{constsnapshot=TaskRegistry.shared().activeSnapshot()constpos=WindowSessionStore.shared().lastPosition()awaitFloatViewAdapterFactory.current().rebuildFromSnapshot(snapshot,pos)}finally{this.rebuilding=false}}}

当前最终:

rebuildCount=1

不是因为系统只触发了一次,而是 Manager 保证一次恢复流程只有一份。

二十四、回前台时先恢复业务状态,再恢复 UI

应用重新进入前台以后,最自然的写法是:

onForeground → show Float View → resume tasks

但这样窗口第一帧可能显示旧状态。

QuickDock 改成:

onForeground → 重新评估 task policy → 更新 TaskRegistry → 恢复 activeJob → 再检查 / 重建 Float View

所以窗口看到的第一帧就是最新状态。

例如本地压缩:

SUSPENDED_POLICY → RUNNING

如果 Float View 当前 activeJob 切回它,页面会直接显示 RUNNING,而不是先闪一下 SUSPENDED。

二十五、UIAbility 生命周期和 Background Task 生命周期不能互相替代

Stage 生命周期提供:

onForeground onBackground

它告诉应用:

前后台环境变化了

Background Tasks Kit 解决的是:

某类业务是否可以在后台受约束地继续

这两个能力层级不同。

所以:

onBackground

并不等于:

开始一个后台任务

而只是触发 Policy 评估。

同样:

onForeground

也不应该直接把所有任务恢复 RUNNING。

只有状态和策略都允许的任务才恢复。

这种分层能避免生命周期回调里堆一大坨业务分支。

二十六、异常恢复以后,要检查旧 Adapter 有没有真正释放

FLOAT_SURFACE_LOST重建新显示层后,如果旧 Adapter 还持有:

timer listener surface reference

即使用户只看到一个窗口,资源也已经重复。

所以恢复完成后的检查不仅是:

listenerCount=1

还包括:

activeAdapterCount=1

当前 04 先记录 listenerCount。

05 会把:

Adapter Window Ball Timer Listener

全部纳入统一资源 Registry。

第四篇先把异常重建的边界写清楚,为下一篇治理做准备。

二十七、多任务后台场景最后做了八组验收

第一组,两个任务同时存在,Registry 数量=2。

第二组,进入后台,上传进入 RUNNING_BACKGROUND。

第三组,本地压缩进入 SUSPENDED_POLICY。

第四组,后台 107 秒后上传从 36% 到 61%。

第五组,回前台压缩从 88% 继续,不生成新 taskId。

第六组,activeJob 切换 3 次,两个任务进度各自保持。

第七组,模拟 FLOAT_SURFACE_LOST,rebuildCount=1。

第八组,重建后:

listenerCount=1 position=732/128 status=RECOVERED

所有条件都满足,04 才正式结束。

二十八、这一篇最后留下的是“后台策略”和“显示恢复”两条独立链路

回头看第四篇,最重要的不是窗口异常本身。

而是把两条链彻底分开:

后台策略链: Stage → BackgroundTaskPolicy → TaskRegistry 显示恢复链: FLOAT_SURFACE_LOST → FloatRecoveryCoordinator → Adapter rebuild

两条链最后都读同一个 TaskRegistry,但互相不替代。

这让 QuickDock 后面即使换成:

下载 上传 视频导出 模型处理

仍然可以复用相同的窗口恢复架构。

参考资料

  • HarmonyOS 7 闪控窗开发指南:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/float-view-guide
  • Background Tasks Kit:https://developer.huawei.com/consumer/cn/doc/
  • 常驻任务并发场景:https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/resident-task-overview
  • 后台视频导出最佳实践:https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-video-background-export

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询