FROM: http://www.linuxidc.com/Linux/2012-04/57902.htm
================================================
Android是多任务系统,Audio系统是竞争资源。Android2.2之前,没有内建的机制来解决多个程序竞争Audio的问题,2.2引入了称作AudioFocus的机制来管理对Audio资源的竞争的管理与协调。本文主要讲解AudioFocus的使用。
按照AudioFocus的机制,在使用AudioStream之前,需要申请AudioFocus,在获得AudioFocus之后才可以使用相应的AudioStream;如果有别的程序竞争你正在使用的AudioStream,你的程序需要在收到通知之后做停止播放或者降低声音的处理。值得指出的是,这种机制是需要合作完成的,需要所有使用Audio资源的程序都按照这种机制来做,而如果有程序在它失去AudioFocus的时候仍然在使用Audio,AudioFocus拿它也没办法。而这一点对于开放系统的Android来说很致命的:用户可能安装没遵守这种机制的程序,或者版本太老还没引入这种机制的程序,这最终会导致很差的用户体验。
对于手机方案公司来说,要做的能做的事情就是教育和培训团队成员以保证自己内建的程序遵守机制没问题,这包括了Android原生的程序、自己开发的程序,以及适配第三方的程序。
一、AudioFocus的申请与释放
下面看与AudioFocus的相关的类:
获取/放弃AudioFocus的方法都在android.media.AudioManager中,获取AudioFocus用requestAudioFocus()
;用完之后,放弃AudioFocus,用abandonAudioFocus()
。
其中,参数:
AUDIOFOCUS_GAIN
指示申请得到的Audio Focus不知道会持续多久,一般是长期占有;AUDIOFOCUS_GAIN_TRANSIENT
指示要申请的AudioFocus是暂时性的,会很快用完释放的;
返回值,可能是:
二、AudioFocus被抢占与重新获得
由上节中知道,申请/释放AudioFocus时传入了AudioManager.OnAudioFocusChangeListener这个参数,其onAudioFocusChange()方法是Audio Focus被抢占与再次获得通知的地方。所以,每个要使用AudioFocus的程序都要小心实现这个函数,保证AudioFocus实现的一致性。
onAudioFocusChange()方法的focusChange参数指示了该AudioFocus的竞争者对AudioFocus的拥有情况,取值如下:
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
:暂时失去AudioFocus,但是可以继续播放,不过要在降低音量。
下面是onAudioFocusChange()方法处理的代码片段:
- OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {
- public void onAudioFocusChange(int focusChange) {
- if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
- // Pause playback
- } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
- am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
- am.abandonAudioFocus(afChangeListener);
- // Stop playback
- } else if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
- // Lower the volume
- } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
- // Resume playback or Raise it back to normal
- }
- }
- };
三、典型的应用AudioFocus的场景
下面的时序图描述了AudioFocus被抢占与再次获取的典型场景:
Audio Focus被抢占与再次获取的时序图
注意:为了描述简单,此图中除了两个竞争Audio Focus的App之外,只用AudioManager表征了Android的AudioFocus机制中内部参与的对象,实际AudioManager只是外部的表象,内部参与的对象很多,回调函数也并非简单的直接由AudioManager调用,其中还包含了复杂的IPC机制。
图中:
abandonAudioFocus()
归还AudioFocus [Step#6];
小结
Audio Focus机制要参与各方充分理解并统一遵照施行,有没有遵照者或者实现有误的程序存在就可能打破这一机制,带来糟糕的用户体验。在保证Built-in程序没问题的前提下,如果进入AndroidMarket之前的程序都严格执行了AudioFocus相关的测试,应该也没问题。
相关阅读:Android中的Audio播放:控制Audio输出通道切换 http://www.linuxidc.com/Linux/2012-04/57901.htm
问题点以及进一步的探讨
AudioManager am; AudioManager.OnAudioFocusChangeListener afChangeListener = new AudioManager.OnAudioFocusChangeListener() { @Override public void onAudioFocusChange(int focusChange) { switch (focusChange){ case AudioManager.AUDIOFOCUS_GAIN: //获取音频焦点 SinaLog.d("ccc focusChange = AUDIOFOCUS_GAIN"); mVideoWrapper.play(0); break; case AudioManager.AUDIOFOCUS_LOSS: //永久失去 音频焦点 SinaLog.d("ccc focusChange = AUDIOFOCUS_LOSS"); stopVideo(); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: //暂时失去 音频焦点,并会很快再次获得。必须停止Audio的播放,但是因为可能会很快再次获得AudioFocus,这里可以不释放Media资源 SinaLog.d("ccc focusChange = AUDIOFOCUS_LOSS_TRANSIENT"); stopVideo(); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: //暂时失去 音频焦点 ,但是可以继续播放,不过要在降低音量。 SinaLog.d("ccc focusChange = AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK"); break; default: SinaLog.d("ccc focusChange = " + focusChange); break; } } };
am = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); /** * streamType是《Android中的Audio播放:音量和远程播放控制》中说明的AudioStream,其值取决于AudioManager中的STREAM_xxx; * durationHint是持续性的指示: * AUDIOFOCUS_GAIN指 示申请得到的Audio Focus不知道会持续多久,一般是长期占有; * AUDIOFOCUS_GAIN_TRANSIENT 指示要申请的AudioFocus是暂时性的,会很快用完释放的; * AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 不但说要申请的AudioFocus是暂时性的,还指示当前正在使用AudioFocus的可以继续播放,只是要“duck”一下(降低音量)。 * AudioManager.OnAudioFocusChangeListener 是申请成功之后监听AudioFocus使用情况的Listener,后续如果有别的程序要竞争AudioFocus,都是通过这个Listener的onAudioFocusChange()方法来通知这个Audio Focus的使用者的。 */ int result = am.requestAudioFocus(afChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); if (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == result) { //获取音频焦点成功 SinaLog.d("ccc result = AudioManager.AUDIOFOCUS_REQUEST_GRANTED"); } else if (AudioManager.AUDIOFOCUS_REQUEST_FAILED == result) { //获取音频焦点失败 SinaLog.d("ccc result = AudioManager.AUDIOFOCUS_REQUEST_FAILED"); } else { SinaLog.d("ccc result = " + result); } mVideoWrapper.play(0);
if (am != null) am.abandonAudioFocus(afChangeListener);