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
相关产品推荐
相关产品推荐

