Android调用startForeground报Bad notification异常解决方法
问题描述
开发音乐播放器应用实现播放通知前台服务时,调用startForeground()方法抛出如下致命异常:
E/AndroidRuntime: FATAL EXCEPTION: main Process: com.aashushaikh.musicplayer, PID: 19998 android.app.RemoteServiceException: Bad notification for startForeground at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2005) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7656) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
已尝试过相关公开解决方案,均未生效。
已提供的相关代码
ApplicationClass.kt
class ApplicationClass: Application() { companion object{ const val CHANNEL_ID = "channel1" const val PLAY = "play" const val NEXT = "next" const val PREVIOUS = "previous" const val EXIT = "exit" const val NOTIFICATION_ID = 0 } override fun onCreate() { super.onCreate() if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ //Creating the channel for the notification val notificationChannel = NotificationChannel(CHANNEL_ID, "Now Playing Song", NotificationManager.IMPORTANCE_HIGH) notificationChannel.description = "This is an Important Channel for showing the songs" notificationChannel.lockscreenVisibility = Notification.VISIBILITY_SECRET //Manager is necessary to create the notification channel val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(notificationChannel) } } }
MusicService.kt(通知构建片段)
val notification = NotificationCompat.Builder(baseContext, ApplicationClass.CHANNEL_ID) .setContentTitle(PlayerActivity.musicListPlayerActivity[PlayerActivity.songPosition].title) .setContentText(PlayerActivity.musicListPlayerActivity[PlayerActivity.songPosition].artist) .setSmallIcon(R.drawable.ic_playlist) .setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.splash_screen)) .setStyle(androidx.media.app.NotificationCompat.MediaStyle().setMediaSession(mediaSession.sessionToken)) .setPriority(NotificationCompat.PRIORITY_LOW) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setOnlyAlertOnce(true) .addAction(R.drawable.ic_previous, "Previous", null) .addAction(R.drawable.ic_pause, "Pause", null) .addAction(R.drawable.ic_next, "Next", null) .addAction(R.drawable.ic_exit, "Exit", null) .build() startForeground(1, notification)
PlayerActivity.kt
在onServiceConnected回调中调用上述showNotification()方法,完整代码如下:
class PlayerActivity : AppCompatActivity(), ServiceConnection { private lateinit var binding: ActivityPlayerBinding companion object{ lateinit var musicListPlayerActivity: ArrayList<Music> var songPosition: Int = 0 var isPlaying: Boolean = false var musicService: MusicService? = null } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setTheme(R.style.coolPink) binding = ActivityPlayerBinding.inflate(layoutInflater) setContentView(binding.root) //Starting the service val intent = Intent(this, MusicService::class.java) bindService(intent, this, BIND_AUTO_CREATE) startService(intent) initializeLayout() binding.playPauseBtnPA.setOnClickListener { if(isPlaying == true){ pauseMusic() }else{ playMusic() } } binding.previousBtnPA.setOnClickListener { prevNextSong(false) } binding.nextBtnPA.setOnClickListener { prevNextSong(true) } } private fun setLayout(){ Glide.with(this).load(musicListPlayerActivity[songPosition].artUri) .apply (RequestOptions.placeholderOf(R.drawable.ic_noimage)) .into(binding.imagePlayer) binding.songNamePlayer.text = musicListPlayerActivity[songPosition].title } private fun createMediaPlayer(){ try { musicService?.mediaPlayer?.let { musicService!!.mediaPlayer!!.reset() musicService!!.mediaPlayer!!.setDataSource(musicListPlayerActivity[songPosition].path) musicService!!.mediaPlayer!!.prepare() musicService!!.mediaPlayer!!.start() isPlaying = true } ?: kotlin.run { musicService!!.mediaPlayer = MediaPlayer() } } catch (e: Exception) { return } } private fun initializeLayout(){ songPosition = intent.getIntExtra("index", 0) when(intent.getStringExtra("class")){ "MusicAdapter" -> { musicListPlayerActivity = ArrayList() musicListPlayerActivity.addAll(MainActivity.musicListMA) setLayout() } "MainActivity" -> { musicListPlayerActivity = ArrayList() musicListPlayerActivity.addAll(MainActivity.musicListMA) musicListPlayerActivity.shuffle() setLayout() } } } private fun playMusic(){ binding.playPauseBtnPA.setIconResource(R.drawable.ic_pause) isPlaying = true musicService!!.mediaPlayer!!.start() } private fun pauseMusic(){ binding.playPauseBtnPA.setIconResource(R.drawable.ic_play) isPlaying = false musicService!!.mediaPlayer!!.pause() } private fun prevNextSong(increment: Boolean){ if(increment){ setSongPosition(true) setLayout() createMediaPlayer() }else{ setSongPosition(false) setLayout() createMediaPlayer() } } private fun setSongPosition(increment: Boolean){ if(increment){ if(musicListPlayerActivity.size - 1 == songPosition){ songPosition = 0 }else{ ++songPosition } }else{ if(songPosition == 0){ songPosition = musicListPlayerActivity.size - 1 }else{ --songPosition } } } override fun onServiceConnected(name: ComponentName?, service: IBinder?) { var binder = service as MusicService.MyBinder musicService = binder.currentService() createMediaPlayer() musicService!!.showNotification() } override fun onServiceDisconnected(p0: ComponentName?) { musicService = null } }
修复方案
该报错由通知配置与前台服务系统规则不匹配触发,按以下点逐一修改即可解决:
- 修正通知渠道与通知本身的配置冲突:你在
ApplicationClass中给通知渠道设置了lockscreenVisibility = Notification.VISIBILITY_SECRET,但构建通知时又设置了setVisibility(NotificationCompat.VISIBILITY_PUBLIC),参数冲突会直接触发系统通知校验失败,将渠道的可见性改为Notification.VISIBILITY_PUBLIC即可,音乐播放常驻通知不需要设置为SECRET级别。 - 对齐通知渠道与通知的优先级:你给渠道设置了
IMPORTANCE_HIGH(会触发弹窗、提示音),但通知本身设置的是PRIORITY_LOW,优先级不匹配也会被系统拦截,将渠道重要度改为NotificationManager.IMPORTANCE_LOW即可,和通知优先级对齐。 - 禁止使用0作为前台服务通知ID:Android 12及以上版本对前台服务通知ID为0的情况会直接抛出Bad notification异常,你虽然当前调用
startForeground传了ID=1,但建议统一使用你定义的非0常量值作为通知ID,避免后续修改出错。 - 确认
mediaSession初始化时机:你在MediaStyle中传入了mediaSession.sessionToken,要确保调用showNotification()时mediaSession已经完成初始化、不为空,否则媒体样式通知构建失败也会触发该异常。 - 补充通知权限适配:如果targetSdkVersion >= 33,需要在AndroidManifest.xml中添加
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />,并在应用启动时动态申请通知权限,Android 13及以上版本中,未获得通知权限时调用startForeground()会直接抛出该异常。 - 检查通知小图标规范:确保
R.drawable.ic_playlist是符合Android通知规范的小图标(纯透明背景、白色图案的矢量/位图资源),不要使用带彩色填充背景的图片作为小图标,否则在部分厂商ROM上会判定为无效通知。
修改后的通知渠道创建代码参考:
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ val notificationChannel = NotificationChannel(CHANNEL_ID, "Now Playing Song", NotificationManager.IMPORTANCE_LOW) notificationChannel.description = "Channel for playing song notification" notificationChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC notificationChannel.setShowBadge(false) val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(notificationChannel) }
另外不要在onServiceConnected中立刻调用showNotification(),最好把通知构建、startForeground的调用逻辑放到MusicService自身的onCreate或onStartCommand生命周期中,避免服务未完全启动就调用前台服务方法触发异常。
内容的提问来源于stack exchange,提问作者Aashu Shaikh
相关产品推荐
相关产品推荐

