一、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"/>
三、步骤二:创建服务类
创建一个继承自 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 属性指定前台服务需要使用的服务类型,以确保退后台可以保持正常的服务功能。
五、步骤四:启动前台服务
在需要启动前台服务的地方,按需启动前台服务。
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" 属性值,服务会在任务被移除时立即停止。
<service android: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];