處理音頻焦點
儘管某個時刻只有一個activity可以運行,Android卻是一個多任務環境.這對使用音訊應用帶來了特殊的挑戰,因為只有一個音訊輸出而可能多個媒體都想用它.在Android2.2之前,沒有內建的機制來處理這個問題,所以可能在某些情況下導致壞的使用者體驗.例如,當一個使用者正在聽音樂而另一個應用需要通知使用者一些重要的事情時,使用者可能由於音樂聲音大而不能聽的通知.從Android2.2開始,平台為應用提供了一個協商它們如何使用裝置音訊輸出的途徑,這個機制叫做音頻焦點.
當你的應用需要輸出像樂音和通知之類的音頻時,你應該總是請求音頻焦點.一旦應用具有了焦點,它就可以自由的使用音訊輸出.但它總是應該監聽焦點的變化.如果被通知丟失焦點,它應該立即殺死聲音或降低到靜音水平(有一個標誌表明應選擇哪一個)並且僅當重新獲得焦點後才恢複大聲播放.
將來的音頻焦點是合作的.所以,應用被希望(並被強列鼓勵)遵守音頻焦點的方針,但是卻不是被系統強制的.如果一個應用在丟失音頻焦點後依然想大聲播放音樂,系統不會去阻止它.然而使用者卻體驗很壞並且很想把這鳥應用卸載.
要請求音頻焦點,你必須從AudioManager調用requestAudioFocus(),如下所示:
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { // 不能獲得音頻焦點}
requestAudioFocus()的第一個參數是一個AudioManager.OnAudioFocusChangeListener,它的onAudioFocusChange()方法在音頻焦點發改變時被調用.因此,你也應該在你的service和activity上實現此介面.例如:
class MyService extends Service implements AudioManager.OnAudioFocusChangeListener { // .... public void onAudioFocusChange(int focusChange) { // Do something based on focus change... }}
參數focusChange告訴你音頻焦點如何發生了變化,它可以是以上幾種值(它們都是定義在AudioManager中的常量):
AUDIOFOCUS_GAIN:你已獲得了音頻焦點.
AUDIOFOCUS_LOSS:你已經丟失了音頻焦點比較長的時間了.你必須停止所有的音頻播放.因為預料到你可能很長時間也不能再獲音頻焦點,所以這裡是清理你的資源的好地方.比如,你必須釋放MediaPlayer.
AUDIOFOCUS_LOSS_TRANSIENT:你臨時性的丟掉了音頻焦點,很快就會重新獲得.你必須停止所有的音頻播放,但是可以保留你的資源,因為你可能很快就能重新獲得焦點.
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:你臨時性的丟掉了音頻焦點,但是你被允許繼續以低音量播放,而不是完全停止.
下面是一個例子:
public void onAudioFocusChange(int focusChange) { switch (focusChange) { case AudioManager.AUDIOFOCUS_GAIN: // resume playback if (mMediaPlayer == null) initMediaPlayer(); else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start(); mMediaPlayer.setVolume(1.0f, 1.0f); break; case AudioManager.AUDIOFOCUS_LOSS: // Lost focus for an unbounded amount of time: stop playback and release media player if (mMediaPlayer.isPlaying()) mMediaPlayer.stop(); mMediaPlayer.release(); mMediaPlayer = null; break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: // Lost focus for a short time, but we have to stop // playback. We don't release the media player because playback // is likely to resume if (mMediaPlayer.isPlaying()) mMediaPlayer.pause(); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: // Lost focus for a short time, but it's ok to keep playing // at an attenuated level if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f); break; }}
記住音頻焦點API僅在APIlevel 8 (Android2.2)及更高版本上可以,所以如果你想支援更早的Android版本,你必須在可能時採取相容性的策略使用特性,如果不可能,you should adopt a backward compatibility strategy that allows you touse this feature if available, and fall back seamlessly if not.
你可以用反射的方式調用音頻焦點方法或自己在一個單獨的類(叫做AudioFocusHelper)中實現所有的音頻焦點功能來達到向前相容.下面是一個這樣的類:
public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener { AudioManager mAudioManager; // 這裡是其它的欄位,你可能要儲存一個介面的引用,這個介面 // 被用於與你的service通訊以報告焦點的變化. public AudioFocusHelper(Context ctx, /* 其它的參數 */) { mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); // ... } public boolean requestFocus() { return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.requestAudioFocus(mContext, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); } public boolean abandonFocus() { return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.abandonAudioFocus(this); } @Override public void onAudioFocusChange(int focusChange) { // 讓你的service知道焦點變化了 }} 你可以僅在檢測到系統啟動並執行是API level 8 或更早的版本時才建立AudioFocusHelper類的執行個體.例如:if (android.os.Build.VERSION.SDK_INT >= 8) { mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);} else { mAudioFocusHelper = null;}
清理
前面提到過,一個MediaPlayer對象可以消耗掉大量的系統資源,所以你應該僅在需要它時保持它並在用完時立即釋放.明確的調用清理方法而不是依靠系統的垃圾收集機制是很重要的,因為在被收集之前MediaPlayer可能會存在很長時間,雖然此時它只是佔用記憶體而不影響其它的媒體相關的資源.所以,當你使用一個service時,你應該總四重寫onDestroy()方法來保證釋放MediaPlayer:
public class MyService extends Service { MediaPlayer mMediaPlayer; // ... @Override public void onDestroy() { if (mMediaPlayer != null) mMediaPlayer.release(); }}
你也應該尋找其它需要釋放你的MediaPlayer的時機.例如,如果你預料到長時間不能播放媒體(比如丟掉音頻焦點以後),你應該明確地釋放你的MediaPlayer,然後在後面重新建立它.反過來,如果你預測到只會短時間停止播放,你應該保持你的MediaPlayer來避免過多的建立,而不是重新"準備"它.