You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android音频服务启动崩溃:ForegroundServiceDidNotStartInTimeException

问题: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 06:34:54