在鸿蒙HarmonyOS系统中当应用退至后台如返回主界面、锁屏或切换应用后为了降低设备耗电并保障系统流畅度系统默认会在约三秒后对应用进程进行管控挂起或终止。如果应用需要在后台执行用户可感知的长时任务如音视频播放、屏幕录制、导航等必须申请长时任务Continuous Task来防止进程被挂起。以下是申请长时任务的完整开发流程与实战代码一、 配置文件准备module.json5申请长时任务前必须在配置文件中声明后台运行权限并指定长时任务的类型。module: { // 1. 声明后台运行权限 requestPermissions: [ { name: ohos.permission.KEEP_BACKGROUND_RUNNING, reason: $string:reason_background, // 需向用户说明后台运行的原因 usedScene: { abilities: [EntryAbility], when: always } } ], abilities: [ { name: EntryAbility, // 2. 声明后台模式类型以音视频播放为例 backgroundModes: [audioPlayback] } ] }注常见的后台模式包括audioPlayback音视频播放、audioRecording录制、location定位导航、bluetoothInteraction蓝牙交互等。二、 核心代码实现长时任务的生命周期应包裹住整个后台业务。在业务开始前申请在业务结束后及时取消。import { backgroundTaskManager } from kit.BackgroundTasksKit; import { wantAgent, WantAgent } from kit.AbilityKit; import { BusinessError } from kit.BasicServicesKit; Entry Component struct BackgroundTaskDemo { private context: Context getContext(this); // 1. 开启长时任务 async startContinuousTask() { // 配置点击通知栏消息后的动作通常是拉起应用主界面 let wantAgentInfo: wantAgent.WantAgentInfo { wants: [{ bundleName: this.context.abilityInfo?.bundleName, abilityName: this.context.abilityInfo?.name }], actionType: wantAgent.OperationType.START_ABILITY, requestCode: 0, actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG] }; try { const wantAgentObj: WantAgent await wantAgent.getWantAgent(wantAgentInfo); // 申请长时任务 await backgroundTaskManager.startBackgroundRunning( this.context, backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK, // 任务类型 wantAgentObj ); console.info(长时任务申请成功); } catch (error) { console.error(长时任务申请失败:, (error as BusinessError).message); } } // 2. 取消长时任务 async stopContinuousTask() { try { await backgroundTaskManager.stopBackgroundRunning(this.context); console.info(长时任务已取消); } catch (error) { console.error(长时任务取消失败:, (error as BusinessError).message); } } build() { Column({ space: 20 }) { Button(开启后台播放).onClick(() this.startContinuousTask()) Button(停止后台播放).onClick(() this.stopContinuousTask()) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) } }三、 核心约束与注意事项及时主动取消当后台业务执行完毕如用户点击暂停、音频播放结束时应用必须主动调用stopBackgroundRunning取消长时任务。若不及时取消系统会强行终止该任务。一致性校验系统会对长时任务进行一致性校验。如果应用申请了AUDIO_PLAYBACK但实际并未播放音频或者实际执行的业务类型与申请的不一致系统会管控并取消该长时任务。音视频播放的特殊要求若要通过AUDIO_PLAYBACK实现后台播放应用必须接入媒体会话服务AVSession以支持播控中心的统一管控。数量限制一个 UIAbility 同一时刻仅支持申请一个长时任务。如需同时申请多个需创建多个 UIAbility。通知权限申请长时任务成功后系统会在通知栏显示关联消息。应用需确保已开启通知权限否则用户无法感知后台任务的运行状态。四、 多长时任务并发API 21 特性从 API 21 开始系统打破了单个 UIAbility 只能申请一个长时任务的限制支持同一时刻申请最多 10 个长时任务。这在“一边导航一边播放音乐”的复合场景中非常实用。核心代码示例// 申请多个不同类型的长时任务 await backgroundTaskManager.startBackgroundRunning( this.context, backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK, wantAgentObj ); await backgroundTaskManager.startBackgroundRunning( this.context, backgroundTaskManager.BackgroundMode.LOCATION, wantAgentObj );五、 资源切换与防挂起机制当应用在后台进行音视频切歌或切换资源时不需要取消并重新申请长时任务。但必须注意系统的“防挂起”管控机制无缝切换直接在播放器内切换资源并重新设置AVSession的元数据和播控状态即可。防挂起时间窗口系统不允许应用创建长时任务后长时间处于“无实际业务”的状态。切歌操作必须在1分钟内重新起播不能一直处于暂停或停止状态否则系统会强制取消该长时任务。import { avSession } from kit.AVSessionKit; import { media } from kit.MediaKit; // 假设已在类中初始化了 AVPlayer 和 AVSession private async switchAudioInBackground(newAudioUrl: string, metadata: avSession.AVMetadata) { try { // 1. 重置播放器并加载新资源无需取消长时任务 await this.avPlayer.reset(); this.avPlayer.url newAudioUrl; await this.avPlayer.prepare(); this.avPlayer.play(); // 2. 同步更新 AVSession 的元数据歌曲名、歌手、封面等 await this.session.setAVMetadata(metadata); // 3. 同步更新 AVSession 的播控状态重置进度为0状态为播放中 const playbackState: avSession.AVPlaybackState { state: avSession.PlaybackState.PLAYBACK_STATE_PLAY, position: { elapsedTime: 0, updateTime: new Date().getTime() }, duration: metadata.duration }; await this.session.setAVPlaybackState(playbackState); console.info(后台无缝切歌成功); } catch (error) { console.error(后台切歌失败:, (error as BusinessError).message); } }六、 应对系统一致性校验与强制终止系统会对长时任务进行严格的“言行一致”校验。如果应用申请了DATA_TRANSFER数据传输模式系统会持续监测网络流量。若长时间无流量或实际执行的业务与声明不符系统会强制挂起或终止应用。最佳实践按需申请用完即弃在业务真正开始如点击播放、开始下载时申请在业务结束如暂停、下载完成时立即调用stopBackgroundRunning。避免空转绝不要在后台维持一个没有实际产出无音频、无网络请求、无定位的长时任务。import { backgroundTaskManager } from kit.BackgroundTasksKit; import { BusinessError } from kit.BasicServicesKit; Entry Component struct StrictTaskDemo { private context: Context getContext(this); private isDownloading: boolean false; // 1. 业务真正开始时申请长时任务 async startDownloadTask() { if (this.isDownloading) return; try { // 配置 WantAgent 用于通知栏点击拉起应用 const wantAgentObj await this.createWantAgent(); // 申请 DATA_TRANSFER 长时任务 await backgroundTaskManager.startBackgroundRunning( this.context, backgroundTaskManager.BackgroundMode.DATA_TRANSFER, wantAgentObj ); // 申请成功后再真正启动业务逻辑 this.isDownloading true; this.executeRealDownloadLogic(); console.info(长时任务申请成功开始下载); } catch (error) { console.error(长时任务申请失败:, (error as BusinessError).message); } } // 2. 业务结束或暂停时立即取消长时任务 async stopDownloadTask() { if (!this.isDownloading) return; try { // 先暂停真实的业务逻辑避免空转 this.isDownloading false; this.pauseRealDownloadLogic(); // 立即释放长时任务 await backgroundTaskManager.stopBackgroundRunning(this.context); console.info(下载已暂停长时任务已取消); } catch (error) { console.error(长时任务取消失败:, (error as BusinessError).message); } } // 模拟真实的下载逻辑 private executeRealDownloadLogic() { /* 实际下载代码 */ } private pauseRealDownloadLogic() { /* 实际暂停代码 */ } // 构建 WantAgent 辅助方法 private async createWantAgent() { /* 略 */ } build() { Column({ space: 20 }) { Button(开始下载).onClick(() this.startDownloadTask()) Button(暂停下载).onClick(() this.stopDownloadTask()) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) } }七、 非实时任务的更优解WorkScheduler延迟任务并非所有后台任务都需要“长时任务”来保持 CPU 唤醒。对于实时性要求不高、可延迟执行的维护类任务如Wi-Fi 环境下同步云端数据、定期清理本地缓存、上传日志使用长时任务会显著增加设备功耗。替代方案WorkScheduler通过kit.BackgroundTasksKit中的workScheduler模块将任务注册到系统队列由系统根据设备状态如“正在充电且连接 Wi-Fi”统一调度唤醒。核心代码示例import { workScheduler } from kit.BackgroundTasksKit; const workInfo: workScheduler.WorkInfo { workId: 1001, bundleName: com.example.app, abilityName: MyWorkSchedulerAbility, // 需实现 WorkSchedulerExtensionAbility networkType: workScheduler.NetworkType.NETWORK_TYPE_WIFI, // 仅在 Wi-Fi 下触发 isCharging: true, // 仅在充电时触发 repeatCycleTime: 30 * 60 * 1000 // 30分钟循环一次 }; try { workScheduler.startWork(workInfo); console.info(延迟任务注册成功); } catch (error) { console.error(注册失败: ${(error as BusinessError).message}); }