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

Android后台音频服务停止时应用崩溃问题排查

问题描述

我开发了一款可在后台播放音频的Android应用,音频能正常播放并在后台运行,但点击停止服务按钮时,音频虽停止但应用随即崩溃。

相关代码

启动按钮触发函数

fun startHandler(view: View) {
    val audioName = "myAudio"
    val serviceIntent = Intent(this, TheService::class.java)
    serviceIntent.putExtra("name", audioName);
    startForegroundService(serviceIntent)
}

停止按钮触发函数

fun stopHandler(view: View) {
    val serviceIntent = Intent(this, TheService::class.java)
    stopService(serviceIntent)
}

服务端onDestroy()函数

override fun onDestroy() {
    super.onDestroy()
    audioPlayer.stop()
}

服务端onStartCommand()函数

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    val runnable = Runnable {
        val audioName = intent?.getStringExtra("name")
        val audioID = resources.getIdentifier(audioName,"raw", packageName)
        audioPlayer = MediaPlayer.create(this, audioID)
        audioPlayer?.setLooping(true)
        audioPlayer.start()
    }

    val thread = Thread(runnable)
    thread.start()

    return START_NOT_STICKY
}

排查问题与解决方向

  • 空指针异常风险:onDestroy()中直接调用audioPlayer.stop(),但audioPlayer可能未完成初始化、已被释放,或因线程执行顺序问题处于未就绪状态。必须先判空并检查播放状态,同时释放资源:
    override fun onDestroy() {
        super.onDestroy()
        audioPlayer?.let {
            if (it.isPlaying) {
                it.stop()
            }
            it.release()
            audioPlayer = null
        }
    }
    
  • 线程安全问题:audioPlayer在子线程初始化,但onDestroy()在主线程执行,可能出现主线程访问未初始化完成的audioPlayer导致空指针。建议将MediaPlayer初始化逻辑移到主线程,或给audioPlayer操作加同步锁,确保初始化完成后再执行停止操作。
  • 前台服务规范缺失:调用startForegroundService()后,必须在10秒内调用startForeground()显示前台通知,否则系统会强制终止服务。虽然当前崩溃不是直接由此引发,但不符合Android规范,可能触发其他异常,需补充前台通知逻辑:
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 需先创建通知渠道(Android 8.0+要求)
        val notification = NotificationCompat.Builder(this, "AUDIO_CHANNEL")
            .setContentTitle("音频播放中")
            .setContentText("后台运行")
            .setSmallIcon(R.drawable.ic_audio)
            .build()
        startForeground(1, notification)
    
        // 原有播放逻辑
        val runnable = Runnable {
            val audioName = intent?.getStringExtra("name")
            val audioID = resources.getIdentifier(audioName,"raw", packageName)
            audioPlayer = MediaPlayer.create(this, audioID)
            audioPlayer?.setLooping(true)
            audioPlayer?.start()
        }
        Thread(runnable).start()
    
        return START_NOT_STICKY
    }
    
  • 重复创建MediaPlayer实例:多次点击启动按钮会创建多个线程初始化audioPlayer,导致旧实例未被释放,新实例覆盖引用,停止时操作的是新实例,旧实例仍占用资源引发冲突。需在onStartCommand()中先检查是否已有运行中的实例:
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        audioPlayer?.let {
            if (it.isPlaying) {
                return START_NOT_STICKY
            }
        }
        // 原有初始化逻辑
    }
    

内容的提问来源于stack exchange,提问作者Michel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 17:37:01