引子
說 Audio Focus 前先說個很簡單需求:來電時暫停正在播放的音樂,電話結束時恢復播放。
看到這個需求,第一反應肯定是:監聽用戶來電狀態,作相應操作。這裏不多做介紹,這樣做有個不好的地方就是需要隱私權限!這樣做一點也不優雅。
後來搜索時看到一篇分析文章:Android來電時停止音樂播放的流程(順便說一嘴,這篇轉載居然不註明出處!!)。文章裏的分析很明確的指出,系統在框架層就很好的幫我們處理了這個需求,問題是如何將音樂交給系統框架來處理呢?
音頻焦點
問題的解決方法就是:請求系統的音頻焦點(Request the Audio Focus)。
如果英文還行,強烈建議請看官方的原文:Managing Audio Playback,裏面介紹的很清楚。以下爲簡單概述。
官方文檔指出Android 在處理音頻播放是分了多個“音頻流”的,如音樂流、音效流、電話聲音流等,使控制音量時可以互不干涉。多數情況下我們播放音樂都是使用 STREAM_MUSIC 音頻流。
另外,系統中可能會有多個應用程序會播放音頻,所以需要考慮他們之間該如何協調,爲了避免同時播放音樂,Android 系統使用音頻焦點來進行統一管理,即只有獲得了音頻焦點的應用程序纔可以播放音樂。
那麼,播放音頻應該這樣來做:
- 獲取音頻焦點 requestAudioFocus
- 獲取成功後,開始播放音頻
- 處理音頻焦點的丟失和“DUCK”
- 播放完畢後取消焦點
如此便可以完美的解決引子裏的需求。
一個簡單的示例
MusicService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
|
public class MusicService extends Service { private AudioManager mAm; private boolean isPlaymusic; private String url; private MediaPlayer mediaPlayer; public void onCreate() { super.onCreate(); mAm = (AudioManager) getSystemService(AUDIO_SERVICE); } public void onStart(Intent intent, int startId) { if (intent != null) { Bundle bundle = intent.getExtras(); if (bundle != null) { isPlaymusic = bundle.getBoolean("isPlay", true); url = bundle.getString("url"); if (isPlaymusic) play(); else stop(); } } } OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() { public void onAudioFocusChange(int focusChange) { if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) { // Pause playback pause(); } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { // Resume playback resume(); } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { // mAm.unregisterMediaButtonEventReceiver(RemoteControlReceiver); mAm.abandonAudioFocus(afChangeListener); // Stop playback stop(); } } }; private boolean requestFocus() { // Request audio focus for playback int result = mAm.requestAudioFocus(afChangeListener, // Use the music stream. AudioManager.STREAM_MUSIC, // Request permanent focus. AudioManager.AUDIOFOCUS_GAIN); return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } private void resume() { if (mediaPlayer != null) { mediaPlayer.start(); } } private void pause() { if (mediaPlayer != null && mediaPlayer.isPlaying()) { mediaPlayer.pause(); } } OnCompletionListener completionListener = new OnCompletionListener() { public void onCompletion(MediaPlayer player) { if(!player.isLooping()){ mAm.abandonAudioFocus(afChangeListener); } } }; private void play() { if (requestFocus()) { if (mediaPlayer == null) { try { mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(url); mediaPlayer.prepare(); mediaPlayer.setOnCompletionListener(completionListener); } catch (IOException e) { e.printStackTrace(); } } if (!mediaPlayer.isPlaying()) { mediaPlayer.start(); } } } public void onDestroy() { super.onDestroy(); if (mediaPlayer != null) mediaPlayer.release(); } private void stop() { if (mediaPlayer != null) { mediaPlayer.stop(); } } public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub return null; } }
|
經模擬器測試,當來電時音頻焦點會給到鈴聲流,並打出日誌:
I/AudioService(1235): AudioFocus requestAudioFocus() from AudioFocus_For_Phone_Ring_And_Calls
此時MusicService
中的afChangeListener
會得到AUDIOFOCUS_LOSS_TRANSIENT
,於是會暫停播放音頻。
當通話結束或者掛掉電話,afChangeListener
會得到AUDIOFOCUS_GAIN
,於是恢復播放音頻。
注意:
- 播放完畢一定要禁止掉請求的音頻焦點
abandonAudioFocus(afChangeListener)
,否則,如果播放完畢後的某個時段剛好有個通話結束,並且此時沒有其他的應用佔用了焦點,系統會重新通知服務裏的afChangeListener
,導致音頻再次的播放。 - 如果丟失的短暫音頻焦點允許DUCK狀態
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
,在這種情況下,應用程序降低音量繼續播放,不需要暫停。再次獲取後,恢復原來的音量。