开始之前先来一个 Google Developer 链接镇楼,本文主要基于Android P版本的音频焦点机制的梳理学习
什么是音频焦点管理
官方的解释是两个或两个以上的 Android 应用可同时向同一输出流播放音频。系统会将所有音频流混合在一起。虽然这是一项出色的技术,但却会给用户带来很大的困扰。为了避免所有音乐应用同时播放,Android 引入了“音频焦点”的概念。 一次只能有一个应用获得音频焦点。
当您的应用需要输出音频时,它需要请求获得音频焦点,获得焦点后,就可以播放声音了。不过,在你的应用获得音频焦点后,可能无法将其一直持有到播放完成。其他应用可以请求焦点,从而占有你持有的音频焦点。如果发生这种情况,你的应用应暂停播放或降低音量,以便于用户听到新的音频源。
音频焦点采用合作模式。建议应用遵守音频焦点准则,但系统不会强制执行这些准则。如果应用想要在失去音频焦点后继续大声播放,系统无法阻止它。在这种情况下,就会给用户造成一种不好的体验。
音频焦点的申请
android 8.0前后有差异,我们主要看android 8.0及其以后的
//1、先获取一个AudioManager
audioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
//2、创建AudioAttributes,AudioAttributes 描述了应用的用例。系统会在应用获得和失去音频焦点时查看这些属性。这些属性取代了音频流类型的概念。
mAudioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
//3、创建AudioFocusRequest,其中有如下重要的字段
mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setFocusGain(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(mAudioAttributes)
.setWillPauseWhenDucked(true)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
Log.d("kevin", " foucs change type = " + focusChange);
switch (focusChange) {
case AudioManager.AUDIOFOCUS_LOSS:
Log.d("kevin", "AudioManager.AUDIOFOCUS_LOSS");
//失去焦点,暂停处理,暂停播放当前音乐
//你会长时间的失去焦点,所以不要指望在短时间内能获得。请结束自己的相关音频工作并做好收尾工作。比如另外一个音乐播放器开始播放音乐了,前提是这个另外的音乐播放器他也实现了音频焦点的控制,
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
Log.d("kevin", "AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");
//你的焦点会短暂失去,但是你可以与新的使用者共同使用音频焦点
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
Log.d("kevin", "AudioManager.AUDIOFOCUS_LOSS_TRANSIENT");
//你会短暂的失去音频焦点,你可以暂停音乐,但不要释放资源,因为你一会就可以夺回焦点并继续使用
//如听音乐过程中,收到电话
break;
case AudioManager.AUDIOFOCUS_GAIN:
Log.d("kevin", "AudioManager.AUDIOFOCUS_GAIN");
//播放操作
//你已经完全获得了音频焦点
break;
default:
Log.d("kevin", "Unknown audio focus change code");
}
}
})
.build();
//4、请求获得音频焦点
audioManager.requestAudioFocus(mFocusRequest);
看了上述代码,可能会有几个疑问:
1、音频流类型的概念,Android为不同的应用在不同的场合定义了不同的流类型
电话:STREAM_VOICE_CALL
系统:STREAM_SYSTEM
铃声:STREAM_RING
音乐:STREAM_MUSIC
闹钟:STREAM_ALARM
通知:STREAM_NOTIFICATION
蓝牙:STREAM_BLUETOOTH_SCO
其他国家的提示音:STREAM_SYSTEM_ENFORCED
双音多频:STREAM_DTMF
TTS: STREAM_TTS
这部分Stream Type可以自己扩展
2、setFocusGain()
每个请求中必要的字段,可以理解为告诉系统,该应用需要使用焦点多长时间
AUDIOFOCUS_GAIN 长时间持有音频焦点
AUDIOFOCUS_GAIN_TRANSIENT 只希望在短时间内播放音频,类似通知
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 只希望在短时间内播放音频,并允许前一个持有焦点的应用在降低其音频输出的情况下继续播放,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 用于表示对音频焦点的临时请求,类似录音等请求操作
3、setWillPauseWhenDucked()
当其他应用使用 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 请求焦点时,持有焦点的应用通常不会收到 onAudioFocusChange() 回调,因为系统可以自行降低音量。如果您需要暂停播放而不是降低音量,请调用 setWillPauseWhenDucked(true),然后创建并设置 OnAudioFocusChangeListener
4、setAcceptsDelayedFocusGain
当焦点被其他应用锁定时,对音频焦点的请求可能会失败。此方法可实现延迟获取焦点,即在焦点可用时异步获取焦点。请注意,要使“延迟获取焦点”起作用,您还必须在音频请求中指定 AudioManager.OnAudioFocusChangeListener,因为您的应用必须收到回调才能知道自己获取了焦点
5、setOnAudioFocusChangeListener
一般来说,在原生请求中指定了 willPauseWhenDucked(true) 或 setAcceptsDelayedFocusGain(true) 时,才需要 OnAudioFocusChangeListener。
音频焦点的申请流程
AudioManager requestAudioFocus(AudioFocusRequest focusRequest)
--> requestAudioFocus(AudioFocusRequest afr, AudioPolicy ap)
public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {
//将AudioFocusRequest 中listener 保存到一个ConcurrentHashMap中
registerAudioFocusRequest(afr);
//关于AudioPolicy ap 这个参数我们后面再说
...
//获取IAudioService
final IAudioService service = getService();
...
//通过binder 调用AudioService的requestAudioFocus
synchronized (mFocusRequestsLock) {
try {
// TODO status contains result and generation counter for ext policy
status = service.requestAudioFocus(afr.getAudioAttributes(),
afr.getFocusGain(), mICallBack,
mAudioFocusDisp