您现在的位置是:主页 > news > 公司网站建设专家/百度学术官网
公司网站建设专家/百度学术官网
admin2025/4/29 18:10:49【news】
简介公司网站建设专家,百度学术官网,wordpress浏览doc,淮北市相山区建设局网站摘要:Android 9 的音频焦点仲裁策略基本上可以用一句话来概括:后来居上,电话最大。这种策略显然是不能满足音频焦点仲裁的复杂需求的,所以Google在Android 10 中做了大幅度的改进,其中最主要的就是引入了音频焦点判断矩…
摘要:Android 9 的音频焦点仲裁策略基本上可以用一句话来概括:后来居上,电话最大。这种策略显然是不能满足音频焦点仲裁的复杂需求的,所以Google在Android 10 中做了大幅度的改进,其中最主要的就是引入了音频焦点判断矩阵,通过矩阵来仲裁后来者是否可以抢占当前焦点。
由于Android9的音频焦点策略基本不能满足项目需求,所以一般会引入外部焦点仲裁策略,不知道如何引入的可以参考这篇文章:自定义音频焦点策略的实现。既然要引入,何不引入Android 10 的音频焦点策略呢?
音频焦点仲裁策略分析
1.关键类及变量列表
变量 | 类型 | 说明 |
---|---|---|
AudioFocusInfo | 类 | 描述焦点申请者属性 |
FocusEntry | 内部类 | 对AudioFocusInfo和Context的封装 |
sInteractionMatrix | 二维数组 | 仲裁焦点申请结果 |
mFocusHolders | 全局变量,HashMap | 保存当前焦点持有者 |
mFocusLosers | 全局变量,HashMap | 保存暂时失去焦点并等待重新获得焦点的申请 |
losers | 局部变量,ArrayList | 保存失去焦点但失去焦点类型尚未确定的申请 |
blocked | 局部变量,ArrayList | 保存mFocusLosers中可以被当前申请者抢占的申请 |
permanentlyLost | 局部变量,ArrayList | 保存永久失去焦点的申请 |
2.申请焦点及仲裁过程详解
申请焦点的入口函数是:evaluateFocusRequest
,接受一个AudioFocusInfo
类型参数
新建两个boolean类型参数用来描述焦点申请者属性:
permanent
:描述申请者是否申请永久获取焦点(即申请类型是否为AUDIOFOCUS_GAIN)
allowDucking
:描述申请者是否申请暂时获取焦点且允许混音(即申请类型是否为AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
// Is this a request for premanant focus?// AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE -- Means Notifications should be denied// AUDIOFOCUS_GAIN_TRANSIENT -- Means current focus holders should get transient loss// AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK -- Means other can duck (no loss message from us)// NOTE: We expect that in practice it will be permanent for all media requests and// transient for everything else, but that isn't currently an enforced requirement.final boolean permanent =(afi.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN);//final boolean allowDucking =// (afi.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);// only interaction matrix can decide if ducking allowedfinal boolean allowDucking = (afi.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
将usage转化为Context:(什么是usage和Context?参考此处)
// Convert from audio attributes "usage" to HAL level "context"final int requestedContext = mCarAudioService.getContextForUsage(afi.getAttributes().getUsage());
防止已经拥有或者暂时失去焦点的引用再次申请焦点,新建两个FocusEntry
:
replacedCurrentEntry
:当已经拥有焦点的应用再次申请焦点时,对这个变量赋值,如果申请成功则删除replacedCurrentEntry
,用新的请申请代替
replacedBlockedEntry
:当暂时失去焦点的应用再次申请焦点时,对这个变量赋值,如果申请成功则删除replacedBlockedEntry
,用新的请申请代替
// If we happen to find entries that this new request should replace, we'll store them here.// This happens when a client makes a second AF request on the same listener.// After we've granted audio focus to our current request, we'll abandon these requests.FocusEntry replacedCurrentEntry = null;FocusEntry replacedBlockedEntry = null;
接下来会遍历mFocusHolders
:
首先判断如果当前申请者的requestedContext
是NOTIFICATION
并且mFocusHolders
已经存在一个暂时独占焦点的申请者(AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE),则直接返回申请失败
// If this request is for Notifications and a current focus holder has specified// AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE, then reject the request.// This matches the hardwired behavior in the default audio policy engine which apps// might expect (The interaction matrix doesn't have any provision for dealing with// override flags like this).if ((requestedContext == ContextNumber.NOTIFICATION) &&(entry.mAfi.getGainRequest() ==AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {return AudioManager.AUDIOFOCUS_REQUEST_FAILED;}
接着判断是否是同一个应用申请的焦点,如果是同一个应用且申请的是同一个Context类型的焦点,则将目前mFocusHolders
中的此应用的entry赋值给replacedCurrentEntry,之后如果焦点申请成功则会将这个entry删除并将新的加入,如果申请的是不同的Context类型,则直接返回申请失败
// We don't allow sharing listeners (client IDs) between two concurrent requests// (because the app would have no way to know to which request a later event applied)if (afi.getClientId().equals(entry.mAfi.getClientId())) {if (entry.mAudioContext == requestedContext) {// This is a request from a current focus holder.// Abandon the previous request (without sending a LOSS notification to it),// and don't check the interaction matrix for it.Slog.i(TAG, "Replacing accepted request from same client");replacedCurrentEntry = entry;continue;} else {// Trivially reject a request for a different USAGESlog.e(TAG, "Client " + entry.getClientId() + " has already requested focus "+ "for " + entry.mAfi.getAttributes().usageToString() + " - cannot "+ "request focus for " + afi.getAttributes().usageToString() + " on "+ "same listener.");return AudioManager.AUDIOFOCUS_REQUEST_FAILED;}}
然后就进入到刺激的焦点仲裁阶段了。先来看一下我们的焦点仲裁矩阵:
private static int sInteractionMatrix[][] = {// Row selected by playing sound (labels along the right)// Column selected by incoming request (labels along the top)// Cell value is one of INTERACTION_REJECT, INTERACTION_EXCLUSIVE, INTERACTION_CONCURRENT// Invalid, Music, Nav, Voice, Ring, Call, Alarm, Notification, System{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid{ 0, 1, 2, 1, 1, 1, 1, 2, 2 }, // Music{ 0, 2, 2, 1, 2, 1, 2, 2, 2 }, // Nav{ 0, 2, 0, 2, 1, 1, 0, 0, 0 }, // Voice{ 0, 0, 2, 2, 2, 2, 0, 0, 2 }, // Ring{ 0, 0, 2, 0, 2, 2, 2, 2, 0 }, // Context{ 0, 2, 2, 1, 1, 1, 2, 2, 2 }, // Alarm{ 0, 2, 2, 1, 1, 1, 2, 2, 2 }, // Notification{ 0, 2, 2, 1, 1, 1, 2, 2, 2 }, // System};
用三个数字表示焦点仲裁的三种结果:
// Values for the internal interaction matrix we use to make focus decisionsstatic final int INTERACTION_REJECT = 0; // Focus not granted 拒绝焦点申请static final int INTERACTION_EXCLUSIVE = 1; // Focus granted, others loose focus 接受焦点申请static final int INTERACTION_CONCURRENT = 2; // Focus granted, others keep focus 同时接受两个申请
判断结果如果是INTERACTION_REJECT
,则返回申请失败,如果是INTERACTION_EXCLUSIVE
,则将当前焦点持有者加入losers
,如果是INTERACTION_CONCURRENT
,则再判断:1)焦点申请者是否不接受混音;2)当前焦点持有者是否不接受混音;3)焦点持有者是否持有RECEIVE_CAR_AUDIO_DUCKING_EVENTS
标志(我也不知道啥意思,一般不会使用),如果有任何一条符合,则将当前焦点持有者加入losers
,如果所有都不符合,则会出现同时两个应用同时占有焦点同时播放的情况
for (FocusEntry entry : mFocusHolders.values()) {Slog.d(TAG, "Evaluating focus holder: " + entry.toString());// If this request is for Notifications and a current focus holder has specified// AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE, then reject the request.// This matches the hardwired behavior in the default audio policy engine which apps// might expect (The interaction matrix doesn't have any provision for dealing with// override flags like this).if ((requestedContext == ContextNumber.NOTIFICATION) &&(entry.mAfi.getGainRequest() ==AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {return AudioManager.AUDIOFOCUS_REQUEST_FAILED;}// We don't allow sharing listeners (client IDs) between two concurrent requests// (because the app would have no way to know to which request a later event applied)if (afi.getClientId().equals(entry.mAfi.getClientId())) {if (entry.mAudioContext == requestedContext) {// This is a request from a current focus holder.// Abandon the previous request (without sending a LOSS notification to it),// and don't check the interaction matrix for it.Slog.i(TAG, "Replacing accepted request from same client");replacedCurrentEntry = entry;continue;} else {// Trivially reject a request for a different USAGESlog.e(TAG, "Client " + entry.getClientId() + " has already requested focus "+ "for " + entry.mAfi.getAttributes().usageToString() + " - cannot "+ "request focus for " + afi.getAttributes().usageToString() + " on "+ "same listener.");return AudioManager.AUDIOFOCUS_REQUEST_FAILED;}}// Check the interaction matrix for the relationship between this entry and the requestswitch (sInteractionMatrix[entry.mAudioContext][requestedContext]) {case INTERACTION_REJECT:Slog.d(TAG, "INTERACTION_REJECT");// This request is rejected, so nothing further to doreturn AudioManager.AUDIOFOCUS_REQUEST_FAILED;case INTERACTION_EXCLUSIVE:Slog.d(TAG, "INTERACTION_EXCLUSIVE");// The new request will cause this existing entry to lose focuslosers.add(entry);break;case INTERACTION_CONCURRENT:// If ducking isn't allowed by the focus requestor, then everybody else// must get a LOSS.// If a focus holder has set the AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS flag,// they must get a LOSS message even if ducking would otherwise be allowed.// If a focus holder holds the RECEIVE_CAR_AUDIO_DUCKING_EVENTS permission,// they must receive all audio focus losses.Slog.d(TAG, "INTERACTION_CONCURRENT");if (!allowDucking|| entry.wantsPauseInsteadOfDucking()|| entry.receivesDuckEvents()) {losers.add(entry);}break;default:Slog.e(TAG, "Bad interaction matrix value - rejecting");return AudioManager.AUDIOFOCUS_REQUEST_FAILED;}}
接下来会遍历mFocusLosers
,这里面保存的是暂时失去焦点的申请者,这些申请者很可能因为当前申请者的申请行为而永久失去焦点。遍历的过程和上面如出一辙,仲裁结果为INTERACTION_EXCLUSIVE
或INTERACTION_CONCURRENT
中符合情况的entry
,会被放入blocked
中。代码就不列举了,和上面基本一样。
如果一个焦点申请到这里依然没有被拒绝,那么恭喜,它将获得音频焦点,新建一个FocusEntry
用来保存它:
// Now that we've decided we'll grant focus, construct our new FocusEntryFocusEntry newEntry = new FocusEntry(afi, requestedContext);
新建一个ArrayList
:permanentlyLost,用来保存永久失去焦点的申请:
// These entries have permanently lost focus as a result of this request, so they// should be removed from all blocker lists.ArrayList<FocusEntry> permanentlyLost = new ArrayList<>();
如果replacedCurrentEntry
或replacedBlockedEntry
不等于null
,则把它们从队列中删除,用新申请的newEntry来替换它
if (replacedCurrentEntry != null) {mFocusHolders.remove(replacedCurrentEntry.getClientId());permanentlyLost.add(replacedCurrentEntry);}if (replacedBlockedEntry != null) {mFocusLosers.remove(replacedBlockedEntry.getClientId());permanentlyLost.add(replacedBlockedEntry);}
然后遍历blocked
中的entry
,如果当前焦点申请者希望永久占有焦点,则此entry
永久失去焦点,如果当前焦点申请者只是暂时占有焦点,则判断当前焦点申请者是否不允许混音和此entry
的mReceivedLossTransientCanDuck
变量是否为真,如果都满足,则这个entry
气数未尽,还能继续保存在mFocusLosers
中,不过它会被加上一个新的mBlockers
,也就是当前焦点申请者,在所有mBlockers
都被移除后,这个entry
就会获得焦点
// Now that we're sure we'll accept this request, update any requests which we would// block but are already out of focus but waiting to come backfor (FocusEntry entry : blocked) {// If we're out of focus it must be because somebody is blocking usassert !entry.mBlockers.isEmpty();if (permanent) {// This entry has now lost focus foreversendFocusLoss(entry, AudioManager.AUDIOFOCUS_LOSS);entry.mReceivedLossTransientCanDuck = false;final FocusEntry deadEntry = mFocusLosers.remove(entry.mAfi.getClientId());assert deadEntry != null;permanentlyLost.add(entry);} else {if (!allowDucking && entry.mReceivedLossTransientCanDuck) {// This entry was previously allowed to duck, but can no longer do so.Log.i(TAG, "Converting duckable loss to non-duckable for "+ entry.getClientId());sendFocusLoss(entry, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);entry.mReceivedLossTransientCanDuck = false;}// Note that this new request is yet one more reason we can't (yet) have focusentry.mBlockers.add(newEntry);}}
之后遍历losers
中的entry
,这里会做两件事:
- 判断
entry
失去焦点的类型:
如果当前焦点申请者希望永久占有焦点,则失去焦点的类型为AUDIOFOCUS_LOSS
,如果当前焦点申请者只是暂时占有焦点,且允许混音并持有RECEIVE_CAR_AUDIO_DUCKING_EVENTS
标志,则失去焦点的类型为AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
并将的entry
的mReceivedLossTransientCanDuck
变量设为true,其他情况失去焦点的类型为AUDIOFOCUS_LOSS_TRANSIENT
- 判断
entry
是暂时失去焦点还是永久失去:
如果当前焦点申请者希望永久占有焦点,则entry
永久失去焦点,如果不是,则entry
暂时失去焦点
// Notify and update any requests which are now losing focus as a result of the new requestfor (FocusEntry entry : losers) {// If we have focus (but are about to loose it), nobody should be blocking us yetassert entry.mBlockers.isEmpty();int lossType;if (permanent) {lossType = AudioManager.AUDIOFOCUS_LOSS;} else if (allowDucking && entry.receivesDuckEvents()) {lossType = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;entry.mReceivedLossTransientCanDuck = true;} else {lossType = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;}sendFocusLoss(entry, lossType);// The entry no longer holds focus, so take it out of the holders listmFocusHolders.remove(entry.mAfi.getClientId());if (permanent) {permanentlyLost.add(entry);} else {// Add ourselves to the list of requests waiting to get focus back and// note why we lost focus so we can tell when it's time to get it backmFocusLosers.put(entry.mAfi.getClientId(), entry);entry.mBlockers.add(newEntry);}}
终于接近尾声了啊,接下来会遍历permanentlyLost
,对这个容器内的所有entry
执行removeFocusEntryAndRestoreUnblockedWaiters(entry)
方法,这个方法会遍历mFocusLosers并将传入的这个entry
从mFocusLosers中的entry
的mBlockers中移除,如果mFocusLosers中有哪一个entry
的mBlockers变为空,则说明所有阻碍它获取焦点的障碍已经消失,此时它就应该获取焦点:
// Now that all new blockers have been added, clear out any other requests that have been// permanently lost as a result of this request. Treat them as abandoned - if they're on// any blocker lists, remove them. If any focus requests become unblocked as a result,// re-grant them. (This can happen when a GAIN_TRANSIENT_MAY_DUCK request replaces a// GAIN_TRANSIENT request from the same listener.)for (FocusEntry entry : permanentlyLost) {Slog.d(TAG, "Cleaning up entry " + entry.getClientId());removeFocusEntryAndRestoreUnblockedWaiters(entry);}
private void removeFocusEntryAndRestoreUnblockedWaiters(FocusEntry deadEntry) {// Remove this entry from the blocking list of any pending requestsIterator<FocusEntry> it = mFocusLosers.values().iterator();while (it.hasNext()) {FocusEntry entry = it.next();// Remove the retiring entry from all blocker listsentry.mBlockers.remove(deadEntry);// Any entry whose blocking list becomes empty should regain focusif (entry.mBlockers.isEmpty()) {Slog.i(TAG, "Restoring unblocked entry " + entry.getClientId());// Pull this entry out of the focus losers listit.remove();// Add it back into the focus holders listmFocusHolders.put(entry.getClientId(), entry);dispatchFocusGained(entry.mAfi);}}}
最终!将当前申请的焦点放入mFocusHolders
,并返回AUDIOFOCUS_REQUEST_GRANTED
// Finally, add the request we're granting to the focus holders' listmFocusHolders.put(afi.getClientId(), newEntry);Log.i(TAG, "AUDIOFOCUS_REQUEST_GRANTED");return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
我们趁热打铁看一下与申请焦点对立的放弃焦点申请:
public synchronized void onAudioFocusAbandon(AudioFocusInfo afi) {Log.i(TAG, "onAudioFocusAbandon " + afi.getClientId());FocusEntry deadEntry = removeFocusEntry(afi);if (deadEntry != null) {removeFocusEntryAndRestoreUnblockedWaiters(deadEntry);}}
调用removeFocusEntry
,这个方法会在mFocusHolders
和mFocusLosers
中寻找要放弃的deadEntry
,如果没有找到就打印一个警告,如果成功放弃,则会继续执行removeFocusEntryAndRestoreUnblockedWaiters
,这个方法我们在之前已经讲解过了
以上!