Android是多任務系統,Audio系統是競争資源。Android2.2之前,沒有内建的機制來解決多個程式競争Audio的問題,2.2引入了稱作AudioFocus的機制來管理對Audio資源的競争的管理與協調。本文主要講解AudioFocus的使用。
按 照AudioFocus的機制,在使用Audio之前,需要申請AudioFocus,在獲得AudioFocus之後才可以使用Audio;如果有别的 程式競争你正在使用的Audio,你的程式需要在收到通知之後做停止播放或者降低聲音的處理。值得指出的是,這種機制是需要合作完成的,需要所有使用 Audio資源的程式都按照這種機制來做,而如果有程式在它失去AudioFocus的時候仍然在使用Audio,AudioFocus拿它也沒辦法。而 這一點對于開放系統的Android來說很緻命的:使用者可能安裝沒遵守這種機制的程式,或者版本太老還沒引入這種機制的程式,這最終會導緻很差的使用者體 驗。
對于手機方案公司來說,要做的能做的事情就是教育和教育訓練團隊成員以保證自己内建的程式遵守機制沒問題,這包括了Android原生的程式、自己開發的程式,以及适配第三方的程式。
一、AudioFocus的申請與釋放
下面看與AudioFocus的相關的類:

擷取/放棄AudioFocus的方法都在android.media.AudioManager中,擷取AudioFocus用
requestAudioFocus()
;用完之後,放棄AudioFocus,用
abandonAudioFocus()
。
其中,參數 :
- streamType 是《Android中的Audio播放:音量和遠端播放控制》 中說明的AudioStream,其值取決于AudioManager中的STREAM_xxx,在AudioStream的裁決機制中并未有什麼實際意義;
- durationHint 是持續性的訓示:
-
訓示申請得到的Audio Focus不知道會持續多久,一般是長期占有;AUDIOFOCUS_GAIN
-
訓示要申請的AudioFocus是暫時性的,會很快用完釋放的;AUDIOFOCUS_GAIN_TRANSIENT
- AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 不但說要申請的AudioFocus是暫時性的,還訓示目前正在使用AudioFocus的可以繼續播放,隻是要“duck”一下(降低音量)。
-
- AudioManager.OnAudioFocusChangeListener 是申請成功之後監聽AudioFocus使用情況的Listener,後續如果有别的程式要競争AudioFocus,都是通過這個Listener的onAudioFocusChange() 方法來通知這個Audio Focus的使用者的。
傳回值 ,可能是:
- AUDIOFOCUS_REQUEST_GRANTED :申請成功;
- AUDIOFOCUS_REQUEST_FAILED :申請失敗。
二、AudioFocus被搶占與重新獲得
由上節中知道,申請/釋放AudioFocus時傳入了AudioManager.OnAudioFocusChangeListener 這個參數,其onAudioFocusChange() 方法是Audio Focus被搶占與再次獲得通知的地方。是以,每個要使用AudioFocus的程式都要小心實作這個函數,保證AudioFocus實作的一緻性。
onAudioFocusChange() 方法的focusChange參數訓示了該AudioFocus的競争者對AudioFocus的擁有情況,取值如下:
- AUDIOFOCUS_GAIN :獲得了Audio Focus;
- AUDIOFOCUS_LOSS : 失去了Audio Focus,并将會持續很長的時間。這裡因為可能會停掉很長時間,是以不僅僅要停止Audio的播放,最好直接釋放掉Media資源。而因為停止播放 Audio的時間會很長,如果程式因為這個原因而失去AudioFocus,最好不要讓它再次自動獲得AudioFocus而繼續播放,不然突然冒出來的 聲音會讓使用者感覺莫名其妙,感受很不好。這裡直接放棄AudioFocus,當然也不用再偵聽遠端播放控制【如下面代碼的處理】。要再次播放,除非使用者再 在界面上點選開始播放,才重新初始化Media,進行播放。
- AUDIOFOCUS_LOSS_TRANSIENT :暫時失去Audio Focus,并會很快再次獲得。必須停止Audio的播放,但是因為可能會很快再次獲得AudioFocus,這裡可以不釋放Media資源;
-
:暫時失去AudioFocus,但是可以繼續播放,不過要在降低音量。AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
下面是onAudioFocusChange() 方法處理的代碼片段:
[java] view plain copy
- 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機制。
圖中:
- AudioFocus Client通過requestAudioFocus()擷取AudioFocus,在獲得AudioFocus之後,開始播放Audio[Step#1 ~ #2];
- 其它程式(Other App)也通過requestAudioFocus()擷取AudioFocus [Step#3]
- AudioFocus Client失去了Audio Focus,在onAudioFocusChanged()中,根據focusChange【focusChange的值與Other App申請時的durationHint相反,即focusChange = -1*durationHint】的值,做第二節中所描述的處理[Step#4];
- 其它程式(Other App)擷取Audio Focus之後,開始播放Audio[Step#5];
- 其它程式(Other App)使用Audio之後,通過
歸還AudioFocus [Step#6];abandonAudioFocus()
- AudioFocus Client重新獲得了Audio Focus,可做進一步的處理 [Step#7]
小結
Audio Focus機制要參與各方充分了解并統一遵照施行,有沒有遵照者或者實作有誤的程式存在就可能打破這一機制,帶來糟糕的使用者體驗。在保證Built-in 程式沒問題的前提下,如果進入AndroidMarket之前的程式都嚴格執行了AudioFocus相關的測試,應該也沒問題。
使用Audio的程式要做到:
- 使用前,用requestAudioFocus()申請AudioFocus,并根據應用的實際選取恰當的durationHint值;
- 正确的在AudioManager.OnAudioFocusChangeListener 中響應AudioFocus失去和重新擷取事件;
- Audio使用結束,用
歸還AudioFocus。abandonAudioFocus()
問題點以及進一步的探讨
- 内部裁決機制怎樣的?
- 申請的不同 Audio Stream 之間是不存在競争的嗎?
【更新】
1. AudioFocus中雖然把AudioStream作為參數,但是AudioFocus的内部裁決機制并未針對AudioStream做什麼特别的處 理。AudioFocus的處理針對所有的申請者來說的,除了它自身内部作為Alert的申請者有點特殊外,其它一律平等。是以文中,去掉 AudioStream的描述。
2. 閱讀AudioFocus内部實作機制後,對一些描述更加明确化。