跑着跑着进程没了,鸿蒙后台任务的保活边界
用鸿蒙做一个带下载功能的应用,或者是需要后台播放音频的 App,很多人的第一反应是:在主线程写个异步函数,或者开个 Worker,然后把 App 切到后台,以为它能一直在那跑。
现实是骨感的。App 切到后台不出几秒钟,你打开 HiLog 一看,整个进程都被系统挂起(Suspend)甚至杀死了。下载进度条卡住不动,音乐也停了。用户觉得这 App 是个半成品,你还在那里纳闷:“代码明明没报错,为什么就是跑不完?”
这根本不是代码报错的问题,这是系统为了省电和保证前台流畅度,对后台任务进行了极其严格的管控。鸿蒙不养闲人,也不养“闲进程”。
识别短时任务与长时任务的边界
系统允许你干后台的活,但你必须“打报告”,也就是申请后台任务。鸿蒙里主要分两种:短时任务(Transient Task)和长时任务(Continuous Task)。
短时任务适合那种“很快就结束”的任务,比如保存几张图片、发送一个短小的事件日志。单次申请最长 3 分钟。如果超过时间还没结束,系统会直接下发违规通知,并可能回收你的应用进程。
typescript
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { wantAgent, WantAgent } from '@kit.AbilityKit';
async function requestShortTask(context: Context) {
const wantAgentInfo: wantAgent.WantAgentInfo = {
wants: [{
bundleName: 'com.example.app',
abilityName: 'EntryAbility'
}],
operationType: wantAgent.OperationType.START_ABILITY,
requestCode: 0
};
const agent = await wantAgent.getWantAgent(wantAgentInfo);
// 申请短时任务,延迟 3 秒挂起(必须有 reason)
await backgroundTaskManager.requestSuspendDelay(
'保存用户配置',
agent,
(reason) => {
// 如果走到这里,说明 3 分钟到了,必须取消任务并清理
console.error(`短时任务超时被取消,原因: ${reason}`);
}
);
}
注意 requestSuspendDelay 里的第一个参数,这是给系统看的“理由”。如果你的理由写得含糊其辞,或者频繁申请又很快释放,会被系统的频控机制盯上,后续再申请就难了。
下载与音频必须申请长时任务
如果你的任务是下载一个几百兆的文件,或者是后台播放音乐,短时任务绝对不够用。你必须申请长时任务(Continuous Task)。
长时任务必须在 module.json5 里先声明权限和后台模式,否则代码里调 API 会直接抛错。
json5
{
"module": {
"abilities": [
{
"name": "EntryAbility",
"backgroundModes": ["dataTransfer", "audioPlayback"]
}
],
"requestPermissions": [
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
}
]
}
}
配置了 backgroundModes 之后,还要在业务代码里真正启动这个长时任务。这里最容易错的是:启动时机不对。
typescript
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
async function startDownloadTask() {
try {
const context = getContext(this) as common.UIAbilityContext;
// 启动长时任务
await backgroundTaskManager.startBackgroundRunning(
context,
backgroundTaskManager.BackgroundMode.DATA_TRANSFER,
// 这里传入 WantAgent,用户点击通知栏可以回到应用
agent
);
// 开始真正的下载逻辑
startNetworkDownload();
} catch (e) {
console.error(`长时任务启动失败: ${e.message}`);
}
}
关键语句是 startBackgroundRunning。这个 API 一执行,系统通知栏会默默出现一个“应用正在后台运行”的提示。很多开发者觉得这个提示碍眼,偷偷摸摸地在不该用的时候用,结果被应用市场审核驳回。
长时任务最大的坑在于:不要以为申请了就可以无限期跑了。如果系统检测到你申请的是 dataTransfer(数据传输)模式,但你的实际动作里根本没有网络请求和文件读写,或者在后台偷偷拉起了一个不可见的 Activity,系统会在监控到行为不匹配后,无情地把你干掉。
周期性的同步改用 WorkScheduler
如果一个任务的周期很长,比如“每天凌晨同步一次数据”,就不要死磕长时任务了,那是在浪费电,也容易被系统风控。
这种场景用 WorkScheduler,也就是延迟任务。它是系统级的任务调度器,系统会综合考虑电量、网络、休眠状态,在最合适的时候把你的任务拉起来。
typescript
import { workScheduler } from '@kit.BackgroundTasksKit';
const work: workScheduler.WorkInfo = {
workId: 1001,
bundleName: 'com.example.app',
abilityName: 'SyncAbility', // 必须是 WorkSchedulerExtensionAbility
isRepeat: true,
repeatCycleTime: 24 * 60 * 60 * 1000, // 24小时
networkType: workScheduler.NetworkType.NETWORK_TYPE_WIFI,
isCharging: true // 充电时才执行
};
workScheduler.startWork(work);
repeatCycleTime 是“最小重复间隔”,不代表一到 24 小时绝对执行。如果手机没连着 WiFi,或者没在充电,系统绝不会为了你一个后台同步把用户电量耗光。
不管是短时、长时还是延迟任务,核心思想只有一个:遵守系统的规矩。该申请申请,该释放释放,任务类型和声明保持一致,你的应用才能在后台安稳地存活下去。
更多推荐
所有评论(0)