转自以下两篇 《深入理解Android:卷III A》一一3.4AudioFocus机制的实现 AudioFocus的申请与释放
- 场景:当听音乐并且在刷朋友圈或者微博的时候,我们想点击一个视频看,此时音乐会暂停只保留视频播放,然后退出视频后音乐又重新播放;
在Android2.2时引入了AudioFocus机制来对Audio资源的竞争进行管理与协调;需要Audio资源双方都实现该机制才能完成当一方失去或者得到焦点时,另一方能够进行反应:停止播放或者降低音量的效果; - 获取AudioManager实例:
AudioManager magager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
- 请求音频的焦点
requestAudioFocus (AudioManager.OnAudioFocusChangeListener l,int streamType,int durationHint)
参数一:申请成功后对AudioFocus的监听;
参数二:音频类型
STREAM_ALARM :手机闹铃, STREAM_MUSIC :手机音乐
STREAM_RING :电话铃声, STREAM_SYSTEAM :手机系统
STREAM_DTMF :音调, STREAM_NOTIFICATION :系统提示
STREAM_VOICE_CALL :语音电话
参数三:
AudioManager.AUDIOFOCUS_GAIN :申请者希望长时间获得AudioFocus
AudioManager.AUDIOFOCUS_LOSS : 当前的使用者将长时间失去了Audio Focus,需要停止Audio的播放,并且释放Media资源。为了避免再次自动获得AudioFocus而继续播放,不然突然冒出来的声音会让用户感觉莫名其妙,直接放弃AudioFocus,如果需要再次播放,用户要在界面上点击开始播放,才重新初始化Media,进行播放。
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT :暂时失去AudioFocus,并会很快再次获得。必须停止Audio的播放,但是因为是暂时失去AudioFocus,可以不释放Media资源;
AUDIOFOCUS_GAIN_TRANSIENT :暂时获取焦点 适用于短暂的音频
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK :应用跟其他应用共用焦点但播放的时候其他音频会降低音量
返回值:
AUDIOFOCUS_REQUEST_GRANTED:申请成功;
AUDIOFOCUS_REQUEST_FAILED:申请失败;
- 放弃音频的焦点
abandonAudioFocus (AudioManager.OnAudioFocusChangeListenerl) - AudioFocus被抢占与再次获取的时序
- Client通过requestAudioFocus()获取AudioFocus,在获得AudioFocus之后,开始播放Audio;
- 其它程序(Other App)也通过requestAudioFocus()获取同类AudioStream的AudioFocus
- Client失去了AudioFocus,在onAudioFocusChanged()中,根据focusChange的值,做相应的处理(暂停or播放);
- 其它程序(Other App)获取AudioFocus之后,开始播放Audio;
- 其它程序(Other App)使用Audio之后,通过abandonAudioFocus()归还AudioFocus;
- Client重新获得了Audio Focus,可做进一步的处理
特殊情景:
AudioFocus的含义可以和Windows的窗口焦点机制做类比,只不过我们的焦点对象是音频的回放实例。在同一时间,只能有一个音频回放实例拥有焦点。每个回放实例开始播放前, 必须向AudioService申请获取AudioFocus,只有申请成功才允许开始回放。在回放实例播放结束后,要求释放AudioFocus。在回放实例播放的过程中,AudioFocus有可能被 其他回放实例抢走,这时,被抢走AudioFocus的回放实例需要根据情况采取暂停、静音或降低音量的操作,以突出拥有AudioFocus的回放实例的播放。当AudioFocus被还回来时, 回放实例可以恢复被抢走之前的状态,继续播放。 总体上来说,AudioFocus是一个没有优先级概念的抢占式的机制。在一般情况下后一个申请者都能从前一个申请者的手中获取AudioFocus。不过只有一个例外,就是通话。通话作 为手机的首要功能,同时也是一种音频的播放过程,所以从来电铃声开始到通话结束这个过程,Telephony相关的模块也会申请AudioFocus,但是它的优先级是最高的。Telephony 可以从所有人手中抢走AudioFocus,但是任何人无法从它手中将其夺回。这在后面的代码分析中可以看到。 值得一提的是,AudioFocus机制完全是一个建议性而不是强制性的机制。也就是说,上述的行为是建议回放实例遵守,而不是强制的。所以,市面上仍有一些带有音频播放功能的应 用没有采用这套机制。
例子1:
AudioFocus相关的API一共有三个,分别是requestAudioFocus()、abandonAudioFocus()以及AudioFocusListener回调接口,它们分别负责AudioFocus的申请、释放以及变化通知。
为了让大家了解AudioFocus的实现原理,我们先来看一个AudioFocus使用方法。下面是一个播放器的部分代码
public void play(){
// 在开始播放前,先申请AudioFocus,注意传入的参数
int result = mAudioManager.requestAudioFocus(
mAudioFocusListener,AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
// 只有成功申请到AudioFocus之后才能开始播放
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
mMediaPlayer.start();
else // 申请失败,如果系统没有问题,这一定是正在通话过程中,所以,还是不要播放了
showMessageCannotStartPlaybackDuringACall(); //提示用户暂时无法播放
}
public void stop() {
// 停止播放时,需要释放AudioFocus
mAudioManager.abandonAudioFocus(mAudioFocusListener);
}
在开始播放前,应用首先要申请AudioFocus。申请AudioFocus时传入了3个参数。
mAudioFocusListener:参数名为l。AudioFocus变化通知回调。当AudioFocus被其他AudioFocus使用者抢走或归还时将通过这个回调对象进行通知。
AudioManager.STREAM_MUSIC: 参数名为streamType。这个参数表明了申请者即将使用哪种流类型进行播放。目前这个参数仅提供一项信息而已,对AudioFocus的机制没有任何影响,不过Android在后续的升级中可能会使用此参数。
AudioManager.AUDIOFOCUS_GAIN: 参数名为durationHint。这个参数指明了申请者将拥有这个AudioFocus多长时间。例子中传入的AUDIOFOCUS_GAIN表明了申请者将长期占用这个AudioFocus。另外还有两个可能的取值,它们是AUDIOFOCUS_GAIN_TRANSIENT和AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK。这两个取值的含义都表明申请者将暂时占用AudioFocus,不同的是,后者还指示了即将被申请者抢走AudioFocus的使用者不需要暂停,只要降低一下音量就可以了(这就是“DUCK”的意思)。
在停止播放时,需要调用abandonAudioFocus释放AudioFocus,会将其归还给之前被抢 走AudioFocus的那个使用者。
接下来,我们看一下mAudioFocusListener是如何实现的。
private onAudioFocusChangeListenermAudioFocusListener =
newOnAudioFocusChangeListener(){
// 当AudioFocus发生变化时,这个函数将会被调用。其中参数focusChange指示发生了什么变化
publicvoidonAudioFocusChange(int focusChange){
switch(focusChange) {
// AudioFocus被长期夺走,需要中止播放,并释放AudioFocus
// 这种情况对应于抢走AudioFocus的申请者使用了AUDIOFOCUS_GAIN
case AUDIOFOCUS_LOSS:
stop();
break;
// AudioFocus被临时夺走,不久就会被归还,只需要暂停,AudioFocus被归还后再恢
复播放
// 这对应于抢走AudioFocus的申请者使用了AUDIOFOCUS_GAIN_TRANSIENT
case AUDIOFOCUS_LOSS_TRANSIENT:
saveCurrentPlayingState();
pause();
break;
// AudioFocus被临时夺走,允许不暂停,所以降低音量
// 这对应于抢走AudioFocus的回放实例使用了AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
case AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
saveCurrentPlayingState();
setVolume(getVolume()/2);
break;
// AudioFocus被归还,这时需要恢复被夺走前的播放状态
case AUDIOFOCUS_GAIN:
restorePlayingState();
break;
}
}
};
从这里能够看出,AudioFocus机制的逻辑是完整而清晰的。理解上述例子以后,相信AudioFocus的工作原理已经浮现在脑海里了吧?如果有兴趣,可以不用着急阅读下面的分析,先思考一下AudioFocus可能的工作原理,然后再与Android的实现进行比较。
从调用requestAudioFocus()进行申请到abandonAudioFocus()释放的这段时间内,只能说这个使用者参与了AudioFocus机制,不能保证一直拥有AudioFocus。
释放焦点
private void abandonAudioFocus() {
if (audioFocusState == AUDIO_FOCUS_STATE_NO_FOCUS) {
return;
}
if (Util.SDK_INT >= 26) {
abandonAudioFocusV26();
} else {
abandonAudioFocusDefault();
}
setAudioFocusState(AUDIO_FOCUS_STATE_NO_FOCUS);
}
注意:app进程杀死之后,在AudioService中通过监听Binder mCallback也会自动释放焦点