一 、為什么要發(fā)明音頻焦,它是什么?
答:兩個(gè)或兩個(gè)以上的 Android App可同時(shí)向同一輸出流(比如手機(jī)的藍(lán)牙、手機(jī)的喇叭)播放音頻,系統(tǒng)會(huì)將所有音頻流(就是音頻數(shù)據(jù)了)混合在一起。這是一項(xiàng)有意思的技術(shù),但卻會(huì)出現(xiàn)混音。為了避免所有音樂(lè)應(yīng)用同時(shí)播放,Android 引入了“音頻焦點(diǎn)”的概念。 音頻焦點(diǎn)機(jī)制是Android系統(tǒng)提供的一種道德約定,它倡導(dǎo)的東西有三點(diǎn):
????1、 只有一個(gè)App持有音頻焦點(diǎn);
????2 、播放聲音前申請(qǐng)音頻焦點(diǎn),不需要播放的時(shí)候釋放音頻焦點(diǎn);
????3 、失去音頻焦點(diǎn)應(yīng)該暫停播放或者降低音量。
音頻焦點(diǎn)是Android系統(tǒng)進(jìn)程管理的一個(gè)值,這個(gè)值就記錄了當(dāng)前音頻焦點(diǎn)屬于哪個(gè)應(yīng)用,類(lèi)型等。
二、音頻焦點(diǎn)在Android系統(tǒng)中是怎么表示,怎么管理的?
答:音頻焦點(diǎn)的管理以棧的形式維護(hù)在系統(tǒng)進(jìn)程SystemServer->MediaFocusControl中
private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();
棧頂FocusRequester對(duì)象對(duì)應(yīng)的App就是當(dāng)前持有音頻焦點(diǎn)的App。
App成功申請(qǐng)到音頻焦點(diǎn)時(shí),會(huì)在mFocusStack棧頂添加一個(gè)FocusRequester對(duì)象 ,然后通知棧頂對(duì)應(yīng)的App音頻焦點(diǎn)申請(qǐng)成功,通知棧中其他FocusRequester對(duì)象對(duì)應(yīng)的App音頻焦點(diǎn)丟失。
FocusRequester這個(gè)類(lèi)就是音頻焦點(diǎn)表示類(lèi)
/**
* @hide
* Class to handle all the information about a user of audio focus. The lifecycle of each
* instance is managed by android.media.MediaFocusControl, from its addition to the audio focus
* stack, or the map of focus owners for an external focus policy, to its release.
* 隱藏類(lèi) 所有音頻焦點(diǎn)相關(guān)信息封裝類(lèi)。
* 每個(gè)音頻焦點(diǎn)實(shí)例都被MediaFocusControl類(lèi)鎖管理,它管理著音頻焦點(diǎn)的新增與釋放,音頻焦點(diǎn)棧,外部策略的焦點(diǎn)擁有者等等;
*/
public class FocusRequester {
// on purpose not using this classe's name, as it will only be used from MediaFocusControl
private static final String TAG = "MediaFocusControl";
private static final boolean DEBUG = false;
/**
* 它包含一個(gè)iBinder,可以感知焦點(diǎn)申請(qǐng)方(App)是否存活
* 如果App 進(jìn)程被殺掉,就會(huì)通過(guò)iBinder通知到對(duì)應(yīng)的FocusRequester,一般就是
* 通知MediaFocusControl-> mFocusStack中的FocusRequester對(duì)象,從而從mFocusStack移除對(duì)應(yīng)的
* FocusRequester 釋放音頻焦點(diǎn), 且以后其他進(jìn)程釋放焦點(diǎn),也不會(huì)分發(fā)或者通知給它對(duì)應(yīng)的App
*/
private AudioFocusDeathHandler mDeathHandler; // may be null
/**
* 客戶(hù)端回調(diào),當(dāng)這個(gè)FocusRequester對(duì)應(yīng)的App 音頻焦點(diǎn)發(fā)生變化
* 比如重新獲取、丟失時(shí)候會(huì)給客戶(hù)端回調(diào)
*/
private IAudioFocusDispatcher mFocusDispatcher; // may be null
/**
* 這個(gè)iBinder就是這個(gè)FocusRequester 對(duì)應(yīng)的binder服務(wù)端-App
* 它作用有 :
* 1 當(dāng)FocusRequester從焦點(diǎn)管理?xiàng)M顺觯热鏏bdon后,就進(jìn)行釋放,那么以后App生死都不會(huì)通知到 MediaFocusControl
* 2 比較兩個(gè)FocusRequester一般也是通過(guò)他們持有的iBinder對(duì)比,比如當(dāng)一個(gè)應(yīng)用死了,從焦點(diǎn)棧中移除FocusRequester
* 就是通過(guò)對(duì)比他們持有的iBinder
*/
private final IBinder mSourceRef; // may be null
/**
* App傳過(guò)來(lái)的音頻焦點(diǎn)回調(diào)的hashcode值,可以認(rèn)為就是代表一個(gè)具體的回調(diào)- OnAudioFocusChangeListener
* 因?yàn)橐粋€(gè)App可能設(shè)置多個(gè)的音頻焦點(diǎn)回調(diào)
*/
private final @NonNull String mClientId;
/**
* App包名-ApplicationId
*/
private final @NonNull String mPackageName;
/**
* 申請(qǐng)音頻焦點(diǎn)的App的uid
* 音頻焦點(diǎn)的申請(qǐng)一般是App跨進(jìn)程向系統(tǒng)服務(wù)SystemServer進(jìn)程申請(qǐng)的
* Binder類(lèi)提供了可以獲取調(diào)用方uid的方法,然后寫(xiě)入到這個(gè)FocusRequester中。
*/
private final int mCallingUid;
/**
* 這個(gè)就是管理這個(gè)FocusRequester所在的MediaFocusControl,所以這邊是不能為null
*/
private final MediaFocusControl mFocusController; // never null
/**
* 申請(qǐng)焦點(diǎn)的App的targetSdkVersion(App最佳運(yùn)行Android版本,在這個(gè)版本上做了充分測(cè)試和適配)
*/
private final int mSdkTarget;
/**
* the audio focus gain request that caused the addition of this object in the focus stack.
* 標(biāo)記這個(gè)FocusRequester是申請(qǐng)哪種類(lèi)型的音頻焦點(diǎn),比如正常焦點(diǎn)類(lèi)型-AudioManager#AUDIOFOCUS_GAIN
* 短暫獲取的焦點(diǎn)申請(qǐng)-AudioManager#AUDIOFOCUS_GAIN_TRANSIENT 等等
*/
private final int mFocusGainRequest;
/**
* the flags associated with the gain request that qualify the type of grant (e.g. accepting
* delay vs grant must be immediate)
* 音頻焦點(diǎn)是否可以延遲獲取到,通過(guò)AudioFocusRequest的 build的方法setAcceptsDelayedFocusGain(true)設(shè)置
* 如果設(shè)置為true ,那么如果申請(qǐng)的時(shí)候沒(méi)有立即給到申請(qǐng)者,那么當(dāng)其他應(yīng)用釋放后,申請(qǐng)者依舊可以收到音頻焦點(diǎn)
*/
private final int mGrantFlags;
/**
* the audio focus loss received my mFocusDispatcher, is AudioManager.AUDIOFOCUS_NONE if
* it never lost focus.
* 音頻焦點(diǎn)丟失事件是否已經(jīng)收到,如果從來(lái)沒(méi)有收到就是AudioManager.AUDIOFOCUS_NONE,如果已經(jīng)收到
* 那就通過(guò)這個(gè)保證不重復(fù)收到,只有申請(qǐng)者下次重新申請(qǐng)音頻焦點(diǎn)后恢復(fù)狀態(tài)才可能會(huì)繼續(xù)收到焦點(diǎn)丟失事件
*/
private int mFocusLossReceived;
/**
* whether this focus owner listener was notified when it lost focus
* 這個(gè)值根mFocusLossReceived 關(guān)聯(lián)的,記錄是否收到過(guò)音頻焦點(diǎn)丟失事件,如果已經(jīng)收到過(guò)
* 那音頻焦點(diǎn)棧,最上面應(yīng)用釋放音頻焦點(diǎn)后,如果這個(gè)FocusRequester在最上面就,那就重新獲取了音頻焦點(diǎn)
* 需要通知到對(duì)應(yīng)的client,如果沒(méi)有收到過(guò)音頻焦點(diǎn)丟失通知,那么當(dāng)最上面的應(yīng)用釋放音頻焦點(diǎn)后,就算
* 這個(gè)FocusRequester在最上面,也不會(huì)通知對(duì)應(yīng)的client重新獲取音頻焦點(diǎn)
*/
private boolean mFocusLossWasNotified;
/**
* the audio attributes associated with the focus request
* 音頻屬性 -不做展開(kāi)
*/
private final @NonNull AudioAttributes mAttributes;
}
三、App失去音頻焦點(diǎn),還可以播放聲音嗎?
答:可以,但不推薦。上文講到音頻焦點(diǎn)機(jī)制是Android系統(tǒng)提供的一種道德約定,所以也可以不必遵守,當(dāng)失去音頻焦點(diǎn)的時(shí)候依舊我行我素繼續(xù)播放,但這種體驗(yàn)很不好。
????比如你是一個(gè)視頻應(yīng)用,一般在應(yīng)用退到后臺(tái)的時(shí)候或者來(lái)電話(huà)的時(shí)候會(huì)失去音頻焦點(diǎn),這之后應(yīng)用繼續(xù)播放用戶(hù)明顯能感覺(jué)到是這個(gè)視頻應(yīng)用有問(wèn)題。
????再比如一個(gè)放音樂(lè)的應(yīng)用,在后臺(tái)放音樂(lè),你打開(kāi)愛(ài)奇藝看電影,這時(shí)候這個(gè)音樂(lè)程序雖失去了音頻焦點(diǎn),但依舊繼續(xù)放音樂(lè),那我肯定也非常不爽這個(gè)音樂(lè)應(yīng)用。
????綜合來(lái)說(shuō)雖然音頻焦點(diǎn)機(jī)制是Android系統(tǒng)提供的一種道德約定,失去音頻焦點(diǎn)依舊可以播放聲音,但是這樣產(chǎn)生的不好體驗(yàn)很容易被發(fā)現(xiàn),所以大家還是遵守的好。
四、應(yīng)用通過(guò)AudioManager.requestAudioFocus申請(qǐng)音頻焦點(diǎn)后,系統(tǒng)是同步給出申請(qǐng)結(jié)果的嗎?
答:是的,除非當(dāng)時(shí)電話(huà)類(lèi)應(yīng)用占用著音頻焦點(diǎn)+App設(shè)置了 可以延遲獲取焦點(diǎn):
mAudioFocusRequest =
new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(mAudioAttributes)
.setAcceptsDelayedFocusGain(true) // 可以延遲獲取焦點(diǎn)
.setOnAudioFocusChangeListener(mOnAudioFocusChangeListener)
.build();
mAudioManager.requestAudioFocus(mAudioFocusRequest);
這樣等其他應(yīng)用釋放焦點(diǎn)后,如果當(dāng)前申請(qǐng)?jiān)诮裹c(diǎn)管理?xiàng)W钌戏?,那就?huì)接收到獲得到音頻焦點(diǎn)的回調(diào)。
五、看到類(lèi)似Log: 09-24 17:05:01.116 W/MediaFocusControl( 2742): requestAudioFocus() from uid/pid 10060/15667 clientId=android~~~ 就代表申請(qǐng)音頻焦點(diǎn)成功了嗎
答:不是的,這個(gè)只代表申請(qǐng)流程走到了系統(tǒng)SystemServer進(jìn)程,如果有電話(huà)類(lèi)應(yīng)用占用,Binder通信異常等問(wèn)題都會(huì)導(dǎo)致焦點(diǎn)申請(qǐng)失敗,但大部分情況下可以認(rèn)為是申請(qǐng)成功。
六、音頻焦點(diǎn)申請(qǐng)的詳細(xì)流程是怎么樣的?

七、如果播放結(jié)束忘記釋放音頻焦點(diǎn),會(huì)有什么影響?系統(tǒng)會(huì)回收嗎?
答:會(huì)有影響,比如你就需要短暫獲取下焦點(diǎn)做個(gè)提示音,就會(huì)導(dǎo)致比如你播放完提示音
本來(lái)應(yīng)該繼續(xù)放音樂(lè)的App獲取不了焦點(diǎn)不繼續(xù)播放。系統(tǒng)會(huì)回收,但是需要進(jìn)程死掉。
八、看到類(lèi)似Log:09-24 17:05:01.116 W/MediaFocusControl :abandonAudioFocus() from uid/pid代表釋放焦點(diǎn)釋放了嗎?
答:同問(wèn)題五,這個(gè)只代表App申請(qǐng)音頻焦點(diǎn)釋放流程走到系統(tǒng)SystemServer進(jìn)程,如果出現(xiàn)binder通信異常等問(wèn)題,也會(huì)導(dǎo)致音頻焦點(diǎn)釋放失敗,不過(guò)絕大部分時(shí)候我們可以認(rèn)為成功釋放了。
九、 音頻焦點(diǎn)釋放的流程是怎么樣的?

十、音頻焦點(diǎn)申請(qǐng)類(lèi)型AUDIOFOCUS_GAIN_TRANSIENT和AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK的區(qū)別?
答:AUDIOFOCUS_GAIN_TRANSIENT 對(duì)應(yīng) AUDIOFOCUS_LOSS_TRANSIENT
AUDIOFOCUS_GAIN_TRANSIENT 表示 短暫獲得,一會(huì)就釋放焦點(diǎn),比如你只是想發(fā)個(gè)notification時(shí)用下一秒不到的鈴聲。
AUDIOFOCUS_LOSS_TRANSIENT 表示 短暫的失去音頻焦點(diǎn),不要自己主動(dòng)去放棄焦點(diǎn),可以暫停音樂(lè),但不要釋放資源,因?yàn)檫^(guò)系統(tǒng)會(huì)把焦點(diǎn)分發(fā)給繼續(xù)使用。
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 對(duì)應(yīng) AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
duck 、ducking英文是鴨子,鉆入水中,低頭的意思,在這里就是我們就可以理解低頭的意思。
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 表示短暫獲音頻焦點(diǎn),之前的音頻焦點(diǎn)使用者雖然會(huì)丟失音頻焦點(diǎn),但無(wú)需暫停播放,只需要降低音量就好;
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 表示臨時(shí)失去了音頻焦點(diǎn),但可以以較低的音量的播放音頻; 嗨嗨 低頭播放 不與爭(zhēng)鋒
備注:本文基于Android 11/ SDK-30進(jìn)行分析解讀