AudioManager.OnAudioFocusChangeListener在AUDIOFOCUS_LOSS后失效的解决方法
音频焦点失效问题解决方案
核心问题分析
- 音频属性配置错误:多次调用
setUsage会覆盖之前的设置,最终生效的是最后一个USAGE_ASSISTANCE_SONIFICATION,这会导致系统对音频焦点的处理逻辑不符合音乐播放器的预期。 - 焦点状态处理逻辑错误:收到
AUDIOFOCUS_LOSS(永久失去焦点)时,代码反而执行播放操作,完全违背逻辑;且永久失焦后没有主动放弃焦点,也没有在后续需要时重新请求。 - 错误创建Activity实例:在监听器里直接
new MusicActivity()是无效的,Activity不能通过这种方式实例化来控制播放器,会导致操作无法作用到实际运行的播放器上。 - 焦点监听器生命周期问题:
audioFocusChangeListener是局部变量,可能被GC回收,需要改为Service的成员变量。
修复步骤及代码修改
1. 修正音频属性配置
音乐播放器只需设置USAGE_MEDIA,删除其他重复的setUsage调用:
playbackAttributes = AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build()
2. 修正焦点状态处理逻辑
重新梳理各焦点状态的正确行为,同时将监听器改为Service成员变量避免被回收:
// 声明为Service成员变量 private lateinit var audioFocusChangeListener: AudioManager.OnAudioFocusChangeListener override fun onCreate() { super.onCreate() val idNotification = (System.currentTimeMillis() % 10000).toInt() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForeground(idNotification, Notification.Builder(this, NotificationService.channelId).build()) audioManager = getSystemService(AUDIO_SERVICE) as AudioManager playbackAttributes = AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build() audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange -> Log.d("AudioListener", focusChange.toString()) when (focusChange) { AudioManager.AUDIOFOCUS_GAIN -> { // 获得焦点,恢复播放 if (!resting && player != null && !player!!.isPlaying) { player!!.start() } } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { // 临时失焦,暂停播放 if (player != null && player!!.isPlaying) { player!!.pause() } } AudioManager.AUDIOFOCUS_LOSS -> { // 永久失焦,暂停并放弃焦点 if (player != null && player!!.isPlaying) { player!!.pause() } audioManager.abandonAudioFocusRequest(audioFocusRequest) resting = true } } } val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) .setAudioAttributes(playbackAttributes) .setAcceptsDelayedFocusGain(true) .setOnAudioFocusChangeListener(audioFocusChangeListener) .build() audioFocusRequest = audioManager.requestAudioFocus(focusRequest) } }
3. 移除错误的Activity实例化
不要在Service里直接创建Activity对象,播放器的控制逻辑直接放在Service内部维护,通过Service自身的player实例调用start()/pause()方法。
4. 添加重新请求焦点的逻辑
当用户在永久失焦后再次触发播放(比如通知栏按钮点击),需要重新请求音频焦点:
// 在Service中添加播放恢复方法 fun resumePlayback() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) .setAudioAttributes(playbackAttributes) .setAcceptsDelayedFocusGain(true) .setOnAudioFocusChangeListener(audioFocusChangeListener) .build() val result = audioManager.requestAudioFocus(focusRequest) if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { player?.start() resting = false } } }
5. 完善Service生命周期管理
在onDestroy中释放音频焦点和播放器资源:
override fun onDestroy() { super.onDestroy() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { audioManager.abandonAudioFocusRequest(audioFocusRequest) } player?.release() player = null }
关键注意事项
- 音频焦点的请求和放弃必须成对出现,永久失焦后一定要调用
abandonAudioFocusRequest,否则后续无法正常重新请求。 - Service内的播放器状态要统一维护,避免和Activity状态不一致。
- 针对Android O及以上版本,必须使用
AudioFocusRequest而非旧的requestAudioFocus方法。
内容的提问来源于stack exchange,提问作者Pup In The Tree
相关产品推荐
相关产品推荐

