对于涉及音视频采集和播放的移动端应用,通常需要进行额外的保活处理,否则应用在后台运行时会受到一定的功能性限制,甚至在后台运行一段时间后被系统强制终止运行。下面我们分别介绍 Android 应用保活方案,以及 iOS 应用保活方案。
Android 应用保活方案
目前 Android 端常用的保活方式是启动前台服务。前台服务是一种特殊的服务,它在运行时会显示一个持续的通知,以告知用户该服务正在运行。由于前台服务的通知是持续可见的,系统会认为这是一个高优先级的任务,应用可以在后台持续运行,而不容易被系统终止。下面介绍前台服务的实现方式及步骤。
步骤一:声明权限
在 AndroidManifest.xml 文件中添加以下权限声明。
<!-- 允许应用使用前台服务 --><uses-permission android:name="android.permission.FOREGROUND_SERVICE" /><!-- 如果应用需要在前台服务中使用摄像头 --><uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" /><!-- 如果应用需要在前台服务中使用麦克风 --><uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" /><!-- 允许前台服务发送通知 --><uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
注意:
若您的项目设置 targetSdkVersion 34及以上,且需要在前台服务中使用摄像头和麦克风,则
FOREGROUND_SERVICE_CAMERA
和 FOREGROUND_SERVICE_MICROPHONE
权限声明是必须的。在 Android 13及以上,如果想让前台服务显示在通知栏上,则需要声明
POST_NOTIFICATIONS
权限。步骤二:创建服务类
创建一个继承自 Service 的类,并在其中实现前台服务的逻辑。
public class MyForegroundService extends Service {@Nullable@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {super.onCreate();// 创建通知渠道Notification notification = createNotification();// 处理服务启动逻辑if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {startForeground(1024, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);} else {startForeground(1024, notification);}}private Notification createNotification() {String CHANNEL_ONE_ID = "CHANNEL_ONE_ID";String CHANNEL_ONE_NAME = "CHANNEL_ONE_ID";NotificationChannel notificationChannel;//进行8.0的判断if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {notificationChannel = new NotificationChannel(CHANNEL_ONE_ID,CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_HIGH);notificationChannel.enableLights(true);notificationChannel.setLightColor(Color.RED);notificationChannel.setShowBadge(true);notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);if (manager != null) {manager.createNotificationChannel(notificationChannel);}}// 设置点击通知栏回到应用,可选Intent intent = new Intent(this, MainActivity.class);ActivityOptions options = null;if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {options = ActivityOptions.makeBasic();}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {options.setPendingIntentBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);}PendingIntent pendingIntent;if (options != null) {pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE, options.toBundle());} else {pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {Notification notification = new Notification.Builder(this, CHANNEL_ONE_ID).setChannelId(CHANNEL_ONE_ID).setSmallIcon(R.mipmap.videocall_float_logo).setContentTitle("这是一个测试标题").setContentIntent(pendingIntent).setContentText("这是一个测试内容").build();notification.flags |= Notification.FLAG_NO_CLEAR;return notification;}else {NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ONE_ID).setSmallIcon(R.mipmap.videocall_float_logo).setContentTitle("这是一个测试标题").setContentText("这是一个测试内容").setContentIntent(pendingIntent).setPriority(NotificationCompat.PRIORITY_DEFAULT);return builder.build();}}@Overridepublic void onDestroy() {super.onDestroy();// 停止前台服务stopForeground(true);}}
步骤三:声明服务
在 AndroidManifest.xml 文件中声明服务。
<service android:name=".MyForegroundService" android:enabled="true" android:exported="false" android:foregroundServiceType="mediaPlayback|mediaProjection|microphone|camera" />
注意:
您可以通过
android:foregroundServiceType
属性指定前台服务需要使用的服务类型,以确保退后台可以保持正常的服务功能。mediaPlayback
服务用于媒体播放。mediaProjection
服务用于媒体投影。microphone
服务用于使用麦克风。camera
服务用于使用摄像头。步骤四:启动前台服务
在需要启动前台服务的地方,按需启动前台服务。
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); boolean areNotificationsEnabled = notificationManager.areNotificationsEnabled(); if (!areNotificationsEnabled) { // 提示用户启用通知权限 Toast.makeText(this, "请启用通知权限以确保服务正常运行", Toast.LENGTH_LONG).show(); // 引导用户到设置页面 Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) .putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); startActivity(intent); } else { // 启动前台服务 Intent serviceIntent = new Intent(this, MyForegroundService.class); ContextCompat.startForegroundService(this, serviceIntent); }
注意:
为了确保前台服务能够正常运行,建议在启动前台服务之前,检查通知权限是否被禁用。如果被禁用,可以提示用户启用通知权限。
步骤五:停止前台服务
可从外部组件(例如 Activity 或 BroadcastReceiver)中停止前台服务。
// 创建服务的IntentIntent serviceIntent = new Intent(this, MyForegroundService.class);// 停止服务stopService(serviceIntent);
在大部分移动设备上,针对启动前台服务的应用,用户在最近应用列表里划卡强杀,前台服务会同时终止,应用会完全停止运行。但在部分境外品牌移动设备上(例如 Google Pixel 系列和 SAMSUNG A 系列),划卡强杀后应用并不会完全停止运行,前台服务仍然处于活跃状态,这会导致用户还能够听到媒体播放。
针对此类设备,可以通过实现以下两种方案,从而避免应用被强杀后仍然播放媒体声音的现象。
方案一:在服务声明中定义 stopWithTask 属性
添加
android:stopWithTask="true"
属性值,服务会在任务被移除时立即停止。<serviceandroid:name=".MyForegroundService"android:enabled="true"android:exported="false"android:stopWithTask="true"android:foregroundServiceType="mediaPlayback|microphone" />
方案二:在 Service 层监听 onTaskRemoved 回调
监听 onTaskRemoved,当任务被移除时,该方法会被回调,您可以在这里执行清理操作或保存数据的逻辑。
@Overridepublic void onTaskRemoved(Intent rootIntent) {super.onTaskRemoved(rootIntent);// 例如在这里执行TRTC退房,避免继续进行音频采集和播放TRTCCloud mTRTCCloud = TRTCCloud.sharedInstance(this);mTRTCCloud.exitRoom();}
注意:
以上两种方案任选其一即可,当设置
android:stopWithTask="true"
之后,onTaskRemoved
方法将不会被回调。iOS 应用保活方案
苹果对应用在后台的行为有严格的限制,以保护用户的隐私和设备的电池寿命。通常可以通过启用特定的后台模式(Background Modes),从而在一定程度上实现应用保活,允许应用在后台播放音频或视频。
启用后台模式
Xcode 启用后台模式(Background Modes)的步骤如下:
1. 打开您的 Xcode 项目。
2. 选择您的项目文件(通常在项目导航器的顶部)。
3. 在项目文件中,选择您的目标(Targets)。
4. 在目标设置中,选择 Signing&Capabilities 选项卡。
5. 找到 Background Modes 选项,勾选 Audio, AirPlay, and Picture in Picture 模式。


常见问题
1. 在语音通话或直播场景中,主播将应用退至后台或锁屏,插拔耳机导致音频采集和播放无声
在 SDK 默认的音频策略下,系统音量类型处于自动切换模式,即“麦上通话,麦下媒体”。同时音量类型也会随音频路由的变化而变化,例如插入耳机音量类型会由通话音量切换至媒体音量。因此,在自动切换模式下,主播插拔耳机会导致系统音量类型的切换,此时系统需要重启音频驱动。而 iOS 系统在后台或锁屏状态下重启音频驱动有概率会失败,所以会导致音频采集和播放无声。
针对这类问题,可以通过固定系统音量类型来规避,例如指定全程通话音量或全程媒体音量。
// 指定全程通话音量[self.trtcCloud setSystemVolumeType:TRTCSystemVolumeTypeVOIP];// 指定全程媒体音量[self.trtcCloud setSystemVolumeType:TRTCSystemVolumeTypeMedia];
2. 在视频通话或直播场景中,主播将应用退至后台或锁屏,远端观众拉流画面黑屏但声音正常
苹果系统严格禁止应用在后台采集视频。即使您启用了后台模式,应用在进入后台后,摄像头仍然会自动停止工作。这是为了保护用户的隐私,防止应用在未经用户同意的情况下录制视频。因此,这类场景下的视频采集问题暂时无法避免,只能实现音频的正常采集和播放。
3. 观众进房时房间内无人推流,将应用退至后台或锁屏,后续房间内有人推流也无法正常接收
iOS 应用在切换至后台之前,如果没有启动 AudioUnit 采集或播放,则很快会被挂起,且无法人为唤醒,直到切换至前台。要解决此类问题,只需要在应用切换至后台之前,保持 AudioUnit 一直运行即可(播放静音数据)。具体实现方法可参考下方示例代码。
// 进房之后启用自定义音轨[self.trtcCloud enableMixExternalAudioFrame:NO playout:YES];// 退房之前关闭自定义音轨[self.trtcCloud enableMixExternalAudioFrame:NO playout:NO];