开发媒体播放器:应用被杀死时如何保持通知持久显示?
我来帮你一步步分析和解决这个问题:
核心问题分析
你遇到的情况是:应用被杀死后通知消失,但音乐仍在播放,这说明你的Service还在后台运行,但前台状态被系统移除了,导致通知消失;而未杀死应用时通知正常,说明正常播放流程里的前台服务设置是没问题的。另外锁屏通知没有控制按钮,也和通知的配置、MediaSession的设置有关。
具体解决方案
1. 必须创建NotificationChannel(Android O+ 强制要求)
从Android 8.0(API 26)开始,所有通知都必须关联到一个NotificationChannel,否则通知可能无法正常显示,或者在应用被杀死后被系统自动清理。你需要在创建通知前添加channel的创建代码:
private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channelId = "media_player_channel" val channelName = "Media Player Controls" val channelDescription = "Notification channel for media playback controls" val importance = NotificationManager.IMPORTANCE_HIGH // 高优先级确保通知显示在锁屏且有声音提示 val channel = NotificationChannel(channelId, channelName, importance).apply { description = channelDescription setShowBadge(false) lockscreenVisibility = Notification.VISIBILITY_PUBLIC // 确保锁屏可见 } val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) } }
然后在buildNotification方法开头调用这个函数,并且在Notification.Builder里指定channelId:
var builder: Notification.Builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Notification.Builder(this, "media_player_channel") } else { Notification.Builder(this) } // 接着设置其他属性...
2. 确保Service在播放期间始终保持前台状态
当音乐正在播放时,你的Service必须一直处于前台状态,不能随意退出前台。目前你在pauseMedia里调用了stopForeground(false),这会让Service退出前台但保留通知,这没问题;但要确保在恢复播放时,重新调用startForeground,把Service变回前台状态。
另外,当用户杀死应用时,系统可能会触发onTaskRemoved方法,你可以在这里重新激活前台服务,避免通知消失:
override fun onTaskRemoved(rootIntent: Intent?) { super.onTaskRemoved(rootIntent) // 如果音乐还在播放,重新启动前台服务 if (mp?.isPlaying == true) { // 重新构建通知并调用startForeground val playPauseAction = if (mp!!.isPlaying) { generateAction(Icon.createWithResource(baseContext, R.drawable.pause), "Pause", ACTION_PAUSE) } else { generateAction(Icon.createWithResource(baseContext, R.drawable.play), "Play", ACTION_PLAY) } buildNotification(playPauseAction) } }
3. 正确关联MediaSession并设置回调
你的MediaStyle已经关联了MediaSession,但需要确保MediaSession被正确激活,并且设置了回调来处理播放状态变化,这样系统才能正确识别播放状态,维护通知的存在:
private lateinit var mediaSession: MediaSessionCompat override fun onCreate() { super.onCreate() // 初始化MediaSession mediaSession = MediaSessionCompat(this, "MediaPlayerSession") mediaSession.isActive = true // 激活MediaSession // 设置回调处理播放控制 mediaSession.setCallback(object : MediaSessionCompat.Callback() { override fun onPlay() { // 恢复播放逻辑 resumeMedia() } override fun onPause() { // 暂停播放逻辑 pauseMedia() } override fun onRewind() { // 快退逻辑 mp?.seekTo(mp!!.currentPosition - 10000) } override fun onFastForward() { // 快进逻辑 mp?.seekTo(mp!!.currentPosition + 30000) } }) } override fun onDestroy() { super.onDestroy() mediaSession.isActive = false // 销毁时关闭MediaSession mediaSession.release() // 释放MediaPlayer,避免音乐继续播放 mp?.stop() mp?.release() mp = null stopForeground(true) }
4. 修复锁屏通知没有控制按钮的问题
锁屏通知没有控制按钮,主要是因为MediaSession没有正确设置播放状态,以及通知的MediaStyle没有正确配置。确保在播放状态变化时,更新MediaSession的播放状态:
比如在播放开始时:
private fun startMedia() { mp?.start() // 更新MediaSession状态 mediaSession.setPlaybackState(PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_PLAYING, mp!!.currentPosition, 1.0f) .setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE or PlaybackStateCompat.ACTION_REWIND or PlaybackStateCompat.ACTION_FAST_FORWARD) .build()) // 调用buildNotification更新通知 buildNotification(generateAction(Icon.createWithResource(baseContext, R.drawable.pause), "Pause", ACTION_PAUSE)) }
暂停时:
private fun pauseMedia() { if (mp!!.isPlaying) { mp!!.pause() resumePosition = mp!!.currentPosition // 更新MediaSession状态 mediaSession.setPlaybackState(PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_PAUSED, mp!!.currentPosition, 1.0f) .setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE or PlaybackStateCompat.ACTION_REWIND or PlaybackStateCompat.ACTION_FAST_FORWARD) .build()) stopForeground(false) } }
这样系统就能识别播放状态,在锁屏上显示对应的控制按钮了。
额外注意点
- 确保你的
MediaPlayerService是用startForegroundService启动的(Android O+ 要求),而不是普通的startService。 - 在
onDestroy里一定要释放MediaPlayer,避免音乐在Service销毁后继续播放的异常情况。
内容的提问来源于stack exchange,提问作者kn0wmad1c

