Android音频服务启动崩溃:ForegroundServiceDidNotStartInTimeException
当ViewModel调用AudioController的setQueue方法时,音频播放器能正常启动,但几秒后崩溃,通知无法显示,报错信息:
android.app.ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{e4dbb13 u0 ../...audio.AudioPlayerService}
实现代码
AudioController类
class AudioController(val context: Context){ private val player = ExoPlayer.Builder(context).build() private val _state = MutableStateFlow(PlayerState()) init{player.addListener(object : Player.Listener {...})} private fun emitState() {//updates the _state based on player} suspend fun setQueue(tracks: List<Track>, startIndex: Int) { val intent = Intent(context, AudioPlayerService::class.java) ContextCompat.startForegroundService(context, intent) val items = tracks.map { MediaItem.Builder() .setMediaId("${it.id}") .setUri(it.url) .setMediaMetadata(...) .build() } withContext(Dispatchers.Main) { player.setMediaItems(items, startIndex, 0L) player.prepare() player.playWhenReady = false emitState() } } }
AudioPlayerService类
class AudioPlayerService : MediaLibraryService() { var mediaLibrarySession: MediaLibrarySession? = null var audioController = getKoin().get<AudioController>() private val player: ExoPlayer by lazy { audioController.getPlayer() } private lateinit var notificationProvider: DefaultMediaNotificationProvider private val callback = object : MediaLibrarySession.Callback {...} override fun onCreate() { super.onCreate() notificationProvider = DefaultMediaNotificationProvider.Builder(this) .setNotificationId(NOTIFICATION_ID) .setChannelId(NOTIFICATION_CHANNEL_ID) .build() setMediaNotificationProvider(notificationProvider) mediaLibrarySession = MediaLibrarySession .Builder(this, player, callback) .setCustomLayout(listOf(getCorrectBookmarkButton())) .build() } override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? = mediaLibrarySession override fun onDestroy() { mediaLibrarySession?.run { player.release() release() mediaLibrarySession = null } super.onDestroy() } }
Application类(创建通知渠道)
class MyApplication : Application() { override fun onCreate() { super.onCreate() createNotificationChannel(this) //koin init happens here } } private fun createNotificationChannel(context: Context) { val channel = NotificationChannel( "player_channel", "Audio Playback", NotificationManager.IMPORTANCE_HIGH ).apply { description = "Shows the currently playing audio" } val manager = getSystemService(context,NotificationManager::class.java) manager?.createNotificationChannel(channel) }
解决方案
崩溃的核心原因是:调用startForegroundService()启动前台服务后,必须在10秒内调用Service.startForeground(),否则系统会抛出该异常。以下是具体修复步骤:
在
AudioPlayerService的onCreate中立即调用startForeground
利用已初始化的notificationProvider生成基础通知,确保服务快速进入前台状态:override fun onCreate() { super.onCreate() notificationProvider = DefaultMediaNotificationProvider.Builder(this) .setNotificationId(NOTIFICATION_ID) .setChannelId(NOTIFICATION_CHANNEL_ID) .build() setMediaNotificationProvider(notificationProvider) mediaLibrarySession = MediaLibrarySession .Builder(this, player, callback) .setCustomLayout(listOf(getCorrectBookmarkButton())) .build() // 关键:生成通知并启动前台服务 val notification = notificationProvider.createNotification(player, null) startForeground(NOTIFICATION_ID, notification) }确保通知渠道ID完全匹配
检查NOTIFICATION_CHANNEL_ID常量的值是否和Application中创建的"player_channel"一致,否则通知无法正常创建,会导致startForeground失败。优化服务启动与播放器准备的时序
将服务启动逻辑优先执行,避免播放器准备的耗时操作占用前台服务启动的超时窗口:suspend fun setQueue(tracks: List<Track>, startIndex: Int) { // 优先启动服务 val intent = Intent(context, AudioPlayerService::class.java) ContextCompat.startForegroundService(context, intent) // 播放器准备逻辑延后执行 withContext(Dispatchers.Main) { player.setMediaItems(tracks.map { MediaItem.Builder() .setMediaId("${it.id}") .setUri(it.url) .setMediaMetadata(...) .build() }, startIndex, 0L) player.prepare() player.playWhenReady = false emitState() } }适配Android 12+权限要求
针对Android 12及以上版本,需要在Manifest中声明POST_NOTIFICATIONS权限,并在运行时主动请求该权限,否则通知无法显示。
内容的提问来源于stack exchange,提问作者Jaimin Vashi

