Harmony os------长时任务(Continuous Task,ArkTS)
一句话: 前台/后台长期跑、用户能感知的业务(音乐、导航、下载、录屏...) → 必须申请「长时任务」,否则一退后台就被挂起/静音/杀进程。
1. 长时任务干嘛用?
典型场景(用户能明显感觉到你还在干活):
- 🎵 后台播放音乐 / 播放视频 / 投屏播放
- 📡 导航、实时定位
- 🎤 录音 / 录屏退后台继续录
- 📤 后台长时间上传 / 下载
- 🔵 蓝牙传文件、车钥匙
- 🔗 多设备互联、分布式投屏
- 📞 音视频通话(VoIP)
- 🛡 PC/2in1 上的杀毒、DLP 等持续计算任务
只要你退到后台还要长时间跑 ,并且是用户可感知的业务 → 就要配套申请对应类型的长时任务。
2. 长时任务类型总览(一定要记住这张表)
| 类型常量 | 配置项字段 | 典型场景举例 |
|---|---|---|
DATA_TRANSFER |
dataTransfer |
浏览器/网盘后台上传下载(非托管下载) |
AUDIO_PLAYBACK |
audioPlayback |
音视频播放、投播、游戏音频等(需接入 AVSession) |
AUDIO_RECORDING |
audioRecording |
录音、录屏退后台 |
LOCATION |
location |
后台定位 / 导航 |
BLUETOOTH_INTERACTION |
bluetoothInteraction |
蓝牙传文件、车钥匙 |
MULTI_DEVICE_CONNECTION |
multiDeviceConnection |
分布式业务、多设备互联、投播(元服务也可用) |
VOIP |
voip |
音视频通话退后台 |
TASK_KEEPING |
taskKeeping |
杀毒、DLP 等计算任务(PC/2in1 为主,需特权 ACL) |
一些特别说明(高频坑点)
-
DATA_TRANSFER
-
如果用「上传/下载代理接口托管给系统」,,那你申请
DATA_TRANSFER也没用,退后台还是会挂起。 -
如果自己实现传输逻辑 → 可以申请
DATA_TRANSFER。 -
必须定期更新传输进度(首次更新不能超过 10 分钟),否则长时任务会被系统取消。
进度要通过实况窗通知 更新,详见
startBackgroundRunning里的示例。
-
-
AUDIO_PLAYBACK
-
媒体类型(
STREAM_USAGE_MUSIC/MOVIE/AUDIOBOOK/GAME)后台播放 → 必须:- 接入 AVSession
- 申请
AUDIO_PLAYBACK长时任务
-
否则:
- 退后台会被 静音 + 冻结,只有切回前台才恢复。
-
从 API 20 起:
- 如果没接入 AVSession 但申请了
AUDIO_PLAYBACK→ 由后台任务模块在通知栏显示通知。 - 如果接入了 AVSession → 通知改由 AVSession 发。
- 如果没接入 AVSession 但申请了
-
3. 约束 & 系统管控逻辑
3.1 谁能申请?
-
Stage 模型 :只能在 UIAbility 里申请。
-
FA 模型 :只能在 ServiceAbility 里申请。
-
支持:
- 当前应用自己申请
- 跨设备 / 跨应用申请 → 只对系统应用开放。
3.2 数量限制(版本差异)
-
API 21 起:
- 一个 UIAbility 同时最多可申请 10 个长时任务。
- 用的是新版接口:
startBackgroundRunning(context, request: ContinuousTaskRequest)。
-
API 20 及以前:
- 一个 UIAbility / ServiceAbility 同一时刻只能有一个长时任务。
- 如果一个应用需要多个长时任务 → 得开多个 UIAbility 来申请。
但注意:只要其中一个 UIAbility 申请了长时任务,整个应用下的进程都不会被挂起。
3.3 系统何时会「动刀」管你的应用?
申请长时任务 ≠ 永久豁免,系统会做一致性校验:
-
申请了但不干对应的活
- 例如:申请
AUDIO_PLAYBACK,但实际没有播放任何音频/视频。 → 应用退后台时,会被挂起。
- 例如:申请
-
业务类型和申请不一致
- 只申请了
AUDIO_PLAYBACK,实际在后台还做录音(应该申请AUDIO_RECORDING)。 → 系统判定违规,同样会挂起。
- 只申请了
-
业务已经结束还不取消
- 比如用户手动暂停音乐,你长时任务还挂着不关。 → 系统检测到业务不再发生,会挂起。
-
后台负载过高
- 长时任务属于「用户可感知+资源有限」模式,系统会根据典型负载审查。
- 如果进程长期高负载,不符合该类型合理行为 → 可能被挂起或直接终止。
-
后台播放音频时被打断
- 被系统或其他事件打断时长时任务会被停。
- 音频重新播放 → 你要重新申请长时任务。
重点:任务结束一定要主动 stop,不要挨系统的刀。
4. 通知栏 & 用户控制权
-
申请长时任务成功后:
- 通知栏会显示「正在运行录制任务 / 播放任务...」之类的通知。
- 用户手动删除通知 → 系统会自动停止这条长时任务。
-
对音频播放:
- 停止长时任务时,你也要把音频流停掉,不然系统可能会强制终止应用。
5. 关键 API 总览(backgroundTaskManager)
模块:
@kit.BackgroundTasksKit+@kit.AbilityKit中的wantAgent
5.1 旧版:一个 UIAbility 一个任务
php
startBackgroundRunning(context: Context, bgMode: BackgroundMode, wantAgent: WantAgent): Promise<void>;
stopBackgroundRunning(context: Context): Promise<void>;
bgMode是一个字符串或数组(如"audioRecording"、"audioPlayback"...)- 用于 API 20 及以前的「单任务模式」。
5.2 新版:支持多个连续任务(API 21)
php
startBackgroundRunning(context: Context, request: ContinuousTaskRequest): Promise<ContinuousTaskNotification>;
stopBackgroundRunning(context: Context, continuousTaskId: number): Promise<void>;
ContinuousTaskRequest:可以一次性配置多个类型,比如:["audioPlayback", "location"]- 返回
ContinuousTaskNotification,里面有notificationId等信息。
5.3 监听长时任务被系统取消
javascript
backgroundTaskManager.on("continuousTaskCancel", (info) => {
console.info('Canceled task id: ' + info.id);
console.info('Reason: ' + info.reason);
});
backgroundTaskManager.off("continuousTaskCancel", callback); // 取消监听
场景:比如被系统判断不符合规则,或用户清掉通知栏 → 会触发这个回调。
6. 录制场景示例(录音/录屏)
6.1 配置权限 & 后台模式(module.json5)
json
"module": {
"abilities": [
{
// ... 你的 EntryAbility / MainAbility ...
"backgroundModes": [
"audioRecording",
"bluetoothInteraction",
"audioPlayback"
]
}
]
}
还要在工程里声明权限:ohos.permission.KEEP_BACKGROUND_RUNNING(系统权限配置处)。
6.2 关键代码拆解(then 写法)
按钮点击 → 申请录制长时任务 → 通知栏出一条"正在录制" → 点击另一按钮取消。
typescript
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { wantAgent, WantAgent } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
function continuousTaskCancelCallback(info: backgroundTaskManager.ContinuousTaskCancelInfo) {
console.info('ContinuousTask canceled, id: ' + info.id);
console.info('Reason: ' + info.reason);
}
@Entry
@Component
struct Index {
@State message: string = 'ContinuousTask';
private context: Context | undefined = this.getUIContext().getHostContext();
// 注册任务取消回调
OnContinuousTaskCancel() {
try {
backgroundTaskManager.on('continuousTaskCancel', continuousTaskCancelCallback);
} catch (error) {
console.error(`OnContinuousTaskCancel failed. code: ${(error as BusinessError).code}, msg: ${(error as BusinessError).message}`);
}
}
// 取消注册
OffContinuousTaskCancel() {
try {
backgroundTaskManager.off('continuousTaskCancel', continuousTaskCancelCallback);
} catch (error) {
console.error(`OffContinuousTaskCancel failed. code: ${(error as BusinessError).code}, msg: ${(error as BusinessError).message}`);
}
}
// 申请长时任务(录制类)
startContinuousTask() {
const info: wantAgent.WantAgentInfo = {
wants: [
{
bundleName: 'com.example.myapplication',
abilityName: 'MainAbility'
}
],
actionType: wantAgent.OperationType.START_ABILITY,
requestCode: 0,
actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG],
};
wantAgent.getWantAgent(info).then((agent: WantAgent) => {
const list: Array<string> = ['audioRecording']; // 录制长时任务
backgroundTaskManager.startBackgroundRunning(this.context!, list, agent)
.then(() => {
console.info('startBackgroundRunning succeeded');
// 这里开始真正的录制逻辑(录音/录屏等)
})
.catch((err: BusinessError) => {
console.error(`startBackgroundRunning failed. code: ${err.code}, msg: ${err.message}`);
});
}).catch((err: BusinessError) => {
console.error(`getWantAgent failed. code: ${err.code}, msg: ${err.message}`);
});
}
// 取消长时任务
stopContinuousTask() {
backgroundTaskManager.stopBackgroundRunning(this.context!)
.then(() => {
console.info('stopBackgroundRunning succeeded');
// 同时要停止录制逻辑
})
.catch((err: BusinessError) => {
console.error(`stopBackgroundRunning failed. code: ${err.code}, msg: ${err.message}`);
});
}
build() {
Row() {
Column() {
Text('Index').fontSize(50).fontWeight(FontWeight.Bold)
Button(() => Text('申请长时任务').fontSize(25).fontWeight(FontWeight.Bold))
.type(ButtonType.Capsule)
.margin({ top: 10 })
.backgroundColor('#0D9FFB')
.width(250).height(40)
.onClick(() => this.startContinuousTask())
Button(() => Text('取消长时任务').fontSize(25).fontWeight(FontWeight.Bold))
.type(ButtonType.Capsule)
.margin({ top: 10 })
.backgroundColor('#0D9FFB')
.width(250).height(40)
.onClick(() => this.stopContinuousTask())
Button(() => Text('注册长时任务取消回调').fontSize(25).fontWeight(FontWeight.Bold))
.type(ButtonType.Capsule)
.margin({ top: 10 })
.backgroundColor('#0D9FFB')
.width(250).height(40)
.onClick(() => this.OnContinuousTaskCancel())
Button(() => Text('取消注册长时任务取消回调').fontSize(25).fontWeight(FontWeight.Bold))
.type(ButtonType.Capsule)
.margin({ top: 10 })
.backgroundColor('#0D9FFB')
.width(250).height(40)
.onClick(() => this.OffContinuousTaskCancel())
}.width('100%')
}.height('100%')
}
}
7. 和「短时任务」的对比小结(帮你一起记)
| 对比点 | 短时任务(requestSuspendDelay) | 长时任务(startBackgroundRunning) |
|---|---|---|
| 用途 | 应用即将被挂起前,争取一点点时间做收尾 | 退后台后长期持续跑业务(音乐 / 导航 / 下载 / 通话等) |
| 时间尺度 | 单次最多几分钟,总配额 10 分钟 / 天左右 | 可长时间运行,只要任务真实且类型匹配 |
| 用户可感知 | 通常不可感知(只是你在做收尾) | 用户明显能感知(播放、导航、下载、通话...) |
| 通知栏 | 无固定通知要求,主要系统管理 | 必须有对应通知(或由 AVSession 管),用户可手动停止 |
| 典型触发点 | 前台或 onBackground 回调时 |
一般在用户开始某个长期任务时(播放按钮、开始导航等) |
| 核心风险 | 超时不 cancel 会被管控 | 申请类型与业务不一致 / 业务已结束不 cancel 会被管控 |