资讯动态

HarmonyOS 7 WorkScheduler:后台任务约束漂移与发布门禁

发布时间:2026/10/8 23:09:28 来源:尧图企业网站定制
ScheduleFence的小票同步在开发机上一直正常提审前的预演却暴露了一个很尴尬的问题产品配置写的是“联网且充电时同步”运行时代码却只要求任意网络更糟的是每次账号重新登录都会再次调用startWork()设备里残留了两个语义相同、参数却不同的任务。两处代码都能编译后台回调也偶尔能进所以常规冒烟没有报警。真正危险的是“声明、策略、运行态”三份事实逐渐分叉。这个问题如果靠发布前人工翻module.json5、work-policy.json和 ArkTS迟早会漏。我把这次修复做成了一条 Hvigor 门禁构建 release 包之前先对齐能力声明、任务指纹与设备注册表再允许产物进入提交流程。一、故障不在 startWork而在三份事实不一致项目里有一项周期任务ReceiptSyncWorkworkId4102负责上传离线小票。产品策略要求NETWORK_CONNECTED CHARGING重复周期 20 分钟设备重启后继续保留。开发阶段为了方便调试有人把isCharging改成了false后来只更新了策略文件没有同步回 ArkTS。第二个偏差来自登录流程。旧代码把“登录成功”当成“必须注册一次”完全没查询已有任务。第一次登录注册正确退出后换账号再次登录仍使用同一个workId但parameters.accountScope已变化。不同系统版本对重复请求的处理细节不应被业务猜测应用需要自己保证幂等。第三个偏差在能力声明源码类名已经改成ReceiptSyncWorkrelease 的module.json5仍保留旧的InvoiceUploadWork。三个偏差组合起来就会得到“策略需要充电、运行任务不需要、发布包甚至找不到回调组件”的状态。我给本轮审计固定了批次WS-1002-0412目标只有四个数声明项 4、运行项 4、约束漂移 0、重复任务 0。全部满足时才输出RELEASE_READY。二、先建立唯一策略源不再手抄 WorkInfo修复的第一步不是加 if而是禁止页面直接拼WorkInfo。所有调度请求都从work-policy.json读出业务策略再由一个工厂转换为系统结构。策略中的NETWORK_CONNECTED是团队自己的语义名称适配层把它映射为workScheduler.NetworkType.NETWORK_TYPE_ANYCHARGING映射为isChargingtrue。下面这段工厂解决的是同一任务在不同入口被手工构造的问题。workId、Ability 名、约束、周期和参数都会进入稳定指纹账号值不直接落日志只保存不可逆的 scope 哈希。import{workScheduler}fromkit.BackgroundTasksKitexportinterfaceReceiptPolicy{workId:numberabilityName:stringrepeatMinutes:numberrequireNetwork:booleanrequireCharging:booleanpersisted:boolean}exportfunctionbuildReceiptWork(policy:ReceiptPolicy,accountScopeHash:string):workScheduler.WorkInfo{return{workId:policy.workId,bundleName:com.example.schedulefence,abilityName:policy.abilityName,networkType:policy.requireNetwork?workScheduler.NetworkType.NETWORK_TYPE_ANY:undefined,isCharging:policy.requireCharging,isRepeat:true,repeatCycleTime:policy.repeatMinutes*60*1000,isPersisted:policy.persisted,parameters:{policyVersion:2026.10.02,accountScopeHash}}}这里没有把NETWORK_TYPE_ANY解释成“没有网络限制”。在 WorkScheduler 语义里它代表需要任意可用网络真正不要求网络时才不填networkType。repeatCycleTime用毫秒传入策略层保留分钟是为了让评审者一眼看懂 20而不是从1200000反推。工厂只负责结构转换不直接调用系统接口。Hvigor 因而能在构建机加载同一策略并计算预期指纹不必模拟系统调度。三、注册前先对账重复请求不能靠系统兜底调度入口现在改成ensureReceiptWork()。它先用obtainAllWorks()获取当前注册项再按workId找目标。若不存在执行startWork()若指纹一致直接返回UNCHANGED若同 ID 参数漂移先停止旧项再注册新项。这段代码解决的是重复登录、配置升级和应用重启后的幂等问题。needCanceltrue表示旧任务不再保留重新注册后使用新策略整个过程用本地互斥锁串行避免两个登录回调同时穿过查询阶段。exportasyncfunctionensureReceiptWork(expected:workScheduler.WorkInfo):Promisestring{returnscheduleMutex.runExclusive(async(){constallWorksawaitworkScheduler.obtainAllWorks()constsameIdallWorks.filter((item)item.workIdexpected.workId)if(sameId.length0){workScheduler.startWork(expected)returnCREATED}const[current,...duplicates]sameIdfor(constduplicateofduplicates){workScheduler.stopWork(duplicate,true)}if(fingerprint(current)fingerprint(expected)duplicates.length0){returnUNCHANGED}workScheduler.stopWork(current,true)workScheduler.startWork(expected)returnduplicates.length0?DEDUPED_AND_UPDATED:UPDATED})}这里不能把startWork()当成异步 Promise 去await当前接口是同步提交请求真正执行仍由系统在条件满足后回调。提交成功也不等于任务立即运行所以 UI 只显示“已登记”不显示“正在同步”。系统拒绝非法参数时会抛错入口统一捕获并记录错误码不能用一个永远为真的布尔值假装成功。查询与写入并非系统级事务互斥锁只能保护当前进程多进程场景应把注册职责收敛到一个进程。workId也不能按账号增长这里固定4102账号切换通过参数指纹触发替换。四、onWorkStart 只领取任务onWorkStop 必须可中断过去的回调把上传逻辑写成一个长 PromiseonWorkStop()只打了一行日志。系统因超时或应用取消而触发停止时网络请求仍可能继续写本地游标下一轮就会跳过尚未确认的小票。现在ReceiptSyncWork在开始时创建取消令牌每上传一批就提交检查点停止时先取消网络再把状态持久化为INTERRUPTED。下一轮从最后确认的 receiptId 继续而不是从“已发送”数量猜位置。import{WorkSchedulerExtensionAbility,workScheduler}fromkit.BackgroundTasksKitexportdefaultclassReceiptSyncWorkextendsWorkSchedulerExtensionAbility{privatetoken:CancelToken|undefinedonWorkStart(workInfo:workScheduler.WorkInfo):void{this.token?.cancel(REPLACED)this.tokennewCancelToken()hilog.info(0x0000,ScheduleFence,onWorkStart workId${workInfo.workId}policy2026.10.02)receiptUploader.runFromCheckpoint(this.token).then((count){hilog.info(0x0000,ScheduleFence,sync completed workId4102 uploaded${count})}).catch((error:Error){hilog.error(0x0000,ScheduleFence,sync interrupted${error.message})})}onWorkStop(workInfo:workScheduler.WorkInfo):void{this.token?.cancel(SYSTEM_STOP)checkpointStore.markInterrupted(workInfo.workId)this.tokenundefinedhilog.info(0x0000,ScheduleFence,onWorkStop workId4102 checkpointsaved)}}回调有明确的系统时间边界不能当常驻服务。异步链必须捕获错误重复开始时先取消旧令牌避免两个上传器竞争同一个检查点。onWorkStop()可能来自超时也可能来自主动stopWork()。因此恢复逻辑不根据“停止原因”决定是否保存而是无条件写入最后确认位置。网络层、游标和数据库句柄都要在取消后释放如果只把 token 置空底层 socket 仍有机会完成回调。五、Hvigor 门禁同时检查声明、策略与产物运行时幂等解决不了module.json5漏改所以第二层保护放在构建期。workAudit任务读取四类信息策略文件、ArkTS 导出的规范化任务描述、release 的module.json5、解包后 HAP 中的最终声明。源码配置正确但产物被 flavor 覆盖时最后一层仍能拦住。门禁检查四项ReceiptSyncWork是否以typeworkScheduler声明srcEntry文件是否存在workId4102是否唯一网络、充电、周期与持久化策略是否和指纹一致。任何差异都输出机器可读 JSON 并让任务失败。import{hvigor,HvigorNode}fromohos/hvigorexportfunctionregisterWorkAudit(node:HvigorNode):void{node.registerTask({name:workAudit,run:(){constpolicyreadPolicy(config/work-policy.json)constmanifestreadReleaseManifest(entry/src/main/module.json5)constartifactinspectHap(entry/build/default/outputs/default/entry-default-signed.hap)constreportauditWorkDeclarations(policy,manifest,artifact)writeReport(build/reports/work-audit-WS-1002-0412.json,report)if(report.driftCount0||report.duplicateCount0){thrownewError(WORK_AUDIT_FAILED drift${report.driftCount}duplicate${report.duplicateCount})}console.info(RELEASE_READY batchWS-1002-0412 declared4 registered4 drift0 duplicate0)}})}hvigor.getRootNode().afterNodeEvaluate((node)registerWorkAudit(node))门禁不读取设备注册表。registered4指四个规范化任务均有唯一注册入口设备态去重由预演页验证避免 CI 依赖某台模拟器的残留数据。任务挂在 release 产物生成之后、上传之前。若只扫源码flavor 合并后的 HAP 仍可能变化。报告名为work-audit-WS-1002-0412.json便于和提审工单绑定。六、一次失败预演比“任务能跑”更能说明问题修复前的报告是声明项 3、运行项 4、漂移 1、重复 1状态BLOCKED。漂移字段明确指向ReceiptSyncWork.isCharging重复项指向两个workId4102的规范化指纹。没有堆栈也不要求审核同学理解 ArkTS只需要看差异表。修复后重新执行 release 流程module.json5、策略和 HAP 声明全部对齐模拟器先注入一个旧指纹任务ensureReceiptWork()返回DEDUPED_AND_UPDATED再次调用返回UNCHANGED。这两次结果很关键第一次证明能恢复历史脏状态第二次证明正常启动不会反复停止和注册。最终页面展示批次WS-1002-0412、任务ReceiptSyncWork、workId 4102、约束NETWORK_CONNECTED CHARGING、声明4 / 4、漂移0、重复0底部状态为RELEASE_READY构建退出码0。时间统一为 04:12。七、这类门禁最容易留下的假安全感只比较 Ability 名称不够。srcEntry、type、任务 ID、重复周期和约束都可能各自漂移最好把关键字段排序后做稳定指纹并在报告里同时输出人类可读值。只检查startWork()调用次数也不够。同一个入口可能因多次登录被重复执行而多个入口也可能在互斥条件下只运行一次。门禁检查静态唯一性运行时检查设备实际状态两者缺一不可。不要在onWorkStop()里立即重新注册任务。系统停止任务可能正是因为条件不满足或执行超时回调里无条件重启会形成重试风暴。重复任务按既有调度策略等待下一轮一次性任务若需要业务重试应记录重试次数和退避时间再由统一入口登记。也不要把“需要联网”误写成“必须 Wi-Fi”。策略层使用产品语义适配层才映射系统枚举。将枚举值直接散落在页面、Extension 和脚本中下一次策略调整又会制造三份事实。最后审计页面本身不能成为生产控制台。正式构建只展示摘要不提供清空全部任务或任意修改约束的按钮调试操作放在受控 flavor 中避免运维便利变成用户侧风险。八、把提审证据变成工程结果后台任务的问题往往不是“API 不会调”而是代码运行数月后账号切换、策略升级、flavor 合并和生命周期中断逐渐叠加。靠一次成功回调无法证明这些边界都安全。这次留下的不是一个更复杂的封装而是一条可复核链路work-policy.json定义业务约束工厂生成唯一WorkInfo注册器清理重复与漂移Extension 在停止时保存检查点Hvigor 对 release 产物做最终审计。任意一层不一致状态都停在BLOCKED只有声明 4、运行项 4、漂移 0、重复 0 才进入RELEASE_READY。相比“提审前再人工看一遍”机器门禁并不聪明但它不会忘记。下次新增后台任务时开发者必须同时补策略、声明、注册入口和停止语义否则构建直接失败。这正是发布工程需要的确定性。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑