Android锁屏后ExoPlayer播放10-20分钟中断的编程修复方法
解决ExoPlayer后台锁屏播放停止的问题
这个问题我之前帮不少开发者处理过——本质上是Android系统在后台资源紧张时,会优先回收非前台进程的资源,再加上没有正确持有唤醒锁和声明播放优先级,才导致ExoPlayer在锁屏十几分钟后被暂停。下面给你一步步的解决方案:
1. 给ExoPlayer配置唤醒模式与音频属性
ExoPlayer确实没有MediaPlayer那样直接的setWakeMode()方法,但它把这些配置整合到了初始化Builder里,用来告诉系统我们需要持续播放音频的需求:
// 配置音频属性,声明这是媒体播放,优先级更高 val audioAttributes = AudioAttributes.Builder() .setContentType(C.CONTENT_TYPE_MUSIC) .setUsage(C.USAGE_MEDIA) .build() // 创建播放器时设置唤醒模式和音频属性 val player = ExoPlayer.Builder(context) .setAudioAttributes(audioAttributes, true) // 第二个参数设为true,让ExoPlayer自动处理音频焦点 .setWakeMode(C.WAKE_MODE_NETWORK) // 网络电台选这个,保持网络连接+CPU唤醒;本地音频用C.WAKE_MODE_LOCAL .build()
WAKE_MODE_NETWORK:适合网络电台,确保播放时CPU和网络连接都保持活跃;WAKE_MODE_LOCAL:适合本地音频,只保持CPU唤醒即可。
2. 用前台服务托管播放(核心!)
从Android 8.0开始,后台服务会被系统限制,一段时间后就会被杀死。所以必须把播放逻辑放到前台服务里,通过显示通知告诉系统:这个服务在执行重要的媒体播放任务,不能随便回收。
ExoPlayer提供了PlayerNotificationManager工具类,能自动帮你生成符合要求的前台通知,还能同步播放状态:
class RadioPlaybackService : Service() { private lateinit var player: ExoPlayer private lateinit var notificationManager: PlayerNotificationManager override fun onCreate() { super.onCreate() // 初始化播放器(复用上面的配置) val audioAttributes = AudioAttributes.Builder() .setContentType(C.CONTENT_TYPE_MUSIC) .setUsage(C.USAGE_MEDIA) .build() player = ExoPlayer.Builder(this) .setAudioAttributes(audioAttributes, true) .setWakeMode(C.WAKE_MODE_NETWORK) .build() // 配置通知渠道(Android 8.0+必需) val channelId = "radio_playback_channel" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( channelId, "电台播放", NotificationManager.IMPORTANCE_LOW ) getSystemService(NotificationManager::class.java).createNotificationChannel(channel) } // 初始化PlayerNotificationManager notificationManager = PlayerNotificationManager.Builder(this, 1001, channelId) .setMediaDescriptionAdapter(object : PlayerNotificationManager.MediaDescriptionAdapter { override fun getCurrentContentTitle(player: Player): CharSequence { return "你的电台名称" } override fun createCurrentContentIntent(player: Player): PendingIntent? { // 点击通知打开主界面 val intent = Intent(this@RadioPlaybackService, MainActivity::class.java) return PendingIntent.getActivity( this@RadioPlaybackService, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) } override fun getCurrentContentText(player: Player): CharSequence? { return "正在播放当前节目" } override fun getCurrentLargeIcon(player: Player, callback: PlayerNotificationManager.BitmapCallback): Bitmap? { // 可选:设置电台通知图标 return BitmapFactory.decodeResource(resources, R.drawable.ic_radio_icon) } }) .setNotificationListener(object : PlayerNotificationManager.NotificationListener { override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) { super.onNotificationCancelled(notificationId, dismissedByUser) // 用户取消通知时,停止播放并销毁服务 player.stop() stopSelf() } }) .build() notificationManager.setPlayer(player) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { // 启动前台服务,PlayerNotificationManager会自动处理通知的显示 return START_STICKY } override fun onDestroy() { super.onDestroy() // 释放资源 notificationManager.setPlayer(null) player.release() } override fun onBind(intent: Intent): IBinder? { return null } }
3. 声明必要的权限与服务
在AndroidManifest.xml里添加权限和注册服务:
<!-- 前台服务权限 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- 唤醒锁权限 --> <uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- 网络电台需要的网络权限 --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 注册播放服务,Android 12+需要指定foregroundServiceType --> <service android:name=".RadioPlaybackService" android:foregroundServiceType="mediaPlayback" />
4. 额外注意:处理音频焦点
虽然我们在初始化播放器时已经设置了setAudioAttributes(audioAttributes, true)让ExoPlayer自动处理音频焦点(比如来电时暂停,来电结束后恢复),如果需要自定义行为,可以添加AudioFocusListener来监听焦点变化事件,不过默认逻辑已经能满足大部分电台应用的需求。
总结
把这几点结合起来,就能解决后台锁屏播放停止的问题:
- 前台服务保证系统不会轻易杀死播放进程;
- 唤醒模式让CPU/网络保持活跃,不会进入休眠;
- 正确的音频属性声明让系统知道这是高优先级的媒体播放任务。
内容的提问来源于stack exchange,提问作者Vadim Fedchuk




