HarmonyOS 鸿蒙Next中长时任务不生效

HarmonyOS 鸿蒙Next中长时任务不生效 【问题描述】: 长时任务不生效,我模拟了H5播放背景音乐,试了所有的模式,都不行,只要后台运行就会停止播放背景音乐

【问题现象】:没有具体报错信息,我调用的接口都是通过,没有异常,但我也不知道怎么看有没有生效

【版本信息】:开发工具,HbuilderX4.84 手机系统:HarmonyOs 6.0.0.120 SP6 Api语言版本6.0.1(21)

【复现代码】:不涉及

【尝试解决方案】:不涉及

7 回复

开发者您好,运行demo,下拉手机通知栏,能看到长时任务的后台运行通知,代表长时任务生效了,只是音频未在后台播放。根据提供demo初步分析,排查出以下两个问题: 1、backgroundTaskManager.startBackgroundRunning中长时任务mode不应该用VOIP视频通话类型,应该改成AUDIO_PLAYBACK音视频播放类型,申请的长时任务类型必须与实际业务匹配。 2、注意长时任务申请时机,需要在进入后台之前就申请长时任务,而不是进入后台之后,音乐停了,再去申请长时任务。

如果修改后依然无法解决问题,请提供如下信息:详细描述修改后出现的问题。

更多关于HarmonyOS 鸿蒙Next中长时任务不生效的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


【背景知识】 ArkWeb:用于在应用程序中显示Web页面内容。 长时任务:应用退至后台后,在后台需要长时间运行用户可感知的任务,如播放音乐、导航等。为防止应用进程被挂起,导致对应功能异常,可以申请长时任务,使应用在后台长时间运行。在长时任务中,支持同时申请多种类型的任务,也可以对任务类型进行更新。应用退至后台执行业务时,系统会做一致性校验,确保应用在执行相应的长时任务。 应用在申请长时任务成功后,通知栏会显示与长时任务相关联的消息,用户删除通知栏消息时,系统会自动停止长时任务。

【解决方案】 系统为了省电和稳定,会强制终止H5的后台行为,Web组件要实现音频后台播放,必须通过“长时任务”向系统申请许可,否则用户一退到后台,音频就会停止播放。 实现步骤如下:

  • 需要module.json5中配置ohos.permission.KEEP_BACKGROUND_RUNNING权限。
{
  "module" : {
    // To do sth.
    "requestPermissions":[
      {
        "name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
        "reason": "$string:reason",
        "usedScene": {
          "abilities": [
            "FormAbility"
          ],
          "when":"inuse"
        }
      }
    ]
  }
}
  • 声明后台模式类型。 在module.json5文件中为需要使用长时任务的UIAbility声明相应的长时任务类型,配置文件中填写长时任务类型的配置项。音频、视频在后台播放需配置为audioPlayback。
 "module": {
     "abilities": [
         {
             "backgroundModes": [
              // 长时任务类型的配置项
             "audioPlayback"
             ]
         }
     ],
     // To do sth.
 }
  • 如果应用本身没有后台播放业务,可以通过监听生命周期函数onBackground来判断应用是否已进入后台。 在EntryAblity.ets中的回调中获取前后台的状态,通过AppStorage将状态保存,然后在Web页面通过@Watch来监听变量的变化。
export default class EntryAbility extends UIAbility {
  onForeground(): void {
    // 切到前台,设置isForeGround值为true
    AppStorage.setOrCreate('isForeGround', true);
  }

  onBackground(): void {
    // 切到后台,设置isForeGround值为false
    AppStorage.setOrCreate('isForeGround', false)
  }
}
  • 在Web加载H5音频页面并创建长时任务。 注意:创建wantAgent.WantAgentInfo时添加需要被拉起应用的bundleName和abilityName。
import { webview } from '@kit.ArkWeb'
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { wantAgent, WantAgent } from '@kit.AbilityKit';

@Entry
@Component
export struct WebAudioPage {
  // 通过getUIContext().getHostContext()方法,来获取page所在的UIAbility上下文
  private context: Context | undefined = this.getUIContext().getHostContext();
  controller: webview.WebviewController = new webview.WebviewController()
  // 启动长时任务的函数(独立函数,避免this指向错误)
  // 监听isForeGround的值
  @Watch('network') @StorageLink('isForeGround') isForeGround: boolean = false;

  network() {
    // 切换到前台了
    if (this.isForeGround) {
      this.stopContinuousTask()
    } else {
      // 切换到后台了
      this.startContinuousTask()
    }
  }

  startContinuousTask() {
    // 指定任务类型为audioPlayback
    const taskTypes = ['audioPlayback'];
    // 指定要启动的Ability(EntryAbility)
    let wantAgentInfo: wantAgent.WantAgentInfo = {wants: [{
      bundleName: 'com.example.xxx', // 替换为你的实际包名
      abilityName: 'EntryAbility',
    }],
      actionType: wantAgent.OperationType.START_ABILITY,
      // 使用者自定义的一个私有值
      requestCode: 0,
      // 点击通知后,动作执行属性
      actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG],
    };

    try {
      wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj) => {
        backgroundTaskManager.startBackgroundRunning(this.context, taskTypes, wantAgentObj)
          .then((res: backgroundTaskManager.ContinuousTaskNotification) => {
            console.info('长时任务启动成功,音频将在后台持续播放');
          })
          .catch((error: BusinessError) => {
            console.error(`启动长时任务失败: code=${error.code}, message=${error.message}`);
          });
      }).catch((error: BusinessError) => {
        console.error('获取 WantAgent 失败:', error);
      });
    } catch (error) {
      console.error('异常:', error);
    }
  }

  stopContinuousTask() {
    backgroundTaskManager.stopBackgroundRunning(this.context).then(() => {
      console.info(`Succeeded in operationing stopBackgroundRunning.`);
    }).catch((err: BusinessError) => {
      console.error(`Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
    });
  }

  build() {
    Column() {
      Web({
        src: $rawfile('audio.html'),
        controller: this.controller
      })
        .domStorageAccess(true)
    }
    .width('100%')
    .height('100%')
  }
}

audio.mp3为音频资源文件,需要放在resources/rawfile目录下。 audio.html代码如下:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
    <meta charset="UTF-8" />
    <title>音频播放测试</title>
</head>
<body>
<h2>点击播放音频</h2>
<audio id="myAudio" controls autoplay loop>
    <source src="audio.mp3" type="audio/mpeg">
</audio>
<script>
    // 防止自动播放被拦截,建议用户交互后播放
    document.getElementById('myAudio').addEventListener('click', function () {
      this.play().catch(e => console.error('播放被阻止:', e));
    });
</script>
</body>
</html>

可以先看下这个说明文档:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/continuous-task

简单说明:
应用按需求申请长时任务,当应用无需在后台运行(任务结束)时,要及时主动取消长时任务,否则应用退至后台会被系统挂起。例如用户主动点击音乐暂停播放时,应用需及时取消对应的长时任务;用户再次点击音乐播放时,需重新申请长时任务。

若音频在后台播放时被打断,系统会自行检测和停止长时任务,音频重启播放时,需要再次申请长时任务。

后台播放音频的应用,在停止长时任务的同时,需要暂停或停止音频流,否则应用会被系统强制终止。

设备当前应用申请和取消长时任务示例代码如下:

function callback(info: backgroundTaskManager.ContinuousTaskCancelInfo) {
  // 长时任务id
  console.info('OnContinuousTaskCancel callback id ' + info.id);
  // 长时任务取消原因
  console.info('OnContinuousTaskCancel callback reason ' + info.reason);
}

@Entry
@Component
struct Index {
  @State message: string = 'ContinuousTask';
 // 通过getUIContext().getHostContext()方法,来获取page所在的UIAbility上下文
  private context: Context | undefined = this.getUIContext().getHostContext();

  OnContinuousTaskCancel() {
    try {
       backgroundTaskManager.on("continuousTaskCancel", callback);
       console.info(`Succeeded in operationing OnContinuousTaskCancel.`);
    } catch (error) {
       console.error(`Operation OnContinuousTaskCancel failed. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
    }
  }

  OffContinuousTaskCancel() {
    try {
       // callback参数不传,则取消所有已注册的回调
       backgroundTaskManager.off("continuousTaskCancel", callback);
       console.info(`Succeeded in operationing OffContinuousTaskCancel.`);
    } catch (error) {
       console.error(`Operation OffContinuousTaskCancel failed. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
    }
  }

  // 申请长时任务.then()写法
  startContinuousTask() {
    let wantAgentInfo: wantAgent.WantAgentInfo = {
      // 点击通知后,将要执行的动作列表
      // 添加需要被拉起应用的bundleName和abilityName
      wants: [
        {
          bundleName: "com.example.myapplication",
          abilityName: "MainAbility"
        }
      ],
      // 指定点击通知栏消息后的动作是拉起ability
      actionType: wantAgent.OperationType.START_ABILITY,
      // 使用者自定义的一个私有值
      requestCode: 0,
      // 点击通知后,动作执行属性
      actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG],
      // 车钥匙长时任务子类型,从API version 16开始支持。只有申请bluetoothInteraction类型的长时任务,车钥匙子类型才能生效。
      // 确保extraInfo参数中的Key值为backgroundTaskManager.BackgroundModeType.SUB_MODE,否则子类型不生效。
      // extraInfo: { [backgroundTaskManager.BackgroundModeType.SUB_MODE] : backgroundTaskManager.BackgroundSubMode.CAR_KEY }
    };

    try {
      // 通过wantAgent模块下getWantAgent方法获取WantAgent对象
      // 在元服务中,使用wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: object) => {替换下面一行代码
      wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
        try {
          let list: Array<string> = ["audioRecording"];
          // let list: Array<string> = ["bluetoothInteraction"]; 长时任务类型包含bluetoothInteraction,CAR_KEY子类型合法
          // 在元服务中,let list: Array<string> = ["audioPlayback"];
          backgroundTaskManager.startBackgroundRunning(this.context, list, wantAgentObj).then((res: backgroundTaskManager.ContinuousTaskNotification) => {
            console.info("Operation startBackgroundRunning succeeded");
            // 此处执行具体的长时任务逻辑,如录音,录制等。
            // 系统会对业务场景的真实性进行检测,如果没有实际执行对应的业务,系统可能会取消对应的长时任务并挂起应用。
          }).catch((error: BusinessError) => {
            console.error(`Failed to Operation startBackgroundRunning. code is ${error.code} message is ${error.message}`);
          });
        } catch (error) {
          console.error(`Failed to Operation startBackgroundRunning. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
        }
      });
    } catch (error) {
      console.error(`Failed to Operation getWantAgent. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
    }
  }

  // 取消长时任务.then()写法
  stopContinuousTask() {
     backgroundTaskManager.stopBackgroundRunning(this.context).then(() => {
       console.info(`Succeeded in operationing stopBackgroundRunning.`);
     }).catch((err: BusinessError) => {
       console.error(`Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${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%')
  }
}

你好,若应用需申请长时任务,应用无需在后台运行(任务结束)时,要及时主动取消长时任务,否则应用退至后台会被系统挂起。例如用户主动点击音乐暂停播放时,应用需及时取消对应的长时任务;用户再次点击音乐播放时,需重新申请长时任务。

若音频在后台播放时被打断,系统会自行检测和停止长时任务,音频重启播放时,需要再次申请长时任务。

后台播放音频的应用,在停止长时任务的同时,需要暂停或停止音频流,否则应用会被系统强制终止。

关于AUDIO_PLAYBACK(音视频播放)说明:

  • 音视频投播,是指将一台设备的音视频投至另一台设备播放。投播退至后台,长时任务会检测音视频播放和投屏两个业务,只要有其一正常运行,长时任务就不会终止。
  • 当应用需要在后台播放媒体类型(流类型为STREAM_USAGE_MUSIC、STREAM_USAGE_MOVIE和STREAM_USAGE_AUDIOBOOK)和游戏类型(流类型为STREAM_USAGE_GAME)时,必须接入媒体会话服务(AVSession)并申请AUDIO_PLAYBACK类型长时任务。
  • 除了上述播放类型,针对用户可感知的其他播放任务,如果应用需要在后台长时间运行该任务,必须申请AUDIO_PLAYBACK类型长时任务,无需接入AVSession。
  • 如果应用不满足上述接入规范,退至后台播放时会被系统静音并冻结,无法在后台正常播放,直到应用重新切回前台时,才会解除静音并恢复播放。
  • 从API version 20开始,申请AUDIO_PLAYBACK类型长时任务但不接入AVSession,申请长时任务成功后会在通知栏显示通知;接入AVSession后,后台任务模块不会发送通知栏通知,由AVSession发送通知。对于API version 19及之前的版本,后台任务模块不会在通知栏显示通知。

运行限制

  • 申请长时任务后,应用未执行相应的业务,系统会对应用进行管控,即应用退至后台会被挂起。如系统检测到应用申请了AUDIO_PLAYBACK(音视频播放),但实际未播放音乐。
  • 申请长时任务后,应用执行的业务类型与申请的不一致,系统会对应用进行管控,即应用退至后台会被挂起。如系统检测到应用只申请了AUDIO_PLAYBACK(音视频播放),但实际上除了播放音乐(对应AUDIO_PLAYBACK类型),还在进行录制(对应AUDIO_RECORDING类型)。
  • 申请长时任务后,应用的业务已执行完,系统会对应用进行管控,即应用退至后台会被挂起。
  • 若运行长时任务的进程后台负载持续高于所申请类型的典型负载,系统会对应用进行管控,即应用退至后台会被挂起或终止。

说明

应用按需求申请长时任务,当应用无需在后台运行(任务结束)时,要及时主动取消长时任务,否则应用退至后台会被系统挂起。例如用户主动点击音乐暂停播放时,应用需及时取消对应的长时任务;用户再次点击音乐播放时,需重新申请长时任务。

若音频在后台播放时被打断,系统会自行检测和停止长时任务,音频重启播放时,需要再次申请长时任务。

后台播放音频的应用,在停止长时任务的同时,需要暂停或停止音频流,否则应用会被系统强制终止。

在HarmonyOS Next中,长时任务不生效可能涉及以下原因:

  1. 未在module.json5中正确配置长时任务权限与后台模式。
  2. 未调用backgroundTaskManager.startBackgroundRunning()方法启动任务。
  3. 任务执行逻辑不符合长时任务规范,如未持续占用资源或未及时更新通知。
  4. 系统资源管理策略限制,如电量优化或内存回收。
  5. 应用未在前台运行或设备处于特定状态(如锁屏)。
    检查配置、启动流程及任务实现是否符合要求。

在HarmonyOS Next中,长时任务(Continuous Task)的设计和实现机制与之前的版本有显著差异,这可能是导致您遇到问题的根本原因。以下是针对您问题的具体分析:

1. 核心机制变更 HarmonyOS Next的长时任务管理更为严格,主要服务于系统级关键场景(如导航、音乐播放、录音等)。应用需要在module.json5配置文件中明确声明所需的长时任务类型,并在代码中动态申请相应的系统能力。

2. 关键排查点

  • 权限与配置:请确认已在module.json5中正确声明了ohos.permission.KEEP_BACKGROUND_RUNNING权限,并在abilities下为对应Ability配置了backgroundModes(例如,音乐播放需包含"audioPlayback")。
  • 动态申请:配置完成后,必须在代码中(如onCreate或播放开始前)调用backgroundTaskManager.requestSuspendDelay()等相关API来动态申请后台运行延迟挂起。仅靠配置无法自动生效。
  • 场景匹配:系统会严格校验申请场景是否与声明的backgroundModes匹配。H5页面播放背景音乐,需确保触发长时任务申请的代码路径与播放行为直接关联,且被系统识别为有效的音频播放场景。

3. 建议操作步骤

  • 检查配置:核对module.json5中的权限与backgroundModes声明。
  • 验证API调用:确保在播放开始时有成功调用长时任务申请API,并处理其返回的DelaySuspendInfo对象。
  • 使用系统能力:对于音频播放,建议优先使用HarmonyOS的音频管理API(@ohos.multimedia.audio)而非纯Web方式,这有助于系统准确识别任务类型。
  • 查看日志:通过DevEco Studio的日志查看器,筛选BackgroundTaskManager相关标签,可以观察长时任务的申请、授予或拒绝详情。

由于HarmonyOS Next的后台管理策略强调系统资源合理分配,非核心场景的持续后台运行会受到限制。请重点确认上述配置与动态调用环节是否已正确完成。

回到顶部