Android 官方文档解析:两个或两个以上的 Android 应用可同时向同一输出流播放音频。系统会将所有音频流混合在一起。虽然这是一项出色的技术,但却会给用户带来很大的困扰。为了避免所有音乐应用同时播放,Android 引入了“音频焦点”的概念。 一次只能有一个应用获得音频焦点。
当您的应用需要输出音频时,它需要请求获得音频焦点,获得焦点后,就可以播放声音了。不过,在您获得音频焦点后,您可能无法将其一直持有到播放完成。其他应用可以请求焦点,从而占有您持有的音频焦点。如果发生这种情况,您的应用应暂停播放或降低音量,以便于用户听到新的音频源。
音频焦点采用合作模式。我们建议应用遵守音频焦点准则,但系统不会强制执行这些准则。如果应用想要在失去音频焦点后继续大声播放,系统无法阻止它。这是一种不好的体验,用户很可能会卸载具有这种不良行为的应用。
行为恰当的音频应用应根据以下一般准则来管理音频焦点:
- 在即将开始播放之前调用
requestAudioFocus()
,并验证调用是否返回[AUDIOFOCUS_REQUEST_GRANTED](https://developer.android.google.cn/reference/android/media/AudioManager#AUDIOFOCUS_REQUEST_GRANTED)
。如果按照本指南中的说明设计应用,则应在媒体会话的onPlay()
回调中调用requestAudioFocus()
。 - 在其他应用获得音频焦点时,停止或暂停播放,或降低音量。
- 播放停止后,放弃音频焦点。
运行的 Android 版本不同,音频焦点的处理方式也会不同:
- 从 Android 2.2(API 级别 8)开始,应用通过调用
[requestAudioFocus()](https://developer.android.google.cn/reference/android/media/AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener,%20int,%20int))
和[abandonAudioFocus()](https://developer.android.google.cn/reference/android/media/AudioManager#abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener))
来管理音频焦点。应用还必须为这两个调用注册[AudioManager.OnAudioFocusChangeListener](https://developer.android.google.cn/reference/android/media/AudioManager.OnAudioFocusChangeListener)
,以便接收回调并管理自己的音量。 - 对于以 Android 5.0(API 级别 21)及更高版本为目标平台的应用,音频应用应使用
[AudioAttributes](https://developer.android.google.cn/reference/android/media/AudioAttributes)
来描述应用正在播放的音频类型。例如,播放语音的应用应指定[CONTENT_TYPE_SPEECH](https://developer.android.google.cn/reference/android/media/AudioAttributes#CONTENT_TYPE_SPEECH)
。 - 面向 Android 8.0(API 级别 26)或更高版本的应用应使用
[requestAudioFocus()](https://developer.android.google.cn/reference/android/media/AudioManager#requestAudioFocus(android.media.AudioFocusRequest))
方法,该方法会接受[AudioFocusRequest](https://developer.android.google.cn/reference/android/media/AudioFocusRequest)
参数。AudioFocusRequest
包含有关应用的音频上下文和功能的信息。系统使用这些信息来自动管理音频焦点的得到和失去。
处理音频焦点一些规则
以下是官方建议的处理音频焦点应该遵循的一些规则:
- 在开始播放之前,调用requestAudioFocus()方法,并检查返回值是否是
AUDIOFOCUS_REQUEST_GRANTED
,若成功获取,则开始播放。
当App失去音频焦点时,根据失去的焦点类型,应该暂停播放,或者将音量调低。
当播放结束时,释放音频焦点。 - 音频焦点如何处理
- 处理音频焦点都是通过AudioManager这个类,如下是获得该类实例的方法:
AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
下面介绍音频焦点处理相关的一些方法(不同的Android版本处理音频焦点的方式略有差别,以下内容基于Android 4.4)
requestAudioFocus():申请音频焦点
abandonAudioFocus(): 释放音频焦点
AudioManager.OnAudioFocusChangeListener接口,提供了onAudioFocusChange()方法来监听音频焦点变化
- 5.1
requestAudioFocus
参数:
AudioManager.OnAudioFocusChangeListener
用于监听音频焦点变化,从而可以进行适当的操作,例如暂停播放等。
streamType
申请音频焦点处理的音频类型,例如,当播放音乐时,可以传入STREAM_MUSIC
;当播放铃声时,可以传入STREAM_RING
。表中列出了所有的可选值:durationHint
可选值有以下五个:
(1)AUDIOFOCUS_GAIN
: 此参数表示希望申请一个永久的音频焦点,并且希望上一个持有音频焦点的App停止播放;例如在需要播放音乐时。
(2)AUDIOFOCUS_GAIN_TRANSIENT
:表示申请一个短暂的音频焦点,并且马上就会被释放,此时希望上一个持有音频焦点的App暂停播放。例如播放一个提醒声音。
(3)AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
:效果同AUDIOFOCUS_GAIN_TRANSIENT
,只是希望上一个持有焦点的App减小其播放声音(但仍可以播放),此时会混音播放。例如导航播报。
(4) AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: 表示申请一个短暂的音频焦点,并且会希望系统不要播放任何突然的声音(例如通知,提醒等),例如用户在录音。
返回值:AUDIOFOCUS_REQUEST_GRANTED或者AUDIOFOCUS_REQUEST_FAILED
- 5.2 abandonAudioFocus(OnAudioFocusChangeListener l)
5.2.1 播放完成后,请调用abandonAudioFocus()
audioManager.abandonAudioFocus(afChangeListener)
5.2.2 这会通知系统您不再需要焦点,并注销关联的 OnAudioFocusChangeListener
。如果您请求的是暂时性焦点,则会通知已暂停或降低音量的应用它可以继续播放或恢复其音量。
- 5.3 AudioManager.OnAudioFocusChangeListener
当音频焦点发生变化时,可以在OnAudioFocusChangeListener
的onAudioFocusChange(int focusChange)方法中监听到,下面详细说明该方法。
onAudioFocusChange(int focusChange) - 参数:
focusChange
可以表明当前音频焦点发生的是何种变化,需要根据该参数状态做出正确的响应。
分为获得和丢失两种情况:
获得:AUDIOFOCUS_GAIN - 5.3.1 表示获得音频焦点,此时应该开始播放,丢失音频焦点,这时分为以下两种情况:
- 5.3.2 短暂的丢失:
如果focusChange
的值是AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK或者 AUDIOFOCUS_LOSS_TRANSIENT时,你的App需要降低播放音量 或者暂停播放(但是需要记录当前的播放状态,以便后续恢复播放)。
在短暂丢失焦点期间,你的App应该持续关注音频焦点的变化,当再次获得焦点时,恢复播放。 - 5.3.3 永久的丢失
若值为AUDIOFOCUS_LOSS
,你的App应该立即停止播放,并且当再次获得音频焦点时,也不会恢复播放,只有当用户主动播放时,再开始播放
- 构建 AudioFocusRequest来请求和放弃音频焦点:
audioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
playbackAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(playbackAttributes)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(afChangeListener, handler)
.build();
mediaPlayer = new MediaPlayer();
final Object focusLock = new Object();
boolean playbackDelayed = false;
boolean playbackNowAuthorized = false;
// ...
int res = audioManager.requestAudioFocus(focusRequest);
synchronized(focusLock) {
if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
playbackNowAuthorized = false;
} else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
playbackNowAuthorized = true;
playbackNow();
} else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
playbackDelayed = true;
playbackNowAuthorized = false;
}
}
// ...
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
if (playbackDelayed || resumeOnFocusGain) {
synchronized(focusLock) {
playbackDelayed = false;
resumeOnFocusGain = false;
}
playbackNow();
}
break;
case AudioManager.AUDIOFOCUS_LOSS:
synchronized(focusLock) {
resumeOnFocusGain = false;
playbackDelayed = false;
}
pausePlayback();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
synchronized(focusLock) {
resumeOnFocusGain = true;
playbackDelayed = false;
}
pausePlayback();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// ... pausing or ducking depends on your app
break;
}
}
}