鸿蒙音频通话应用后台保活与音频连续播放方案
一、方案概述
在实时音频通话场景中,应用切换至后台、设备息屏后,进程保活、音频采集与音频播放的连续性是核心体验指标。本方案基于鸿蒙系统 ** 长时任务(Background Task)** 机制,实现通话场景下的后台保活与音频全链路正常工作,确保应用在后台 / 息屏状态下,本地音频采集、对端音频播放均不中断、不被系统静音或回收。
二、长时任务配置与实现
2.1 module.json5 权限与后台模式声明
在应用模块配置文件中,声明音频通话所需的后台运行模式与敏感权限,是长时任务生效的基础配置。
module.json5
js
"abilities": [
{
"name": "EntryAbility",
"backgroundModes": ["audioPlayback", "audioRecording"]
}
],
"requestPermissions":[
{
"name" : "ohos.permission.MICROPHONE",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"FormAbility"
],
"when":"always"
}
},
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
"reason": "$string:reason",
"usedScene": {
"abilities": [".MainAbility"],
"when": "always"
}
}
]
2.2 动态申请运行时权限
应用运行时主动申请音频、存储、后台运行等敏感权限,获取系统授权,为音频通话与后台保活提供权限基础。
js
async requestPermissionsFn() {
console.log(`requestPermissionsFn entry`);
try {
this.atManager.requestPermissionsFromUser(this.appContext, [
'ohos.permission.MICROPHONE',
'ohos.permission.READ_MEDIA',
'ohos.permission.WRITE_MEDIA',
'ohos.permission.KEEP_BACKGROUND_RUNNING'
]).then(() => {
console.log(`request Permissions success!`);
})
} catch (err) {
console.log(`requestPermissionsFromUser call Failed! error: ${err.code}`);
}
}
2.3 长时任务管理类实现
封装长时任务的启动与停止逻辑,统一管理后台任务生命周期,适配音频播放 + 采集双场景的长时任务需求。
LongTermTaskModel.ets
js
/*
* Copyright (c) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { abilityAccessCtrl, common, PermissionRequestResult, Permissions, wantAgent, WantAgent } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { geoLocationManager } from '@kit.LocationKit';
const TAG: string = '[LongTermTaskModel]';
export class LongTermTaskModel {
private context: common.UIAbilityContext = AppStorage.get("context") as common.UIAbilityContext;
// [Start startLongTask_start]
// Start a long task
startLongTask(): void {
let wantAgentInfo: wantAgent.WantAgentInfo = {
wants: [
{
bundleName: this.context.abilityInfo.bundleName,
abilityName: this.context.abilityInfo.name
}
],
actionType: wantAgent.OperationType.START_ABILITY,
requestCode: 0,
wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};
try {
// wantAgent object is obtained by getWantAgent method in WantAgent module
wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
// backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK | backgroundTaskManager.BackgroundMode.AUDIO_RECORDING
// backgroundTaskManager.BackgroundMode.VOIP
backgroundTaskManager.startBackgroundRunning(this.context, backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK | backgroundTaskManager.BackgroundMode.AUDIO_RECORDING,
wantAgentObj)
.then(() => {
hilog.info(0x0000, TAG, `Operation startBackgroundRunning succeeded`);
})
.catch((error: BusinessError) => {
hilog.error(0x0000, TAG,
`Operation startBackgroundRunning failed. code is ${error.code} message is ${error.message}`);
});
});
} catch (error) {
hilog.error(0x0000, TAG, `Operation getWantAgent failed. error is ${JSON.stringify(error)} `);
}
}
// [End startLongTask_start]
// [Start stopLongTask_start]
// Stop a long task
stopLongTask(): void {
backgroundTaskManager.stopBackgroundRunning(this.context).then(() => {
hilog.info(0x0000, TAG, `Operation stopBackgroundRunning succeeded`);
}).catch((error: BusinessError) => {
hilog.error(0x0000, TAG, `Operation stopBackgroundRunning failed. error is ${JSON.stringify(error)} `);
});
}
// [End stopLongTask_start]
}
在EntryAbility.ets文件onCreate函数中增加context的设置
备注:设置后再LongTermTaskModel.ets调用
bash
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
AppStorage.setOrCreate('context', this.context);
}
}
2.4 长时任务调用方式
在业务组件中初始化长时任务实例,在通话开始时启动任务,保证后台生命周期。
js
import { LongTermTaskModel } from './LongTermTaskModel';
// 声明组件级任务实例
private taskModel: LongTermTaskModel = new LongTermTaskModel();
// 业务逻辑中调用启动
this.taskModel.startLongTask();
三、后台音频持续播放解决方案
3.1 问题原理
鸿蒙系统对后台应用的音频资源存在默认管控策略:应用切至后台后,系统会优先释放音频播放通道,导致对端音频无法播放。
根据鸿蒙官方规范,AVSession(音视频会话) 是系统识别应用音频活跃状态的核心依据,系统检测到有效 AVSession 实例时,将保留音频播放资源,不执行回收策略。
3.2 实现方案
在通话页面生命周期中初始化 AVSession 实例,激活会话并配置基础信息,保证后台音频播放通道不被系统释放。
js
import avSession from '@ohos.multimedia.avsession';
private session: avSession.AVSession | null = null; // AVSession实例
async aboutToAppear() {
await this.initAVSession();
}
// 实践中只创建session,切到后台音频播放就正常了
async initAVSession() {
try {
// 1. 创建会话 - 修正参数顺序和类型
const context = getContext(this) as common.UIAbilityContext;
let session = await avSession.createAVSession(context, 'SESSION_NAME', 'audio');
this.session = session;
// 2. 激活会话
await this.session.activate();
// 3. 设置元数据
const metadata: avSession.AVMetadata = {
assetId: 'audio_123',
title: '我的音频',
artist: 'Artist Name',
};
await this.session.setAVMetadata(metadata);
// 4. 设置播放状态
const playbackState: avSession.AVPlaybackState = {
state: avSession.PlaybackState.PLAYBACK_STATE_PLAY,
speed: 1.0,
position: { elapsedTime: 0, updateTime: Date.now() }
};
await this.session.setAVPlaybackState(playbackState);
console.log('AVSession initialized successfully');
} catch (err) {
console.error(`AVSession Error: ${(err as BusinessError).message}`);
}
}