第26章 后台与保活:Service保活策略、前台Service、屏幕关闭时音频继续
做WebRTC视频会议,最头疼的问题是什么?
我个人觉得,不是音视频编解码,也不是网络抖动。而是——用户把App切到后台,或者锁屏之后,通话就断了。
你想想看,用户正开着会呢,突然来个微信消息,点进去回了一句,再切回来发现通话已经断了。这种体验,用户不骂娘才怪。
所以这一章,我们就来聊聊Android平台上的保活策略。说白了,就是怎么让我们的WebRTC通话在后台、在锁屏状态下,依然坚挺地运行。
26.1 为什么Service会被杀死?
先搞清楚一个问题:Android为什么要杀我们的Service?
原因很简单——资源不够了。内存紧张、CPU吃紧、电池快没电了,系统就会开始清理后台进程。尤其是国产手机,各家厂商都有自己的省电策略,杀后台那叫一个狠。
我在项目中遇到过最夸张的情况:某款手机,App切到后台不到30秒,Service就被干掉了。用户反馈说“一锁屏就断线”,我们排查了好久才发现是系统省电策略搞的鬼。
核心认知:Android系统杀Service,不是针对你,而是为了整体系统的稳定性。我们要做的,是让系统觉得“这个Service很重要,别杀我”。
26.2 前台Service:最基础的保活手段
前台Service,说白了就是给Service一个“永久通知栏”的入场券。只要通知栏还在,系统就不会轻易杀你的Service。
为什么?因为前台Service会有一个持续的通知展示给用户,系统认为用户能感知到这个服务的存在,所以优先级会高很多。
26.2.1 实现前台Service
实现起来其实不复杂。我直接贴代码,你照着写就行。
// 在Service的onCreate或onStartCommand中启动前台Service
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 创建通知渠道(Android 8.0+需要)
createNotificationChannel();
// 构建通知
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("视频会议进行中")
.setContentText("点击返回会议")
.setSmallIcon(R.drawable.ic_call)
.setOngoing(true) // 不可滑动删除
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build();
// 启动前台Service
startForeground(NOTIFICATION_ID, notification);
return START_STICKY;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"视频会议",
NotificationManager.IMPORTANCE_HIGH
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
}
}
小技巧:通知的setOngoing(true)一定要加。这样用户不能滑动删除通知,系统会认为这个服务是“持续且重要的”。
26.2.2 前台Service的坑
嗯,这里要注意。前台Service虽然好用,但有几个坑你得知道。
- Android 9+的限制:前台Service需要申请
FOREGROUND_SERVICE权限,否则会抛异常。 - 通知必须可见:如果用户手动划掉了通知,系统可能会认为服务不再重要,从而杀掉它。
- 国产手机的特殊处理:有些手机会把前台Service的通知默认隐藏,导致保活效果打折扣。
我曾经遇到一个案例:某款手机,前台Service的通知被系统自动折叠到“不重要通知”里,结果Service还是被杀了。后来我们加了一个setPriority(IMPORTANCE_HIGH)才解决。
26.3 屏幕关闭时音频继续
视频会议中,用户可能会锁屏,但音频不能断。这个需求很常见——用户可能想听会议内容,或者只是暂时离开一下。
怎么做?核心思路是:获取一个唤醒锁(WakeLock),让CPU在屏幕关闭时继续工作。
26.3.1 使用WakeLock
// 在WebRTC的PeerConnectionFactory初始化时获取WakeLock
public class AudioKeepAliveManager {
private PowerManager.WakeLock wakeLock;
public void acquireWakeLock(Context context) {
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"WebRTC:AudioKeepAlive"
);
wakeLock.acquire(10 * 60 * 1000L); // 最多保持10分钟
}
public void releaseWakeLock() {
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
}
}
}
警告:WakeLock一定要在不需要时及时释放。否则会严重消耗电量,甚至被系统判定为“耗电异常”而强制杀掉。
26.3.2 结合前台Service使用
我个人习惯的做法是:前台Service + WakeLock 组合使用。前台Service保证Service不被杀,WakeLock保证CPU不休眠。
// 在WebRTC的音频模块中
public class WebRTCAudioManager {
private AudioKeepAliveManager audioKeepAlive;
public void startAudioKeepAlive() {
// 1. 启动前台Service
Intent serviceIntent = new Intent(context, CallService.class);
context.startForegroundService(serviceIntent);
// 2. 获取WakeLock
audioKeepAlive = new AudioKeepAliveManager();
audioKeepAlive.acquireWakeLock(context);
// 3. 初始化WebRTC音频
PeerConnectionFactory factory = ...;
// 设置音频参数,确保锁屏时音频继续
}
public void stopAudioKeepAlive() {
audioKeepAlive.releaseWakeLock();
context.stopService(new Intent(context, CallService.class));
}
}
26.4 进阶保活策略
前台Service + WakeLock 是最基础的方案。但说实话,在国产手机上,这还不够。我整理了几种进阶策略,你可以根据实际情况选择。
| 策略 | 原理 | 适用场景 | 风险 |
|---|---|---|---|
| 双进程守护 | 两个进程互相监听,一个被杀另一个拉起 | 极端保活需求 | 增加系统负担,可能被厂商拉黑 |
| JobScheduler定时唤醒 | 利用系统JobScheduler定期检查Service状态 | 需要定时同步的场景 | 延迟较高,不适合实时通话 |
| 系统白名单申请 | 引导用户将App加入系统省电白名单 | 所有场景 | 需要用户手动操作,体验不好 |
| 前台Service + 通知高优先级 | 提高通知优先级,防止被折叠 | 基础保活 | 部分手机仍会杀 |
我的建议:不要过度追求保活。Android系统越来越严格,强行保活只会让用户觉得你的App耗电、卡顿。合理的方式是:前台Service + 引导用户加入白名单 + 做好断线重连机制。
26.5 知识体系总览
这一章的内容比较多,我画了一张图帮你梳理一下。
26.6 避坑指南
做后台保活,我踩过的坑比你想象的多。这里列几个最常见的,你遇到了可以直接抄答案。
- 不要滥用WakeLock:我曾经在一个项目里,为了保活,全程持有WakeLock。结果用户反馈手机发烫、掉电快。后来我们改成只在通话期间持有,通话结束后立即释放。
- 前台Service的通知不要隐藏:有些开发者觉得通知碍眼,想方设法隐藏。结果Service被杀了还不知道为什么。记住:通知是前台Service的“护身符”。
- 国产手机的特殊处理:华为、小米、OPPO、vivo都有自己的省电策略。我建议你在App内引导用户将你的App加入“受保护应用”列表。虽然操作麻烦,但效果最好。
- 断线重连机制:保活不是万能的。就算你做得再好,系统还是可能杀你的Service。所以一定要做好断线重连机制。WebRTC本身支持ICE重启,你只需要在检测到连接断开时重新发起连接即可。
个人经验:我做过一个统计,在主流手机上,前台Service + WakeLock 的组合,保活成功率大约在85%左右。剩下的15%,要么是系统强制杀,要么是用户手动清理。所以,别追求100%保活,那是不可能的。
好了,这一章的内容就到这里。后台保活是个系统工程,不是加一个Service就能解决的。你需要结合前台Service、WakeLock、系统白名单、断线重连等多种手段,才能给用户一个流畅的体验。