文档中心>TRTC 云助手>模块化方案>移动端应用保活方案

移动端应用保活方案

最近更新时间:2025-01-06 17:59:41

我的收藏
对于涉及音视频采集和播放的移动端应用,通常需要进行额外的保活处理,否则应用在后台运行时会受到一定的功能性限制,甚至在后台运行一段时间后被系统强制终止运行。下面我们分别介绍 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_CAMERAFOREGROUND_SERVICE_MICROPHONE 权限声明是必须的。
在 Android 13及以上,如果想让前台服务显示在通知栏上,则需要声明 POST_NOTIFICATIONS 权限。

步骤二:创建服务类

创建一个继承自 Service 的类,并在其中实现前台服务的逻辑。
public class MyForegroundService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public 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();
}
}

@Override
public 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)中停止前台服务。
// 创建服务的Intent
Intent serviceIntent = new Intent(this, MyForegroundService.class);

// 停止服务
stopService(serviceIntent);
在大部分移动设备上,针对启动前台服务的应用,用户在最近应用列表里划卡强杀,前台服务会同时终止,应用会完全停止运行。但在部分境外品牌移动设备上(例如 Google Pixel 系列和 SAMSUNG A 系列),划卡强杀后应用并不会完全停止运行,前台服务仍然处于活跃状态,这会导致用户还能够听到媒体播放
针对此类设备,可以通过实现以下两种方案,从而避免应用被强杀后仍然播放媒体声音的现象。

方案一:在服务声明中定义 stopWithTask 属性

添加 android:stopWithTask="true" 属性值,服务会在任务被移除时立即停止。
<service
android:name=".MyForegroundService"
android:enabled="true"
android:exported="false"
android:stopWithTask="true"
android:foregroundServiceType="mediaPlayback|microphone" />

方案二:在 Service 层监听 onTaskRemoved 回调

监听 onTaskRemoved,当任务被移除时,该方法会被回调,您可以在这里执行清理操作或保存数据的逻辑。
@Override
public 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];