HarmonyOS 通知与后台任务:WorkScheduler 机制深度解析

> 读完本文,你将掌握 WorkScheduler 的调度原理、条件触发机制,以及在实际业务中如何用它替代常驻进程完成可靠的后台任务。
> 适用版本:HarmonyOS NEXT / API 12+
> 阅读时长:约 18 分钟
一、从"常驻后台"到"条件驱动":为什么需要 WorkScheduler
许多 Android 开发者迁移到鸿蒙时,第一反应是起一个 Service 常驻后台轮询。但 HarmonyOS 对后台资源管控极为严格------一旦应用切到后台,普通 UIAbility 的 JS 线程会在数秒内被挂起,长时任务若无声明则会被系统杀掉。
HarmonyOS 的后台任务体系分为三层:
┌──────────────────────────────────────────────┐
│ 短时任务(requestSuspendDelay) │ 前台→后台过渡期,最多3分钟
├──────────────────────────────────────────────┤
│ 长时任务(startBackgroundRunning) │ 有UI通知 + 特定类型声明
├──────────────────────────────────────────────┤
│ 延迟任务 WorkScheduler │ ← 本文重点,条件触发,无UI通知
└──────────────────────────────────────────────┘
WorkScheduler 是专为"不紧急、可延迟、资源充裕时执行"场景设计的后台调度框架。典型场景:
-
充电时自动上传日志 / 崩溃报告
-
网络连接时同步本地缓存到服务器
-
低电量时暂停非核心任务,电量充足后恢复
-
定期(每小时/每天)批量处理数据
二、WorkScheduler 核心架构
2.1 调度链路全景
Application System Service Extension
─────────────── ───────────────── ──────────────────
workScheduler.startWork()
│ WorkInfo(条件集合)
▼
WorkSchedulerService ◄───── 条件监听器池 ─────────────────
│ (网络/充电/电量/存储/温度)
│ 所有条件满足
▼
触发回调 ─────────────────────────────────────────────► WorkSchedulerExtensionAbility
onWork(workInfo)
│
业务逻辑(最长120秒)
│
workScheduler.isLastWorkTimeout()
│
finishWork()
2.2 WorkInfo 条件字段
| 字段 | 类型 | 说明 |
|------|------|------|
| workId | number | 任务唯一 ID(同一 Bundle 下唯一) |
| bundleName | string | 包名(必填) |
| abilityName | string | ExtensionAbility 类名(必填) |
| networkType | NetworkType | 网络类型:ANY / UNMETERED / NOT_ROAMING |
| isCharging | boolean | 是否要求充电中 |
| chargerType | ChargingType | USB / AC / WIRELESS |
| batteryLevel | number | 最低电量百分比(0~100) |
| batteryStatus | BatteryStatus | LOW / OKAY / CHARGING |
| storageRequest | StorageRequest | LOW_THRESHOLD / NOT_LOW / FREE |
| repeatCycleTime | number | 重复间隔(毫秒,最小20分钟) |
| isRepeat | boolean | 是否周期重复 |
| isPersisted | boolean | 重启后是否恢复 |
三、WorkSchedulerExtensionAbility 实战
3.1 模块配置
module.json5 (必须声明,否则系统拒绝调度):
{
"extensionAbilities": [
{
"name": "DataSyncWorker",
"srcEntry": "./ets/workers/DataSyncWorker.ets",
"type": "workScheduler",
"label": "$string:worker_label"
}
]
}
3.2 ExtensionAbility 实现
// ets/workers/DataSyncWorker.ets
import WorkSchedulerExtensionAbility from '@ohos.WorkSchedulerExtensionAbility';
import workScheduler from '@ohos.resourceschedule.workScheduler';
import hilog from '@ohos.hilog';
const TAG = 'DataSyncWorker';
const DOMAIN = 0xF001;
export default class DataSyncWorker extends WorkSchedulerExtensionAbility {
// 系统调度触发时回调
onWork(work: workScheduler.WorkInfo): void {
hilog.info(DOMAIN, TAG, `onWork triggered, workId=${work.workId}`);
// 关键:检查上次是否超时
const isTimeout = workScheduler.isLastWorkTimeOut(work.workId);
if (isTimeout) {
hilog.warn(DOMAIN, TAG, 'Last work was timed out, do recovery logic');
this.doRecovery(work);
return;
}
this.doSync(work);
}
// 任务停止时回调
onWorkStop(work: workScheduler.WorkInfo): void {
hilog.info(DOMAIN, TAG, `onWorkStop, workId=${work.workId}`);
this.saveCheckpoint();
}
private async doSync(work: workScheduler.WorkInfo): Promise
{
try {
await this.uploadPendingLogs();
hilog.info(DOMAIN, TAG, 'Sync finished successfully');
} catch (e) {
hilog.error(DOMAIN, TAG, `Sync failed: ${JSON.stringify(e)}`);
} finally {
// 任务完成后必须调用 stopWork
workScheduler.stopWork(work, false);
}
}
private async uploadPendingLogs(): Promise
{
// 实际网络请求逻辑
}
private doRecovery(work: workScheduler.WorkInfo): void {
workScheduler.stopWork(work, false);
}
private saveCheckpoint(): void {
// 用 Preferences 保存进度
}
}
3.3 注册任务(调用方)
// ets/pages/SettingsPage.ets
import workScheduler from '@ohos.resourceschedule.workScheduler';
import bundleManager from '@ohos.bundle.bundleManager';
async function registerSyncWork(): Promise
{
const bundleInfo = await bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT
);
const workInfo: workScheduler.WorkInfo = {
workId: 1001,
bundleName: bundleInfo.name,
abilityName: 'DataSyncWorker',
// 条件:有网络 + 充电中
networkType: workScheduler.NetworkType.NETWORK_TYPE_ANY,
isCharging: true,
// 每2小时重复执行
isRepeat: true,
repeatCycleTime: 2 * 60 * 60 * 1000,
// 重启后恢复
isPersisted: true,
};
try {
workScheduler.startWork(workInfo);
console.info('WorkScheduler task registered successfully');
} catch (e) {
// 9700001 内存不足 / 9700002 超过最大任务数 / 9700003 条件无效
console.error(`startWork failed: code=${e.code}, msg=${e.message}`);
}
}
四、错误写法 → 问题 → 正确写法
坑点一:忘记调用 stopWork
// ❌ 错误写法:任务完成后不通知系统
onWork(work: workScheduler.WorkInfo): void {
doHeavyWork();
// 没有 stopWork
}
问题 :系统等待到120秒超时才强制中断,下次调度时 isLastWorkTimeOut 返回 true。
// ✅ 正确写法
onWork(work: workScheduler.WorkInfo): void {
try {
doHeavyWork();
} finally {
workScheduler.stopWork(work, false);
}
}
坑点二:在 UIAbility 线程直接做网络请求
// ❌ 错误写法
onBackground(): void {
fetch('https://api.example.com/sync').then(...); // 可能被系统挂起
}
问题 :UIAbility 进入后台后 JS 线程随时可能被冻结,Promise 链不会继续执行。
// ✅ 正确写法:onBackground 只注册 WorkScheduler 任务
onBackground(): void {
registerSyncWork();
}
坑点三:repeatCycleTime 设置过小
// ❌ 错误写法
repeatCycleTime: 60 * 1000, // 1分钟,低于最小值20分钟
// ✅ 正确写法
repeatCycleTime: 20 * 60 * 1000, // 最小20分钟
问题 :传入小于最小值, startWork 抛出 9700003 - Work condition is invalid。
五、最佳实践
5.1 任务幂等性设计
做法 :所有 WorkScheduler 任务必须设计为幂等操作。 原因 :系统可能在条件反复满足时多次触发同一任务,或超时后重新调度。 不这样做 :日志重复上传、数据库重复写入等问题频繁出现。
5.2 利用 isPersisted 保证崩溃恢复
做法 :对关键业务任务设置 isPersisted: true。 原因 :设备重启后系统会自动恢复已注册的持久化任务,无需应用重新启动。 不这样做 :重启后任务丢失,需等用户手动打开应用才能恢复。
5.3 合理拆分任务粒度
做法 :将耗时超过60秒的大任务拆分为多个子任务,通过 Preferences 记录进度。 原因 :WorkScheduler 单次执行上限120秒,拆分后即使超时也只损失一个子任务。 不这样做 :系统强制中断后半途而废,需要完整重做。
5.4 条件精准化减少无效触发
做法 :为任务指定最精准的条件组合,避免只设 networkType: ANY 等宽泛条件。 原因 :条件越宽泛,触发频率越高,电量消耗越大,也更容易被系统限流。 不这样做 :系统检测到频繁无效唤醒会降低调度优先级,导致关键任务被延迟。
六、常见坑点速查
坑点一:module.json5 中 type 写错
-
现象:任务从不触发,Logcat 无报错
-
原因 :
type写成了"service"而非"workScheduler" -
复现 :注册任务后
onWork永不回调 -
解决 :改为
type: "workScheduler",重新安装 HAP
坑点二:onWork 中执行同步耗时操作
-
现象:应用出现 ANR,系统强制杀死进程
-
原因 :
onWork在 Extension 主线程执行,长时间同步阻塞事件循环 -
复现:在 onWork 中加大量同步计算
-
解决 :使用
async/await+ Promise,将 I/O 操作异步化
坑点三:超过最大任务数
-
现象 :
startWork返回错误码9700002 -
原因:单个应用最多同时存在10个 WorkScheduler 任务
-
复现 :循环调用
startWork超过10次 -
解决 :注册前调用
workScheduler.getWorkStatus(workId)检查,或先停止旧任务
七、总结
-
WorkScheduler 是鸿蒙延迟任务首选,适合充电/网络/电量条件触发的非紧急后台工作
-
必须在 module.json5 声明
type: "workScheduler",由 WorkSchedulerExtensionAbility 承载 -
每次执行完成后必须调用
stopWork,否则系统等待超时后视为失败 -
单次执行上限120秒,重复间隔最小20分钟,最多同时注册10个任务
-
任务需设计为幂等操作,用
isPersisted保证重启后自动恢复
> 核心结论:用条件驱动替代常驻后台,WorkScheduler 让延迟任务既可靠又省电。
参考资料
-
OpenHarmony 源码路径:
base/resourceschedule/work_scheduler/