概念
在手機(jī)的場(chǎng)景中,我們會(huì)看到晓猛,當(dāng)使用一個(gè)音樂播放器播放音樂時(shí),再打開另一個(gè)音樂播放器播放音樂凡辱,這時(shí)候戒职,上一個(gè)應(yīng)用的音樂會(huì)暫停,這就是音頻焦點(diǎn)器的作用透乾。應(yīng)用可以通過調(diào)用AudioManager的requestAudioFocus方法進(jìn)行焦點(diǎn)的請(qǐng)求洪燥,并注冊(cè)O(shè)nAudioFocusChangeListener監(jiān)聽得知焦點(diǎn)的變化。從而控制音頻的播放乳乌,暫停捧韵,資源釋放等邏輯。
1.應(yīng)用請(qǐng)求焦點(diǎn)方法
音頻焦點(diǎn)為了解決同時(shí)處理多個(gè)音頻播放汉操,音頻混亂播放問題制定的邏輯再来。我們應(yīng)用可以通過獲取AudioManager服務(wù),調(diào)用requestAudioFocus方法進(jìn)行調(diào)用磷瘤。下面為一個(gè)音樂播放器請(qǐng)求焦點(diǎn)示例:
1).獲取AudioManager服務(wù)芒篷。
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
2).設(shè)置音頻屬性。設(shè)置音頻的音頻類型及使用場(chǎng)景采缚,在CarAudio中可通過設(shè)置的使用場(chǎng)景分配焦點(diǎn)针炉。
mAudioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
3).創(chuàng)建一個(gè)用來播放音頻的MediaPlayer,將音頻屬性AudioAttributes傳入MediaPlayer中扳抽。
if (mediaPlayer == null){
mediaPlayer = new MediaPlayer();
}
mediaPlayer.setAudioAttributes(mAudioAttributes);
4).實(shí)現(xiàn)OnAudioFocusChangeListener監(jiān)聽篡帕,用來監(jiān)聽焦點(diǎn)變化
AudioManager.OnAudioFocusChangeListener changeListener = focusChange -> {
switch (focusChange){
case AudioManager.AUDIOFOCUS_LOSS://長(zhǎng)期失去焦點(diǎn),停止播放贸呢,可進(jìn)行音頻資源釋放操作
closeMedia();
break;
case AudioManager.AUDIOFOCUS_GAIN://獲取到了當(dāng)前焦點(diǎn)镰烧,繼續(xù)播放音頻
playMedia();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT://暫時(shí)失去焦點(diǎn),暫停播放
stopMedia();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK://暫時(shí)失去焦點(diǎn)贮尉,可播放聲音拌滋,但聲音需減低
break;
}
};
5).構(gòu)造焦點(diǎn)請(qǐng)求,傳入我們申請(qǐng)的焦點(diǎn)類型FocusGain猜谚,音頻屬性和焦點(diǎn)監(jiān)聽器败砂。
mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(mAudioAttributes)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(changeListener)
.build();
6).調(diào)用requestAudioFocus請(qǐng)求焦點(diǎn)赌渣,根據(jù)請(qǐng)求到的結(jié)果,進(jìn)行音頻播放
int res = mAudioManager.requestAudioFocus(mFocusRequest);
switch (res){
case AudioManager.AUDIOFOCUS_REQUEST_FAILED://申請(qǐng)焦點(diǎn)失敗昌犹,不執(zhí)行播放
break;
case AudioManager.AUDIOFOCUS_REQUEST_GRANTED://申請(qǐng)焦點(diǎn)成功坚芜,執(zhí)行播放
isPlayDelayed = false;
playMedia();
break;
case AudioManager.AUDIOFOCUS_REQUEST_DELAYED://申請(qǐng)焦點(diǎn)延時(shí),不執(zhí)行播放斜姥,我們播放音樂時(shí)會(huì)有這種狀態(tài)鸿竖,
//如果返回值為delay時(shí),后續(xù)的播放可在收到對(duì)應(yīng)的焦點(diǎn)變化監(jiān)聽OnAudioFocusChangeListener中執(zhí)行铸敏。
isPlayDelayed = true;
break;
}
音頻進(jìn)行焦點(diǎn)的申請(qǐng)缚忧,大致就分為這幾步,我們需要在焦點(diǎn)申請(qǐng)成功失敗后杈笔,及焦點(diǎn)丟失和重新獲取時(shí)對(duì)音頻進(jìn)行播放暫停處理恐锣。系統(tǒng)的音頻焦點(diǎn)屬于弱管理逗旁,并不是一定得申請(qǐng)焦點(diǎn)后蚤吹,才能進(jìn)行播放娃兽,只是作為建議,建議應(yīng)用做這樣操作禁筏,從而獲得更好的用戶體驗(yàn)持钉。
2.AudioFocusRequest介紹
申請(qǐng)焦點(diǎn)最重要的是構(gòu)建一個(gè)焦點(diǎn)請(qǐng)求對(duì)象AudioFocusRequest,AudioFocusRequest封裝了申請(qǐng)音頻焦點(diǎn)信息篱昔,通過Builder的方式進(jìn)行創(chuàng)建每强, AudioFocusRequest. Builder提供了以下一些方法。
1.Builder(int focusGain)
:Builder構(gòu)造器創(chuàng)建的時(shí)候必須先傳入一個(gè)focusGain必須先傳入申請(qǐng)焦點(diǎn)的類型旱爆,表示應(yīng)用需要請(qǐng)求一個(gè)什么類型的焦點(diǎn)focusGain類型分為以下幾種:
-
AUDIOFOCUS_GAIN
:長(zhǎng)時(shí)間獲取焦點(diǎn)舀射,一般用于音視頻。 -
AUDIOFOCUS_GAIN_TRANSIENT
:短暫獲得怀伦,一般用于電話脆烟,語音助理等 -
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
:混音,一般用于導(dǎo)航 -
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
:與AUDIOFOCUS_GAIN_TRANSIENT類似房待,表示一個(gè)短暫的獲取焦點(diǎn)邢羔,一般用于語音識(shí)別什么的,很少用桑孩。
2.setFocusGain(int focusGain)
:一樣的是傳入focusGain申請(qǐng)的焦點(diǎn)類型
3.setOnAudioFocusChangeListener(@NonNull OnAudioFocusChangeListener listener)
:設(shè)置焦點(diǎn)變化的監(jiān)聽器OnAudioFocusChangeListener
4.setOnAudioFocusChangeListenerInt(OnAudioFocusChangeListener listener, Handler handler)
:設(shè)置焦點(diǎn)變化的監(jiān)聽器拜鹤,同時(shí)傳入一個(gè)Handler
,指定listenter
回調(diào)到handler
線程流椒。代表可以將焦點(diǎn)變化的監(jiān)聽回調(diào)到handler
中執(zhí)行敏簿。而沒有handler
只能默認(rèn)回調(diào)到與我們初始化AudioManager
的那個(gè)線程,容易導(dǎo)致listener
的回調(diào)卡頓,這樣造成的后果就是我們要停止的聲音沒有及時(shí)停止而造成短暫的混音惯裕。
5.setWillPauseWhenDucked(boolean pauseOnDuck)
:當(dāng)有一個(gè)音頻申請(qǐng)的焦點(diǎn)類型為AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
時(shí)温数,之前棧頂?shù)囊纛l焦點(diǎn)的音頻,系統(tǒng)會(huì)自動(dòng)進(jìn)行降音處理蜻势,而這個(gè)方法設(shè)為true
時(shí)就表示撑刺,取消系統(tǒng)的降音處理,自己通過焦點(diǎn)監(jiān)聽器變AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
狀態(tài)時(shí)握玛,應(yīng)用自行進(jìn)行降音處理够傍。
6.setAcceptsDelayedFocusGain(boolean acceptsDelayedFocusGain)
:設(shè)置是否延遲請(qǐng)求焦點(diǎn)。只有設(shè)置為true
挠铲,requestAudioFocus
才能返回AUDIOFOCUS_REQUEST_DELAYED
延時(shí)請(qǐng)求冕屯,之后才可在焦點(diǎn)監(jiān)聽器中進(jìn)行處理。否則需要進(jìn)行延時(shí)處理時(shí)市殷,焦點(diǎn)將直接申請(qǐng)失敗愕撰。
7.setLocksFocus(boolean focusLocked)
:設(shè)置是否鎖定焦點(diǎn),需要設(shè)置為true時(shí)醋寝,需要給AudioManager
傳入一個(gè)AudioPolicy
才能使用。普通應(yīng)用不可使用带迟。
8.setForceDucking(boolean forceDucking)
:當(dāng)使用場(chǎng)景是USAGE_ASSISTANCE_ACCESSIBILITY
且申請(qǐng)的是AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
焦點(diǎn)類型是音羞,會(huì)強(qiáng)制降低其他音源。主要用于無障礙服務(wù)中仓犬。
9.setAudioAttributes(@NonNull AudioAttributes attributes)
:設(shè)置音頻屬性AudioAttributes
嗅绰。AudioAttributes
音頻屬性定義音頻系統(tǒng)如何處理指定源的路由、音量和焦點(diǎn)決策的屬性搀继。系統(tǒng)Audio中很多地方都使用了這個(gè)音頻屬性窘面。AudioArrtibutes
是同過也是通過Builder構(gòu)造器進(jìn)行參數(shù)設(shè)置的。 AudioAttributes
主要的屬性為usage
和contentType
叽躯。
3 AudioAttributes介紹
代碼路徑:/frameworks/base/media/java/android/media/AudioAttributes.java
用來定義音頻系統(tǒng)如何處理指定源的路由财边、音量和焦點(diǎn)決策的屬性。系統(tǒng)Audio中很多地方都使用了這個(gè)音頻屬性点骑。
AudioArrtibutes是同過Builder方法進(jìn)行構(gòu)造的酣难,AudioArrtibutes.Builder類內(nèi)進(jìn)行如下方法構(gòu)建。
方法 | 描述 |
---|---|
setUsage(@AttributeSdkUsage int usage) | 設(shè)置音頻使用場(chǎng)景黑滴,用來描述為什么要播放這個(gè)音頻憨募,是用來導(dǎo)航/鬧鐘/播放媒體? |
setSystemUsage(@AttributeSystemUsage int systemUsage) | 設(shè)置為系統(tǒng)使用的音頻 |
setContentType(@AttributeContentType int contentType) | 設(shè)置音頻內(nèi)容袁辈,用來描述這個(gè)音頻的內(nèi)容是什么菜谣,是語音/音樂.... |
setFlags(int flags) | 設(shè)置標(biāo)志的組合,F(xiàn)lag用來描述這個(gè)音頻的輸出標(biāo)識(shí),比如:低時(shí)延輸出尾膊、強(qiáng)制可聽…… |
setHotwordModeEnabled(boolean enable) | 是否在hotword 模式下請(qǐng)求捕獲甘磨。實(shí)際上是添加一個(gè)FLAG_HW_HOTWORD 的flag
|
setAllowedCapturePolicy(@CapturePolicy int capturePolicy) | 指定音頻是否可以被其他應(yīng)用程序或系統(tǒng)捕獲 |
addBundle(@NonNull Bundle bundle) | 攜帶Bundle 數(shù)據(jù) |
addTag(String tag) | 添加標(biāo)志 |
setLegacyStreamType(int streamType) | 使用舊的streamType 的類型,對(duì)應(yīng)usage眯停,和setUsage 不可同時(shí)使用 |
setCapturePreset(int preset) | 設(shè)置捕獲預(yù)設(shè)济舆。在AudioRecord 錄音時(shí)使用 |
setInternalCapturePreset(int preset) | 在hotword 模式下進(jìn)行捕獲預(yù)設(shè) |
setHapticChannelsMuted(boolean muted) | 設(shè)置播放音頻時(shí),是否可震動(dòng) |
-
setUsage:音頻使用場(chǎng)景莺债,用來描述為什么要播放這個(gè)音頻滋觉,是用來導(dǎo)航/鬧鐘/播放媒體?
* {@link AttributeSdkUsage#USAGE_MEDIA}, * {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION}, * {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING}, * {@link AttributeSdkUsage#USAGE_ALARM}, {@link AudioAttributes#USAGE_NOTIFICATION}, * {@link AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE}, * {@link AttributeSdkUsage#USAGE_NOTIFICATION_COMMUNICATION_REQUEST}, * {@link AttributeSdkUsage#USAGE_NOTIFICATION_COMMUNICATION_INSTANT}, * {@link AttributeSdkUsage#USAGE_NOTIFICATION_COMMUNICATION_DELAYED}, * {@link AttributeSdkUsage#USAGE_NOTIFICATION_EVENT}, * {@link AttributeSdkUsage#USAGE_ASSISTANT}, * {@link AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY}, * {@link AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE}, * {@link AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION}, * {@link AttributeSdkUsage#USAGE_GAME}.
-
setContentType:用來描述這個(gè)音頻的內(nèi)容是什么齐邦,是語音/音樂....
* {@link AudioAttributes#CONTENT_TYPE_MOVIE}, * {@link AudioAttributes#CONTENT_TYPE_MUSIC}, * {@link AudioAttributes#CONTENT_TYPE_SONIFICATION}, * {@link AudioAttributes#CONTENT_TYPE_SPEECH}, * {@link AudioAttributes#CONTENT_TYPE_UNKNOWN}.
-
setAllowedCapturePolicy:指定音頻是否可以被其他應(yīng)用程序或系統(tǒng)捕獲椎侠。定義了如下三個(gè)值:
public static final int ALLOW_CAPTURE_BY_ALL = 1;//可被任何應(yīng)用截取 public static final int ALLOW_CAPTURE_BY_SYSTEM = 2;//只能被系統(tǒng)應(yīng)用截取 public static final int ALLOW_CAPTURE_BY_NONE = 3;//表示任何應(yīng)用都不能進(jìn)行截取
4.應(yīng)用申請(qǐng)音頻焦點(diǎn)的代碼流程
焦點(diǎn)的申請(qǐng)經(jīng)過AudioManager.java-->AudioService.java --> MediaFocusControl.java的流程來申請(qǐng)的。現(xiàn)在我們看下每個(gè)類里面都進(jìn)行了哪些操作措拇。先看AudioManager.java中做了什么我纪。
1.AudioManager.requestAudioFocus
代碼路徑:/frameworks/base/media/java/android/media/AuidoManager.java
public int requestAudioFocus(@NonNull AudioFocusRequest focusRequest) {
return requestAudioFocus(focusRequest, null /* no AudioPolicy*/);
}
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {
...
//1.進(jìn)行l(wèi)ocksFocus檢測(cè),在Builder時(shí)設(shè)置了setLocksFocus(true)但AudioPolicy為null丐吓,則不能進(jìn)行焦點(diǎn)申請(qǐng)浅悉。
if (afr.locksFocus() && ap == null) {
throw new IllegalArgumentException(
"Illegal null audio policy when locking audio focus");
}
//2.注冊(cè)監(jiān)聽,構(gòu)造FocusRequestInfo券犁,將焦點(diǎn)變化監(jiān)聽器的哈希值做為key术健,如果沒有,則直接以當(dāng)前AudioManager的哈希值作為key粘衬,
//FocusRequestInfo作為value荞估,添加到mAudioFocusIdListenerMap監(jiān)聽列表里
registerAudioFocusRequest(afr);
//3.獲取AudioService,sdk稚新,clientId(與mAudioFocusIdListenerMap中的key對(duì)應(yīng))
final IAudioService service = getService();
final int status;
int sdk;...
final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
final BlockingFocusResultReceiver focusReceiver;
synchronized (mFocusRequestsLock) {
try {
//4.調(diào)用到AudioService的requestAudioFocus方法勘伺,傳入mAudioFocusDispatcher和clientId,
//mAudioFocusDispatcher作為AudioManager中的焦點(diǎn)監(jiān)聽褂删,進(jìn)行應(yīng)用焦點(diǎn)監(jiān)聽分配回調(diào)
status = service.requestAudioFocus(afr.getAudioAttributes(),
afr.getFocusGain(), mICallBack,mAudioFocusDispatcher,clientId,
getContext().getOpPackageName() /* package name */, afr.getFlags(),
ap != null ? ap.cb() : null,sdk);
...
//5.返回的狀態(tài)不為AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY時(shí)飞醉,正常返回
if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
// default path with no external focus policy
return status;
}
//6.status為AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY時(shí),
//將通過BlockingFocusResultReceiver等待調(diào)用setFocusRequestResult之后拿到結(jié)果返回
//該情況用于車載的CarAudio焦點(diǎn)分配笤妙。需要等待時(shí)創(chuàng)建一個(gè)BlockingFocusResultReceiver等待線程冒掌,
//focusReceiver為等待列表,以clientId為key蹲盘,等待200ms后如果焦點(diǎn)狀態(tài)還未在BlockingFocusResultReceiver
//中賦值股毫,則超時(shí)報(bào)錯(cuò),否則正常返回申請(qǐng)結(jié)果召衔。
if (mFocusRequestsAwaitingResult == null) {
mFocusRequestsAwaitingResult =
new HashMap<String, BlockingFocusResultReceiver>(1);
}
focusReceiver = new BlockingFocusResultReceiver(clientId);
mFocusRequestsAwaitingResult.put(clientId, focusReceiver);
}
focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS);
if (DEBUG && !focusReceiver.receivedResult()) {
Log.e(TAG, "requestAudio response from ext policy timed out, denying request");
}
synchronized (mFocusRequestsLock) {
mFocusRequestsAwaitingResult.remove(clientId);
}
return focusReceiver.requestResult();
}
//2.注冊(cè)監(jiān)聽铃诬,構(gòu)造FocusRequestInfo,將焦點(diǎn)變化監(jiān)聽器的哈希值做為key,如果沒有趣席,則直接以當(dāng)前AudioManager的哈希值作為key兵志,
//FocusRequestInfo作為value,添加到mAudioFocusIdListenerMap監(jiān)聽列表里
public void registerAudioFocusRequest(@NonNull AudioFocusRequest afr) {
final Handler h = afr.getOnAudioFocusChangeListenerHandler();
final FocusRequestInfo fri = new FocusRequestInfo(afr, (h == null) ? null :
new ServiceEventHandlerDelegate(h).getHandler());
final String key = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
mAudioFocusIdListenerMap.put(key, fri);
}
總結(jié)宣肚,在AuidoManager中存在一個(gè)mAudioFocusDispatcher進(jìn)行焦點(diǎn)變化監(jiān)聽想罕,一個(gè)mAudioFocusIdListenerMap集合管理所有的焦點(diǎn)請(qǐng)求FocusRequestInfo,并通過OnAudioFocusChangeListener的哈希值作為key進(jìn)行關(guān)聯(lián)霉涨。具體請(qǐng)求結(jié)果再調(diào)用到AudioService中按价,我們?cè)诳聪翧udioService.java中的代碼。
2.AudioService.requestAudioFocus
frameworks/base/services/core/java/com/android/server/audio/AuidoService.java
public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
IAudioPolicyCallback pcb, int sdk) {
final int uid = Binder.getCallingUid();
...
//做權(quán)限檢測(cè)笙瑟,clientId等非空判斷楼镐,提前返回焦點(diǎn)申請(qǐng)失敗
...
return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
clientId, callingPackageName, flags, sdk,
forceFocusDuckingForAccessibility(aa, durationHint, uid), -1 /*testUid, ignored*/);
}
在AudioService中并沒有做過多的操作,只是進(jìn)行了一些參數(shù)檢測(cè)往枷,權(quán)限檢測(cè)框产,提前返回焦點(diǎn)申請(qǐng)失敗,具體的邏輯是調(diào)用MediaFocusControl中的requestAudioFocus错洁,將其返回的status直接返回給AudioManager秉宿。
3.MediaFocusControl.requestAudioFocus
MediaFocusControl為焦點(diǎn)請(qǐng)求的關(guān)鍵類。MediaFocusControl為焦點(diǎn)申請(qǐng)的關(guān)鍵類墓臭,其中包含焦點(diǎn)請(qǐng)求堆棧mFocusStack
protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb,
IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName,
int flags, int sdk, boolean forceDuck, int testUid) {
...
synchronized(mAudioFocusLock) {
//1.判斷焦點(diǎn)申請(qǐng)個(gè)數(shù)是否已滿蘸鲸。mFocusStack為焦點(diǎn)<FocusRequester>的堆棧,棧頂則為當(dāng)前占用的焦點(diǎn)窿锉。
//最大個(gè)數(shù)為100個(gè)。超過100個(gè)返回焦點(diǎn)請(qǐng)求失敗
if (mFocusStack.size() > MAX_STACK_SIZE) {
Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
//2.判斷是否是系統(tǒng)手機(jī)電話音頻膝舅。設(shè)置mRingOrCallActive標(biāo)志為true
//AudioSystem.IN_VOICE_COMM_FOCUS_ID這個(gè)類型是只用于AudioManager.requestAudioFocusForCall專門進(jìn)行電話音頻焦點(diǎn)申請(qǐng)嗡载。
boolean enteringRingOrCall = !mRingOrCallActive
& (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
if (enteringRingOrCall) { mRingOrCallActive = true; }
//3.重點(diǎn),這里會(huì)根據(jù)mFocusPolicy是否為null仍稀,也就是IAudioPolicyCallback是否為null洼滚,構(gòu)造一個(gè)AudioFocusInfo,
//用于比如車載的音頻焦點(diǎn)技潘,在CarAudioService創(chuàng)建時(shí)就會(huì)IAudioPolicyCallback傳入遥巴,mFocusPolicy就為CarAudioService中的AudioPolicy。
//在mFocusPolicy不為null的情況下享幽,將會(huì)返回AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY標(biāo)志铲掐,在AudioManager中等待CarAudio中
//進(jìn)行完焦點(diǎn)分配后,setFocusRequestResult返回給AudioManager值桩,最后返回給應(yīng)用摆霉。
final AudioFocusInfo afiForExtPolicy;
if (mFocusPolicy != null) {
// construct AudioFocusInfo as it will be communicated to audio focus policy
afiForExtPolicy = new AudioFocusInfo(aa, uid,
clientId, callingPackageName, focusChangeHint, 0 /*lossReceived*/,
flags, sdk);
} else {
afiForExtPolicy = null;
}
//4.判斷申請(qǐng)結(jié)果是否需要delay,如果當(dāng)前音頻焦點(diǎn)被通話或手機(jī)鈴聲占用時(shí)且請(qǐng)求的焦點(diǎn)設(shè)置了可延時(shí)獲取,
//則返回AUDIOFOCUS_REQUEST_DELAYED告知焦點(diǎn)請(qǐng)求dealy,否則返回焦點(diǎn)申請(qǐng)失敗携栋。
boolean focusGrantDelayed = false;
if (!canReassignAudioFocus()) {
if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
} else {
focusGrantDelayed = true;
}
}
// 接著3的流程搭盾,mFocusPolicy不為null時(shí),調(diào)用notifyExtFocusPolicyFocusRequest_syncAf方法婉支。
if (mFocusPolicy != null) {
if (notifyExtFocusPolicyFocusRequest_syncAf(afiForExtPolicy, fd, cb)) {
return AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY;
} else {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
}
//5. 判斷請(qǐng)求焦點(diǎn)的客戶端是否已經(jīng)死亡鸯隅,如果已經(jīng)死亡,則返回焦點(diǎn)請(qǐng)求失敗
AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
try {
cb.linkToDeath(afdh, 0);
} catch (RemoteException e) {
// client has already died!
Log.w(TAG, "AudioFocus requestAudioFocus() could not link to "+cb+" binder death");
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
//6.排重向挖,如果請(qǐng)求的焦點(diǎn)和當(dāng)前正在占用焦點(diǎn)的clientId是一樣的蝌以,那么不進(jìn)行焦點(diǎn)的修改。直接返回焦點(diǎn)申請(qǐng)成功户誓。
if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
final FocusRequester fr = mFocusStack.peek();
if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
cb.unlinkToDeath(afdh, 0);
notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
if (!focusGrantDelayed) {
mFocusStack.pop();
fr.release();
}
}
//7.該方法從mFocusStack棧內(nèi)查找是否存在這個(gè)clientId的焦點(diǎn)請(qǐng)求饼灿,有的話進(jìn)行移除,并釋放資源帝美。
removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
//8.新建一個(gè)FocusRequester碍彭,用于成功后push到棧頂
final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
clientId, afdh, callingPackageName, uid, this, sdk);
//9.這段代碼的邏輯為當(dāng)mMultiAudioFocusEnabled這個(gè)變量為true,將并且申請(qǐng)的焦點(diǎn)類型為一個(gè)長(zhǎng)期占用的焦點(diǎn)類型時(shí)悼潭,
//則通過mMultiAudioFocusList列表進(jìn)行焦點(diǎn)管理庇忌。當(dāng)前申請(qǐng)焦點(diǎn)用于call通話時(shí),直接將前面列表中的所有焦點(diǎn)都變?yōu)長(zhǎng)OSS狀態(tài)舰褪,否則
//直接返回焦點(diǎn)申請(qǐng)成功皆疹,并添加到mMultiAudioFocusList列表。該模式下占拍,都進(jìn)行混音
//mMultiAudioFocusEnabled可通過Settings.System.putIntForUser(cr,
// Settings.System.MULTI_AUDIO_FOCUS_ENABLED, enabled ? 1 : 0, cr.getUserId())進(jìn)行配置略就。
if (mMultiAudioFocusEnabled
&& (focusChangeHint == AudioManager.AUDIOFOCUS_GAIN)) {
if (enteringRingOrCall) {
if (!mMultiAudioFocusList.isEmpty()) {
for (FocusRequester multifr : mMultiAudioFocusList) {
multifr.handleFocusLossFromGain(focusChangeHint, nfr, forceDuck);
}
}
} else {
boolean needAdd = true;
if (!mMultiAudioFocusList.isEmpty()) {
for (FocusRequester multifr : mMultiAudioFocusList) {
if (multifr.getClientUid() == Binder.getCallingUid()) {
needAdd = false;
break;
}
}
}
if (needAdd) {
mMultiAudioFocusList.add(nfr);
}
nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
}
if (focusGrantDelayed) {
//10.如果是焦點(diǎn)申請(qǐng)需要延時(shí),在pushBelowLockedFocusOwners方法中判斷焦點(diǎn)堆棧中如果有被鎖定的焦點(diǎn)
//(setLocksFocus(true)或 通話和鈴聲焦點(diǎn))且 在棧頂晃酒,則焦點(diǎn)可直接申請(qǐng)成功表牢,否則返回焦點(diǎn)申請(qǐng)需Delay。
final int requestResult = pushBelowLockedFocusOwners(nfr);
if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
}
return requestResult;
} else {
//11.返回焦點(diǎn)申請(qǐng)成功贝次,通知之前的焦點(diǎn)FocusRequester失去焦點(diǎn)崔兴,將當(dāng)前焦點(diǎn)push到mFocusStack棧頂
propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck);
mFocusStack.push(nfr);
nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
}
// 12. 給AudioPolicy更新焦點(diǎn)申請(qǐng)成功回調(diào)
notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
//13.ENFORCE_MUTING_FOR_RING_OR_CALL設(shè)置為true時(shí),當(dāng)前申請(qǐng)的焦點(diǎn)為通話時(shí)蛔翅,強(qiáng)制其他所有音頻靜音敲茄,直到通話結(jié)束后恢復(fù)
if (ENFORCE_MUTING_FOR_RING_OR_CALL & enteringRingOrCall) {
runAudioCheckerForRingOrCallAsync(true/*enteringRingOrCall*/);
}
}//synchronized(mAudioFocusLock)
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
在MediaFocusControl
的requestAudioFocus
流程中我們注意到,在焦點(diǎn)申請(qǐng)成功的時(shí)候山析,會(huì)執(zhí)行propagateFocusLossFromGain_syncAfq
方法去通知當(dāng)前的焦點(diǎn)持有者丟失焦點(diǎn)堰燎。代碼如下:
@GuardedBy("mAudioFocusLock")
private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr,
boolean forceDuck) {
final List<String> clientsToRemove = new LinkedList<String>();
if (!mFocusStack.empty()) {
for (FocusRequester focusLoser : mFocusStack) {
final boolean isDefinitiveLoss =
focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
if (isDefinitiveLoss) {
clientsToRemove.add(focusLoser.getClientId());
}
}
}
if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
for (FocusRequester multifocusLoser : mMultiAudioFocusList) {
final boolean isDefinitiveLoss =
multifocusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
if (isDefinitiveLoss) {
clientsToRemove.add(multifocusLoser.getClientId());
}
}
}
for (String clientToRemove : clientsToRemove) {
removeFocusStackEntry(clientToRemove, false /*signal*/,
true /*notifyFocusFollowers*/);
}
}
其主要邏輯就是迭代焦點(diǎn)棧,執(zhí)行handleFocusLossFromGain把狀態(tài)變?yōu)長(zhǎng)OSS的焦點(diǎn)對(duì)象移除棧內(nèi)盖腿。
而在焦點(diǎn)申請(qǐng)需要Delay時(shí)爽待,則是去執(zhí)行pushBelowLockedFocusOwners
方法直接將當(dāng)前焦點(diǎn)對(duì)象插入棧頂后一個(gè)位置损同,這樣當(dāng)當(dāng)前的焦點(diǎn)持有者釋放焦點(diǎn)后,焦點(diǎn)申請(qǐng)者能被push到棧頂鸟款,獲取到焦點(diǎn)膏燃,進(jìn)行音頻播放。代碼如下:
private int pushBelowLockedFocusOwners(FocusRequester nfr) {
int lastLockedFocusOwnerIndex = mFocusStack.size();
for (int index = mFocusStack.size()-1; index >= 0; index--) {
if (isLockedFocusOwner(mFocusStack.elementAt(index))) {
lastLockedFocusOwnerIndex = index;
}
}
if (lastLockedFocusOwnerIndex == mFocusStack.size()) {
// this should not happen, but handle it and log an error
Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()",
new Exception());
propagateFocusLossFromGain_syncAf(nfr.getGainRequest(), nfr, false /*forceDuck*/);
mFocusStack.push(nfr);
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
} else {
mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
}
}
其主要邏輯為:先迭代焦點(diǎn)棧何什,如果焦點(diǎn)棧內(nèi)存在被鎖定的焦點(diǎn)组哩,則將當(dāng)前焦點(diǎn)插入到鎖定了焦點(diǎn)者后面,等被鎖定的焦點(diǎn)釋放了焦點(diǎn)后才能獲取焦點(diǎn)处渣。如果不存在伶贰,就直接返回申請(qǐng)成功。這一部在我看來其實(shí)是一個(gè)再次檢測(cè)的過程罐栈,因?yàn)槲覀冊(cè)趫?zhí)行這個(gè)方法之前是已經(jīng)檢查過當(dāng)前焦點(diǎn)持有者是否被鎖定黍衙,是的話才走到這。所以大部分的還是會(huì)執(zhí)行insertElementAt
這個(gè)方法插入到棧內(nèi)荠诬,等待上一個(gè)焦點(diǎn)釋放琅翻。
最后焦點(diǎn)申請(qǐng)者申請(qǐng)成功,焦點(diǎn)持有者丟失焦點(diǎn)柑贞,會(huì)分別執(zhí)行FocusRequester
對(duì)象的 handleFocusGainFromRequest
和handleFocusLossFromGain
方法方椎。
-
handleFocusGainFromRequest
方法比較簡(jiǎn)單,就是執(zhí)行restoreVShapedPlayers
方法钧嘶,通知底層可進(jìn)行音頻播放棠众。@GuardedBy("MediaFocusControl.mAudioFocusLock") void handleFocusGainFromRequest(int focusRequestResult) { if (focusRequestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { mFocusController.restoreVShapedPlayers(this); } }
-
handleFocusLossFromGain
方法中調(diào)用focusLossForGainRequest
方法,根據(jù)上一次焦點(diǎn)的狀態(tài)有决,和當(dāng)前焦點(diǎn)申請(qǐng)者的請(qǐng)求的焦點(diǎn)類型闸拿,返回焦點(diǎn)變化狀態(tài),在handleFocusLoss
方法中執(zhí)行IAudioFocusDispatcher
(AudioManager中傳入的監(jiān)聽器)的dispatchAudioFocusChange
方法书幕,最終將焦點(diǎn)狀態(tài)變化回調(diào)到應(yīng)用的監(jiān)聽器中胸墙。@GuardedBy("MediaFocusControl.mAudioFocusLock") boolean handleFocusLossFromGain(int focusGain, final FocusRequester frWinner, boolean forceDuck) { final int focusLoss = focusLossForGainRequest(focusGain); handleFocusLoss(focusLoss, frWinner, forceDuck); return (focusLoss == AudioManager.AUDIOFOCUS_LOSS); } //mFocusLossReceived為當(dāng)前焦點(diǎn)的上一次變化記錄 private int focusLossForGainRequest(int gainRequest) { switch(gainRequest) { //請(qǐng)求的焦點(diǎn)類型為長(zhǎng)期占用時(shí),焦點(diǎn)狀態(tài)變?yōu)閬G失LOSS case AudioManager.AUDIOFOCUS_GAIN: switch(mFocusLossReceived) { case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: case AudioManager.AUDIOFOCUS_LOSS: case AudioManager.AUDIOFOCUS_NONE: return AudioManager.AUDIOFOCUS_LOSS; } //請(qǐng)求的焦點(diǎn)類型為短期占用按咒, //上次焦點(diǎn)狀態(tài)為短暫丟失或混音時(shí),則焦點(diǎn)狀態(tài)變?yōu)長(zhǎng)OSS_TRANSIENT短暫丟失但骨。 //上次焦點(diǎn)狀態(tài)為丟失時(shí)励七,則焦點(diǎn)狀態(tài)變?yōu)長(zhǎng)OSS丟失 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: switch(mFocusLossReceived) { case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: case AudioManager.AUDIOFOCUS_NONE: return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT; case AudioManager.AUDIOFOCUS_LOSS: return AudioManager.AUDIOFOCUS_LOSS; } //請(qǐng)求的焦點(diǎn)類型為混音 //上次焦點(diǎn)狀態(tài)為混音時(shí),焦點(diǎn)狀態(tài)變?yōu)镃AN_DUCK可混音狀態(tài) //上次焦點(diǎn)狀態(tài)為短暫丟失時(shí)奔缠,焦點(diǎn)狀態(tài)保持LOSS_TRANSIENT //上次焦點(diǎn)狀態(tài)為丟失時(shí)掠抬,焦點(diǎn)狀態(tài)保持LOSS case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: switch(mFocusLossReceived) { case AudioManager.AUDIOFOCUS_NONE: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT; case AudioManager.AUDIOFOCUS_LOSS: return AudioManager.AUDIOFOCUS_LOSS; } default: Log.e(TAG, "focusLossForGainRequest() for invalid focus request "+ gainRequest); return AudioManager.AUDIOFOCUS_NONE; } }
5.釋放焦點(diǎn)abandonAudioFocusRequest
看完焦點(diǎn)的申請(qǐng)流程,在看下焦點(diǎn)的釋放流程校哎,在申請(qǐng)成功焦點(diǎn)播放音頻后两波,需要將焦點(diǎn)再次釋放瞳步,否則焦點(diǎn)不會(huì)自動(dòng)釋放。焦點(diǎn)釋放調(diào)用AudioManager
的abandonAudioFocusRequest
方法腰奋,將申請(qǐng)焦點(diǎn)時(shí)構(gòu)造的AudioFocusRequest
傳入单起。和申請(qǐng)焦點(diǎn)的流程類似,最后也是在MediaFocusControl.java
中執(zhí)行核心代碼劣坊。
protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa,
String callingPackageName) {
...
try {
synchronized(mAudioFocusLock) {
if (mFocusPolicy != null) {
//mFocusPolicy不為null時(shí)嘀倒,同樣的將焦點(diǎn)釋放給到AudioPolicy中釋放
final AudioFocusInfo afi = new AudioFocusInfo(aa, Binder.getCallingUid(),
clientId, callingPackageName, 0 /*gainRequest*/, 0 /*lossReceived*/,
0 /*flags*/, 0 /* sdk n/a here*/);
if (notifyExtFocusPolicyFocusAbandon_syncAf(afi)) {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
}
...
// 移除焦點(diǎn)
removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
//通話焦點(diǎn)則恢復(fù)之前的音頻
if (ENFORCE_MUTING_FOR_RING_OR_CALL & exitingRingOrCall) {
runAudioCheckerForRingOrCallAsync(false/*enteringRingOrCall*/);
}
}
} catch (java.util.ConcurrentModificationException cme) {
Log.e(TAG, "FATAL EXCEPTION AudioFocus abandonAudioFocus() caused " + cme);
cme.printStackTrace();
}
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
關(guān)鍵方法為removeFocusStackEntry
,傳入signal
,notifyFocusFollowers
為true
局冰,接著看removeFocusStackEntry
中的代碼邏輯
@GuardedBy("mAudioFocusLock")
private void removeFocusStackEntry(String clientToRemove, boolean signal,
boolean notifyFocusFollowers) {
AudioFocusInfo abandonSource = null;
//釋放的焦點(diǎn)正處于棧頂测蘑,直接pop出棧
if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
{
FocusRequester fr = mFocusStack.pop();
fr.maybeRelease();
if (notifyFocusFollowers) {
abandonSource = fr.toAudioFocusInfo();
}
//signal為true時(shí),更新當(dāng)前焦點(diǎn)出棧后的棧頂焦點(diǎn)為AUDIOFOCUS_GAIN
if (signal) {
//在該方法中康二,將棧內(nèi)的焦點(diǎn)peek出來碳胳,再執(zhí)行handleFocusGain方法。
notifyTopOfAudioFocusStack();
}
} else {
//釋放的焦點(diǎn)不在棧頂時(shí)沫勿,直接從棧內(nèi)移除挨约。
Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
while(stackIterator.hasNext()) {
FocusRequester fr = stackIterator.next();
if(fr.hasSameClient(clientToRemove)) {
Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for "
+ clientToRemove);
stackIterator.remove();
if (notifyFocusFollowers) {
abandonSource = fr.toAudioFocusInfo();
}
fr.maybeRelease();
}
}
if (abandonSource != null) {
abandonSource.clearLossReceived();
//通知AudioPolicy當(dāng)前焦點(diǎn)已經(jīng)被釋放
notifyExtPolicyFocusLoss_syncAf(abandonSource, false);
}
...
}
6.總結(jié)
至此,我們的audio焦點(diǎn)生命周期已經(jīng)大概都已經(jīng)知曉藕帜。對(duì)以上流程我們進(jìn)行歸納總結(jié):
- 1.系統(tǒng)音頻焦點(diǎn)在用戶場(chǎng)景上烫罩,只做通話和非通話音頻區(qū)分。
- 2.系統(tǒng)音頻焦點(diǎn)由mFocusStack堆棧進(jìn)行管理洽故。焦點(diǎn)申請(qǐng)成功贝攒,push到棧頂;焦點(diǎn)釋放时甚,pop出棧隘弊,焦點(diǎn)需延時(shí),inster插入棧內(nèi)荒适。焦點(diǎn)狀態(tài)變?yōu)長(zhǎng)oss梨熙,從棧內(nèi)移除。
- 3.mFocusPolicy不為null時(shí)刀诬,系統(tǒng)Audio不處理焦點(diǎn)分配咽扇,交由自定義的AudioPolicy去進(jìn)行分配。比如CarAudioFocus
- 4.當(dāng)前焦點(diǎn)持有者為通話時(shí)陕壹,焦點(diǎn)返回Delay质欲,知道通話焦點(diǎn)釋放,重新獲取焦點(diǎn)糠馆。
場(chǎng)景一:
1.音頻A申請(qǐng)了一個(gè)長(zhǎng)期焦點(diǎn)AUDIOFOCUS_GAIN嘶伟,焦點(diǎn)申請(qǐng)成功后,音頻A播放又碌。
2.音頻B也申請(qǐng)了一個(gè)長(zhǎng)期焦點(diǎn)AUDIOFOCUS_GAIN九昧,焦點(diǎn)申請(qǐng)成功绊袋,音頻B的焦點(diǎn)被push入棧頂。音頻B播放
3.音頻A的焦點(diǎn)被移出Stack內(nèi)铸鹰, A焦點(diǎn)監(jiān)聽器收到AUDIOFOCUS_LOSS 狀態(tài)癌别,再次播放,需重新申請(qǐng)焦點(diǎn)掉奄。
場(chǎng)景二:
1.音頻A為通話音頻规个,申請(qǐng)了焦點(diǎn),通話中姓建。
2.音頻B也申請(qǐng)了一個(gè)長(zhǎng)期焦點(diǎn)AUDIOFOCUS_GAIN诞仓,并設(shè)置了可延時(shí)申請(qǐng),焦點(diǎn)申請(qǐng)delay速兔,音頻B被插入棧內(nèi)墅拭。
3.音頻A的通話結(jié)束,釋放焦點(diǎn)涣狗,音頻A被pop出棧谍婉,音頻B來到棧頂。音頻B監(jiān)聽器收到AUDIOFOCUS_GAIN狀態(tài)镀钓,
可進(jìn)行音頻播放穗熬。
場(chǎng)景三:
1.音頻A申請(qǐng)了一個(gè)長(zhǎng)期焦點(diǎn)AUDIOFOCUS_GAIN,焦點(diǎn)申請(qǐng)成功后丁溅,音頻A播放唤蔗。
2.音頻B申請(qǐng)了一個(gè)短期焦點(diǎn)AUDIOFOCUS_GAIN_TRANSIENT,焦點(diǎn)申請(qǐng)成功窟赏,音頻B的焦點(diǎn)被push入棧頂妓柜。音頻B播放
3.音頻A的焦點(diǎn)監(jiān)聽器中收到AUDIOFOCUS_LOSS_TRANSIENT狀態(tài)。如果音頻B處于stack 0位置涯穷,音頻A焦點(diǎn)則于Stack 1的位置棍掐,還在棧內(nèi)。
4.音頻B停止播放拷况,同時(shí)釋放焦點(diǎn)蝇刀,音頻B的焦點(diǎn)出棧壮啊。
5.音頻A成為了棧頂焦點(diǎn)寺晌,監(jiān)聽器中將收到AUDIOFOCUS_GAIN狀態(tài)噪沙,可繼續(xù)音頻播放吆视。