Android WebView 后台播放保活实现分析

15 阅读3分钟

在 Android 开发中,使用 WebView 播放音频或视频时,常遇到的一个问题是:当应用退到后台或屏幕关闭时,播放会自动暂停或被系统杀掉。为了解决这个问题,我们需要一套“保活”机制。本文将分析一个基于前台服务(Foreground Service)和通知(Notification)的保活实现方案。

核心原理

Android 系统为了节省电量和内存,会对后台应用进行严格的资源限制。要让 WebView 在后台持续播放,我们需要提升应用的进程优先级,告诉系统“用户正在关注这个应用”。

最有效的手段是使用 前台服务(Foreground Service)。前台服务要求必须显示一个通知,这不仅告知用户应用正在运行,也显著提高了进程的优先级,大大降低了被系统回收的概率。

此外,为了防止 CPU 休眠和 WiFi 断连,还需要申请 WakeLockWifiLock

实现细节

1. 创建保活服务 (PlaybackKeepAliveService)

这是一个继承自 Service 的类,其核心任务是启动前台服务并申请锁。

1.1 启动前台服务

onStartCommand 中,我们构建一个通知,并调用 startForeground

@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

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

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 中无条件申请权限。如果用户在设置中永久禁用了通知,重复申请会导致界面死循环(闪屏)。

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 忽略电池优化

为了进一步防止系统杀后台,可以引导用户将应用加入“电池优化白名单”。

private void maybeRequestIgnoreBatteryOptimizationsOnce() {
    // ... 检查并请求 ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
}

2.3 生命周期调用

  • onCreate: 调用 maybeStartKeepAliveService(true),允许申请权限。
  • onResume: 调用 maybeStartKeepAliveService(false),检查服务状态但不申请权限,防止循环。
  • onRequestPermissionsResult: 权限申请成功后启动服务。

3. WebView 设置

WebView 本身也需要配置,允许自动播放。

WebSettings settings = existingWebView.getSettings();
settings.setMediaPlaybackRequiresUserGesture(false); // 允许非手势触发的媒体播放

总结

通过结合 Foreground ServiceWakeLockWifiLock,我们可以有效地提升 WebView 在后台的存活率,实现流畅的后台音频播放体验。同时,在实现过程中要注意 Android 版本的适配(尤其是通知权限)以及对用户体验的考量(避免骚扰通知和死循环)。