用鸿蒙做一个带下载功能的应用,或者是需要后台播放音频的 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,或者没在充电,系统绝不会为了你一个后台同步把用户电量耗光。

不管是短时、长时还是延迟任务,核心思想只有一个:遵守系统的规矩。该申请申请,该释放释放,任务类型和声明保持一致,你的应用才能在后台安稳地存活下去。

Logo

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

更多推荐