A hand-drawn doodle illustration on pure white pap

前言

做音乐播放器的同学肯定遇到过这个场景:用户一切到别的 App,你的音乐就停了。用户骂你 App 有 bug,你心里苦——不是不想放,是系统把你挂起了。

HarmonyOS7 的 Background Task Kit 就是来救场的。 它给后台任务提供了合法的身份,让你的 App 在后台也能名正言顺地干活。
移动端系统对后台进程的管控越来越严,这是大势所趋。HarmonyOS7 也不例外——应用退到后台一小段时间就会被挂起,内存不够还会被直接干掉。

但有些场景你确实需要在后台跑:音乐播放、导航、倒计时提醒……系统不可能一刀切,所以 Background Task Kit 提供了几种规范内的后台任务类型,只要你按规矩申请,系统就让你跑。

后台任务类型一览

A hand-drawn doodle illustration on pure white pap

HarmonyOS7 提供了 5 种后台任务,每种针对不同场景:

类型 适用场景 执行时长 是否用户可感知
短时任务 状态保存、数据同步等临时操作 约 3 分钟,可申请延期
长时任务 音乐播放、导航、设备连接等 无硬性上限,但需持续满足条件 ,状态栏有提示
延迟任务 数据备份、日志上传等低优先级任务 系统统一调度,不保证执行时间
代理提醒 闹钟、倒计时、日历提醒 应用被杀后系统代理执行 ,系统通知
能效资源 需要持续使用硬件资源的任务 与申请的资源绑定 视情况而定

划重点:选错类型,任务会被系统拒绝。 比如你拿短时任务去搞后台音乐,跑一会儿就被掐了。

长时任务申请

后台音乐播放是最典型的长时任务场景。来看完整代码:

A hand-drawn doodle illustration on pure white pap

import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { wantAgent, WantAgent } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct MusicPlayer {
  @State isPlaying: boolean = false
  private wantAgentObj: WantAgent | null = null

  async startBackgroundTask() {
    try {
      let wantAgentInfo: wantAgent.WantAgentInfo = {
        wants: [
          {
            bundleName: 'com.example.musicplayer',
            abilityName: 'EntryAbility'
          }
        ],
        operationType: wantAgent.OperationType.START_ABILITY,
        requestCode: 0,
        wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
      }

      this.wantAgentObj = await wantAgent.getWantAgent(wantAgentInfo)

      backgroundTaskManager.startBackgroundRunning(
        this.context,
        backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK,
        this.wantAgentObj!
      )
      this.isPlaying = true
      console.info('长时任务申请成功,后台可以继续播放')
    } catch (error) {
      let e = error as BusinessError
      console.error(`长时任务申请失败: ${e.code} - ${e.message}`)
    }
  }

  stopBackgroundTask() {
    try {
      backgroundTaskManager.stopBackgroundRunning(this.context)
      this.isPlaying = false
      console.info('长时任务已取消')
    } catch (error) {
      let e = error as BusinessError
      console.error(`取消长时任务失败: ${e.code} - ${e.message}`)
    }
  }

  build() {
    Column() {
      Text(this.isPlaying ? '正在播放中...' : '已暂停')
        .fontSize(24)
      Row() {
        Button('开始播放')
          .onClick(() => this.startBackgroundTask())
        Button('停止播放')
          .onClick(() => this.stopBackgroundTask())
      }
    }
  }
}

关键代码讲解:

  • wantAgent.getWantAgent() —— 创建一个 WantAgent 对象,长时任务必须传这个参数。它的作用是:当用户点击状态栏的后台任务图标时,系统通过 WantAgent 把应用拉起来
  • BackgroundMode.AUDIO_PLAYBACK —— 声明后台模式类型,不同场景对应不同的枚举值:导航用 NAVIGATION,蓝牙用 BLUETOOTH_INTERACTION
  • startBackgroundRunning() —— 正式申请长时任务,三个参数:上下文、后台模式、WantAgent
  • stopBackgroundRunning() —— 任务结束后必须手动取消,否则系统会认为你在滥用后台权限

敲黑板:长时任务申请成功后,状态栏会出现一个持久通知,告诉用户有 App 在后台运行。用户可以主动点击关闭,所以别指望它能无限跑。

延迟任务

有些操作不着急,比如凌晨自动备份数据、闲时上传日志。延迟任务就是干这个的——你把任务扔给系统,系统根据内存和功耗情况统一调度。

import { workScheduler } from '@kit.BackgroundTasksKit';

function requestDelayedTask() {
  let workInfo: workScheduler.WorkInfo = {
    workId: 1,
    batteryStatus: workScheduler.BatteryStatus.BATTERY_STATUS_LOW,
    isRepeat: true,
    repeatCycleTime: 86400000,
    isPersisted: true,
    bundleName: 'com.example.myapp',
    abilityName: 'DelayedWorkAbility'
  }

  try {
    workScheduler.startWork(workInfo)
    console.info('延迟任务已注册')
  } catch (error) {
    console.error(`延迟任务注册失败: ${(error as BusinessError).code}`)
  }
}

关键代码讲解:

  • workId —— 任务唯一标识,取消任务时要用这个 ID
  • batteryStatus: BATTERY_STATUS_LOW —— 约束条件,只在低电量时触发?当然不是,这里只是示例。实际开发中你可以设置网络类型、充电状态、存储空间等条件
  • isRepeat: true + repeatCycleTime: 86400000 —— 重复执行,间隔 24 小时
  • isPersisted: true —— 设备重启后是否保留这个任务
  • bundleName + abilityName —— 指定处理这个任务的 Ability,系统会在条件满足时启动它

延迟任务的核心思路是"你定规则,系统来调度"。 你不用关心什么时候执行,系统会在合适的时候帮你跑。

后台代理提醒

闹钟、倒计时这类提醒,哪怕 App 被杀了也得响。代理提醒就是让系统替你干活:

import { reminderAgentManager } from '@kit.ReminderAgentKit';

function setAlarmReminder() {
  let alarm: reminderAgentManager.ReminderRequestAlarm = {
    reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_ALARM,
    hour: 7,
    minute: 30,
    title: '起床闹钟',
    content: '该起床了,别赖床!'
  }

  try {
    let reminderId = reminderAgentManager.publishReminder(alarm)
    console.info(`代理提醒已发布,ID: ${reminderId}`)
  } catch (error) {
    console.error(`代理提醒发布失败: ${(error as BusinessError).code}`)
  }
}

关键代码讲解:

  • ReminderType.REMINDER_TYPE_ALARM —— 闹钟类型,还有 REMINDER_TYPE_TIMER(倒计时)和 REMINDER_TYPE_CALENDAR(日历提醒)
  • hour: 7, minute: 30 —— 每天 7:30 触发
  • title + content —— 通知栏显示的标题和内容
  • publishReminder() —— 发布提醒,返回一个 ID,取消时用 cancelReminder(reminderId)

代理提醒最牛的地方是:即使你的 App 进程已经被杀了,系统照样帮你弹通知。 这就是"代理"的含义——系统替你干活。

后台任务生命周期

搞清楚生命周期,才能知道任务什么时候该申请、什么时候该释放:

应用前台运行
      │
      ▼
应用退至后台 ──── 申请后台任务 ──── 任务生效,继续运行
      │                                    │
      │                                    ▼
      │                            任务结束/用户取消/系统回收
      │                                    │
      ▼                                    ▼
应用被挂起 ◄───────────────── 任务失效,进程挂起

几个要点:

  1. 前台不需要申请后台任务,只有退到后台才需要
  2. 长时任务要配合 WantAgent,不然用户点状态栏图标没反应
  3. 任务结束必须释放,别占着茅坑不拉屎
  4. 系统资源不足时,后台任务也可能被杀,长时任务只是提高优先级,不是免死金牌

注意事项

后台任务这块坑不少,列几个关键的:

  • 权限配置不能忘module.json5 里必须声明对应权限,长时任务要 ohos.permission.KEEP_BACKGROUND_RUNNING,延迟任务要 ohos.permission.WORK_SCHEDULER
  • 短时任务有数量限制:同一时间最多 3 个,超了申请会失败
  • 长时任务类型要匹配:你申请了 AUDIO_PLAYBACK,结果后台没在播放音频,系统检测到会给你掐了
  • 延迟任务不能保证执行:系统调度,可能等很久才跑,别把实时性要求高的任务放这
  • 代理提醒有数量上限:一个应用最多发布 30 个提醒
  • 别拿后台任务干坏事:后台偷偷上传用户数据、挖矿之类的,审核直接打回

写在最后

Background Task Kit 的核心逻辑就一句话:后台不是法外之地,按规矩申请,系统让你跑;不按规矩来,随时被掐。

选对任务类型、配好权限、用完及时释放,这三步做到位,后台任务就不会出幺蛾子。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐