當咱們在settings中試聽鈴聲,這時候忽然來了一個電話,那麼會出現試聽鈴聲和來電鈴聲同時播放的狀況。固然,此狀況一樣適用於鬧鐘鈴聲,媒體音樂播放等。那麼怎麼解決這個問題呢?這就須要當音頻焦點。---》java
由於系統中可能會有多個應用程序會播放音頻,因此須要考慮他們之間該如何交互,爲了不多個應用程序同時播放音樂,Android 系統使用音頻焦點來進行統一管理,即只有得到了音頻焦點的應用程序才能夠播放音樂。 您的應用程序在開始播放音頻文件前,首先應該請求得到音頻焦點,而且應該同時註冊監聽音頻焦點的丟失通知,即若是音頻焦點被系統或其餘的應用程序搶佔時,您的應用程序能夠作出合適的響應。android
首先,我要獲取一個音頻焦點並管理它。ide
private boolean requestFocus() { // Request audio focus for playback int result = mAudioManager.requestAudioFocus(afChangeListener, AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() { public void onAudioFocusChange(int focusChange) { if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { // Pause playback if (mLocalPlayer !=null && mLocalPlayer.isPlaying()){ mLocalPlayer.pause(); } } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { // Resume playback startLocalPlayer(); } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { mAudioManager.abandonAudioFocus(afChangeListener); // Stop playback if (mLocalPlayer !=null && mLocalPlayer.isPlaying()){ mLocalPlayer.stop(); } } } };
能夠很清晰的看見,上面的第一個方法是獲取音頻焦點,經過requestAudioFocus()來實現。而第二個方法就是對音頻焦點進行監聽並管理。在這裏,要先知道以上幾個值的含義:post
知道了以上幾個字段的含義,在對應的狀態,咱們就能作相應的處理。好比AUDIOFOCUS_LOSS_TRANSIENT短暫失去焦點,咱們就暫停咱們的音樂。AUDIOFOCUS_LOSS長期失去焦點,就直接停掉音樂。AUDIOFOCUS_GAIN我獲取了焦點,那麼我就要開始播放音樂了(因爲我徹底獲取了焦點,其餘音樂就沒法播放了,天然當前就只有一個音樂進行播放)。this
獲取音頻焦點,就要釋放音頻焦點:(在哪裏釋放,就看當時的代碼吧)spa
private void destroyLocalPlayer() { if (mLocalPlayer != null) { mLocalPlayer.reset(); mLocalPlayer.release(); mLocalPlayer = null; synchronized (sActiveRingtones) { sActiveRingtones.remove(this); } } mAudioManager.abandonAudioFocus(afChangeListener); }
在解決這個問題的時候,我選擇在每次播放試聽鈴聲時,獲取音頻焦點(什麼時候獲取,也要看當時代碼狀況):rest
private void startLocalPlayer() { if (mLocalPlayer == null) { return; } synchronized (sActiveRingtones) { sActiveRingtones.add(this); } mLocalPlayer.setOnCompletionListener(mCompletionListener); if(requestFocus()){ mLocalPlayer.start(); } }
成功獲取到焦點,才能夠播放當前的試聽鈴聲哦!code
================================================================================================server
更新!更新!這樣的改法果真引入了一個嚴重的bug,就是來電鈴聲不能播放!blog
爲何呢?首先看一下來電鈴聲播放的代碼:
private void handlePlay(SomeArgs args) { RingtoneFactory factory = (RingtoneFactory) args.arg1; Call incomingCall = (Call) args.arg2; args.recycle(); // don't bother with any of this if there is an EVENT_STOP waiting. if (mHandler.hasMessages(EVENT_STOP)) { return; } // If the Ringtone Uri is EMPTY, then the "None" Ringtone has been selected. Do not play // anything. if(Uri.EMPTY.equals(incomingCall.getRingtone())) { mRingtone = null; return; } ThreadUtil.checkNotOnMainThread(); if (mRingtone == null) { mRingtone = factory.getRingtone(incomingCall); if (mRingtone == null) { Uri ringtoneUri = incomingCall.getRingtone(); String ringtoneUriString = (ringtoneUri == null) ? "null" : ringtoneUri.toSafeString(); Log.event(null, Log.Events.ERROR_LOG, "Failed to get ringtone from factory. " + "Skipping ringing. Uri was: " + ringtoneUriString); return; } } handleRepeat(); } private void handleRepeat() { if (mRingtone == null) { return; } if (mRingtone.isPlaying()) { Log.d(this, "Ringtone already playing."); } else { mRingtone.play(); } // Repost event to restart ringer in {@link RESTART_RINGER_MILLIS}. synchronized(this) { if (!mHandler.hasMessages(EVENT_REPEAT)) { mHandler.sendEmptyMessageDelayed(EVENT_REPEAT, RESTART_RINGER_MILLIS); } } }
代碼路徑:packages\services\Telecomm\src\com\android\server\telecom\AsyncRingtonePlayer.java
來電鈴聲播放是mRingtone.play()這一句,也就是說,最終也是用到了Ringtone.java這個類,而且會調用到startLocalPlay()這個方法,而在這個方法裏,剛纔咱們首先獲取了音頻焦點,並設定獲取成功才能播放。我經過追加log發現,來電鈴聲的時候,音頻焦點獲取失敗了,這讓我很費解,爲何試聽鈴聲就可以獲取成功,來電鈴聲就不行呢?因而繼續向下分析:
mAudioManager.requestAudioFocus(afChangeListener,
AudioManager.STREAM_RING,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
首先進到AudioManager裏看一看這個requestAudioFocus方法,最後一直追蹤到AudioService裏,看一看這裏的代碼:
public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb, IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags, IAudioPolicyCallback pcb) { // permission checks if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) { if (AudioSystem.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) { if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE)) { Log.e(TAG, "Invalid permission to (un)lock audio focus", new Exception()); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } } else { // only a registered audio policy can be used to lock focus synchronized (mAudioPolicies) { if (!mAudioPolicies.containsKey(pcb.asBinder())) { Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus"); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } } } } return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd, clientId, callingPackageName, flags); }
代碼路徑:frameworks\base\services\core\java\com\android\server\audio\AudioService.java
上述方法中的AudioSystem.IN_VOICE_COMM_FOCUS_ID的註釋是:
/** * Constant to identify a focus stack entry that is used to hold the focus while the phone * is ringing or during a call. Used by com.android.internal.telephony.CallManager when * entering and exiting calls. */ public final static String IN_VOICE_COMM_FOCUS_ID = "AudioFocus_For_Phone_Ring_And_Calls";
這就顯而易見了,原來來電鈴聲註冊音頻焦點就會失敗。因此requestFocus的判斷加在這裏並不合適。因此我將代碼改爲:
private void startLocalPlayer() { if (mLocalPlayer == null) { return; } synchronized (sActiveRingtones) { sActiveRingtones.add(this); } mLocalPlayer.setOnCompletionListener(mCompletionListener); requestFocus(); mLocalPlayer.start(); }
判斷是否成功什麼的,去見鬼吧!