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

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

核心原理

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

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

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

实现细节

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

相关推荐
青云计划7 小时前
知光项目知文发布模块
java·后端·spring·mybatis
Victor3567 小时前
MongoDB(9)什么是MongoDB的副本集(Replica Set)?
后端
Victor3567 小时前
MongoDB(8)什么是聚合(Aggregation)?
后端
yeyeye1119 小时前
Spring Cloud Data Flow 简介
后端·spring·spring cloud
Tony Bai9 小时前
告别 Flaky Tests:Go 官方拟引入 testing/nettest,重塑内存网络测试标准
开发语言·网络·后端·golang·php
+VX:Fegn089510 小时前
计算机毕业设计|基于springboot + vue鲜花商城系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
程序猿阿伟10 小时前
《GraphQL批处理与全局缓存共享的底层逻辑》
后端·缓存·graphql
小小张说故事10 小时前
SQLAlchemy 技术入门指南
后端·python
识君啊10 小时前
SpringBoot 事务管理解析 - @Transactional 的正确用法与常见坑
java·数据库·spring boot·后端
想用offer打牌11 小时前
MCP (Model Context Protocol) 技术理解 - 第五篇
人工智能·后端·mcp