在 Android 开发中,使用 WebView 播放音频或视频时,常遇到的一个问题是:当应用退到后台或屏幕关闭时,播放会自动暂停或被系统杀掉。为了解决这个问题,我们需要一套"保活"机制。本文将分析一个基于前台服务(Foreground Service)和通知(Notification)的保活实现方案。
核心原理
Android 系统为了节省电量和内存,会对后台应用进行严格的资源限制。要让 WebView 在后台持续播放,我们需要提升应用的进程优先级,告诉系统"用户正在关注这个应用"。
最有效的手段是使用 前台服务(Foreground Service)。前台服务要求必须显示一个通知,这不仅告知用户应用正在运行,也显著提高了进程的优先级,大大降低了被系统回收的概率。
此外,为了防止 CPU 休眠和 WiFi 断连,还需要申请 WakeLock 和 WifiLock。
实现细节
1. 创建保活服务 (PlaybackKeepAliveService)
这是一个继承自 Service 的类,其核心任务是启动前台服务并申请锁。
1.1 启动前台服务
在 onStartCommand 中,我们构建一个通知,并调用 startForeground。
java
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
try {
Notification notification = buildNotification();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10+ 建议指定服务类型为 mediaPlayback
startForeground(
NOTIFICATION_ID,
notification,
android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
);
} else {
startForeground(NOTIFICATION_ID, notification);
}
acquireLocksIfNeeded(); // 申请锁
return START_STICKY; // 如果服务被杀,尝试重启
} catch (RuntimeException ignored) {
stopSelf();
return START_NOT_STICKY;
}
}
1.2 构建通知
为了减少对用户的打扰,我们通常使用 IMPORTANCE_LOW 的通知渠道,并将通知优先级设为 PRIORITY_LOW。
java
private void ensureChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"播放保活",
NotificationManager.IMPORTANCE_LOW // 低重要性,不发出声音
);
// ...
}
private Notification buildNotification() {
return new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("分贝")
.setContentText("后台播放中")
.setOngoing(true) // 设置为正在进行中,用户无法侧滑清除
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.build();
}
1.3 申请电源锁和 WiFi 锁
为了防止手机在黑屏后 CPU 休眠导致播放中断,我们需要申请 PARTIAL_WAKE_LOCK。为了保证流媒体加载顺畅,建议申请 WIFI_MODE_FULL_HIGH_PERF。
java
private void acquireLocksIfNeeded() {
if (wakeLock == null) {
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
// PARTIAL_WAKE_LOCK: 保持 CPU 运转,但允许屏幕关闭
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "fm-app:playback_keep_alive");
wakeLock.acquire();
}
// WifiLock 类似...
}
2. 在 Activity 中集成 (MainActivity)
在 MainActivity 中,我们需要在合适的时机启动这个服务。
2.1 权限处理 (Android 13+)
从 Android 13 (API 33) 开始,显示通知需要 POST_NOTIFICATIONS 权限。我们需要动态申请。
关键点: 避免在 onResume 中无条件申请权限。如果用户在设置中永久禁用了通知,重复申请会导致界面死循环(闪屏)。
java
private void maybeStartKeepAliveService(boolean requestMissingPermissions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
// 仅在允许时(如 onCreate)请求权限
if (requestMissingPermissions) {
ActivityCompat.requestPermissions(
this,
new String[]{android.Manifest.permission.POST_NOTIFICATIONS},
REQUEST_CODE_POST_NOTIFICATIONS
);
}
return; // 没有权限则不启动服务
}
}
// 检查通知开关是否打开
if (!NotificationManagerCompat.from(this).areNotificationsEnabled()) {
return;
}
// 启动前台服务
ContextCompat.startForegroundService(this, new Intent(this, PlaybackKeepAliveService.class));
}
2.2 忽略电池优化
为了进一步防止系统杀后台,可以引导用户将应用加入"电池优化白名单"。
java
private void maybeRequestIgnoreBatteryOptimizationsOnce() {
// ... 检查并请求 ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
}
2.3 生命周期调用
- onCreate : 调用
maybeStartKeepAliveService(true),允许申请权限。 - onResume : 调用
maybeStartKeepAliveService(false),检查服务状态但不申请权限,防止循环。 - onRequestPermissionsResult: 权限申请成功后启动服务。
3. WebView 设置
WebView 本身也需要配置,允许自动播放。
java
WebSettings settings = existingWebView.getSettings();
settings.setMediaPlaybackRequiresUserGesture(false); // 允许非手势触发的媒体播放
总结
通过结合 Foreground Service 、WakeLock 和 WifiLock,我们可以有效地提升 WebView 在后台的存活率,实现流畅的后台音频播放体验。同时,在实现过程中要注意 Android 版本的适配(尤其是通知权限)以及对用户体验的考量(避免骚扰通知和死循环)。