退后台三分钟就被冻结,HarmonyOS 7 后台任务的语义选型
先看一段很多人写过的代码。应用退到后台,你启动一个 setInterval 或 Worker 去轮询接口、上传日志,心想“线程还活着,进程应该不会被挂起”。结果切出去不到几秒,网络请求断了,回调不执行了,日志只停在退后台那一行。
问题不在线程有没有跑起来,而在你把“后台线程”当成了“后台运行资格”。HarmonyOS 的后台管控不看你内部开了多少线程,它只看你有没有向系统申请对应的后台任务类型,以及申请的类型和实际业务是否匹配。Worker、TaskPool、setInterval 本身都不具备让进程在后台持续存活的资格,它们只是执行手段,不是保活凭据。
四类后台任务,先按语义选,不要按“能不能一直跑”选
Background Tasks Kit 把后台能力拆成四种:短时任务、长时任务、延迟任务、代理提醒。它们的适用边界不是“谁更强”,而是“你的业务语义更接近哪一种”。
短时任务解决的是“退后台之后还需要一小段收尾时间”,比如保存草稿、发一条消息、处理一个小文件。系统给你一个有限的时间窗口,在这个窗口内进程不被挂起,但窗口用完必须主动取消。单次最长约三分钟,全天总配额默认十分钟,低电量时单次会缩到一分钟,同一时刻最多并行三个。
长时任务解决的是“用户明确可感知、需要持续运行”的业务:音频播放、导航、录音、蓝牙传输、多设备连接。申请成功后进程在后台不被挂起,但通知栏会常驻一条通知,而且系统会持续校验你申请的类型和实际行为是否一致。
延迟任务解决的是“不着急、条件满足时再跑”的轻量工作:日志上报、缓存清理、条件同步。它由系统挑时机拉起一个 Extension 进程执行,不保证准点,单次回调最长两分钟。
代理提醒解决的是“到点提醒用户”,闹钟、倒计时、日历事件。它的特点是应用进程死了提醒照样响,因为调度和执行都在系统侧,不依赖你的应用活着。
选型的顺序可以反过来问:能交给代理提醒的,就不要用延迟任务;能延迟到条件满足再跑的,就不要占短时配额;短时窗口确实扛不住的持续业务,才轮到长时任务。把长时任务当成“一直挂着省事”的手段,是最容易触发系统一致性校验的写法。
短时任务的配额是预算,不是承诺
requestSuspendDelay() 返回的延迟信息里有一个 actualDelayTime,它是系统根据当前电量、内存和配额余量实际批给你的时长。你如果把它当成固定三分钟来安排任务,低电量场景下就会被打断。更关键的是,任务做完不调 cancelSuspendDelay(),系统会继续计时、继续消耗当天剩余配额,直到配额耗尽,后续连申请都申请不下来。
下面的写法把超时兜底和主动取消放在同一个管理器里,保证无论任务正常结束还是即将超时,都不会把配额留在那里空转。
typescript
import { backgroundTaskManager } from ‘@kit.BackgroundTasksKit’;
import { BusinessError } from ‘@kit.BasicServicesKit’;
class TransientTaskGuard {
private taskId: number = -1;
request(onTimeout: () => void): boolean {
try {
const info = backgroundTaskManager.requestSuspendDelay(‘save-draft’, () => {
// 系统在即将超时前回调,必须在这里主动收尾并取消
onTimeout();
this.cancel();
});
this.taskId = info.requestId;
console.info(actual delay: ${info.actualDelayTime}ms);
return true;
} catch (err) {
const e = err as BusinessError;
// 201 无权限 / 18600001 已超限
console.error(request failed: ${e.code});
return false;
}
}
cancel(): void {
if (this.taskId >= 0) {
backgroundTaskManager.cancelSuspendDelay(this.taskId);
this.taskId = -1;
}
}
}
actualDelayTime 那行日志不是装饰,它告诉你这次实际拿到了多少毫秒。超时回调里先 onTimeout() 做业务收尾,再 cancel() 释放配额,顺序不能反——先取消的话,收尾还没跑完就可能被系统挂起。任务正常完成时也要在 finally 或业务结束点调 cancel(),不能只依赖超时回调。
长时任务不是保活外挂,类型必须和真实行为对上
长时任务的申请入口是 startBackgroundRunning(),但它前面有两道硬门槛。module.json5 里必须声明 ohos.permission.KEEP_BACKGROUND_RUNNING,并且在对应 UIAbility 的 backgroundModes 中写出你实际使用的类型,比如 audioPlayback、location、dataTransfer。这个权限是 restricted 级别,上架前需要单独审核。
申请成功也不等于进了保险箱。系统会校验申请的类型和实际行为是否一致:申请了 audioPlayback 但没有音频在播,申请了 location 却没有在采集定位,或者业务已经结束但长时任务没有取消,系统都会挂起甚至终止进程。音频后台播放还有一个额外的组合条件:必须接入 AVSession,否则退后台后系统会直接把音频静音并冻结,长时任务也救不回来。
typescript
import { backgroundTaskManager } from ‘@kit.BackgroundTasksKit’;
import { wantAgent, WantAgent } from ‘@kit.AbilityKit’;
async function startAudioTask(ctx: Context): Promise<number | undefined> {
const info: wantAgent.WantAgentInfo = {
wants: [{ bundleName: ‘com.example.app’, abilityName: ‘EntryAbility’ }],
actionType: wantAgent.OperationType.START_ABILITY,
requestCode: 0,
actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};
const agent: WantAgent = await wantAgent.getWantAgent(info);
try {
const res = await backgroundTaskManager.startBackgroundRunning(
ctx, [‘audioPlayback’], agent
);
// 记下 continuousTaskId,取消时要用
return res.continuousTaskId;
} catch (err) {
// 9800005 类型与业务不匹配 / 16000007 并发冲突
return undefined;
}
}
启动长时任务之后,你还需要监听 continuousTaskCancel 事件。用户在通知栏把那条常驻通知划掉,或者系统因为资源回收、业务不匹配主动摘掉任务,都会走这个回调。回调里要做的不是“重新申请”,而是先停掉真实业务、清理状态,等下一次用户触发业务时再走正常申请流程。
任务结束不取消,和没申请一样危险
短时任务的规则写得很直白:任务完成后不主动取消,系统会继续计时并消耗配额;超时不取消,系统会对应用进行管控,包括挂起和终止进程。长时任务同理,业务已经停止但 stopBackgroundRunning() 没有调用,系统的一致性校验会把它判定为“挂着不用”,进程被挂起只是时间问题。
排查时可以按一个很短的清单过一遍:退后台几秒就停,先看有没有申请任何后台任务;申请了长时任务但很快被取消,看真实业务有没有在跑、类型有没有写错;音频后台没声,检查 AVSession 接没接;延迟任务一直不执行,看应用活跃分组和触发条件是不是太窄;短时配额很快用完,第一嫌疑永远是任务结束没 cancel()。
延迟任务还有一个容易被忽略的边界:它的 repeatCycleTime 最小间隔是两小时,而且实际执行频率还受应用活跃分组约束——活跃分组两小时,经常使用四小时,常用二十四小时,极少使用四十八小时,受限或从未使用的分组直接不调度。把延迟任务当精确定时器用,代码本身不会报错,但业务节奏会永远对不上。Extension 里也不能调用 backgroundTaskManager、camera、audio、media 这些接口,想把播放或相机逻辑顺手塞进延迟任务里,只会在运行时撞墙。
综上所述,后台任务的核心不是“找到一种让进程别死的手段”,而是先把业务翻译成系统能理解的语义:一小段收尾走短时,用户可感知的持续行为走长时并接受一致性校验,不急的轻量工作交给延迟调度,到点提醒用户就交给代理提醒。申请之后,任务结束就取消这条纪律比选型本身更容易被忽略,而它恰恰是配额和进程管控最先动手的地方。下一次写后台逻辑之前,先问一句:我这个业务,在系统眼里到底属于哪一种任务?
更多推荐


所有评论(0)