您现在的位置是:主页 > news > 公司网站建设专家/百度学术官网

公司网站建设专家/百度学术官网

admin2025/4/29 18:10:49news

简介公司网站建设专家,百度学术官网,wordpress浏览doc,淮北市相山区建设局网站摘要:Android 9 的音频焦点仲裁策略基本上可以用一句话来概括:后来居上,电话最大。这种策略显然是不能满足音频焦点仲裁的复杂需求的,所以Google在Android 10 中做了大幅度的改进,其中最主要的就是引入了音频焦点判断矩…

公司网站建设专家,百度学术官网,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

首先判断如果当前申请者的requestedContextNOTIFICATION并且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_EXCLUSIVEINTERACTION_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<>();

如果replacedCurrentEntryreplacedBlockedEntry不等于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永久失去焦点,如果当前焦点申请者只是暂时占有焦点,则判断当前焦点申请者是否不允许混音和此entrymReceivedLossTransientCanDuck变量是否为真,如果都满足,则这个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,这里会做两件事:

  1. 判断entry失去焦点的类型:
    如果当前焦点申请者希望永久占有焦点,则失去焦点的类型为AUDIOFOCUS_LOSS,如果当前焦点申请者只是暂时占有焦点,且允许混音并持有RECEIVE_CAR_AUDIO_DUCKING_EVENTS标志,则失去焦点的类型为AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK并将的entrymReceivedLossTransientCanDuck变量设为true,其他情况失去焦点的类型为AUDIOFOCUS_LOSS_TRANSIENT
  2. 判断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,这个方法会在mFocusHoldersmFocusLosers中寻找要放弃的deadEntry,如果没有找到就打印一个警告,如果成功放弃,则会继续执行removeFocusEntryAndRestoreUnblockedWaiters,这个方法我们在之前已经讲解过了

以上!