详解(二)"/>
Android CarAudioFocus详解(二)
前面文章分析了申请调用的流程,同时也有一篇执行流程的文字描述,但是总觉得不够清晰,让我们来用图示和代码结合说下这部分:
AudioRequester:AudioFocusInfo为传入的参数,里面包含了USAGE类型,GAIN类型,ClientId。其中permanent表示是否永久获取焦点,allowDucking表示是否接受duck。
mFocusHolders:焦点持有者HashMap。里面保存了当前持有焦点的FocusEntry。
mFocusLosers:也是HashMap,里面保存了被抢占焦点,有可能恢复持有的FocusEntry。
losers:AudioRequester和mFocusHolders通过evaluateRequest函数冲突后生成的中间产物。
blocks:AudioRequester和mFocusLosers通过evaluateRequest函数冲突后生成的中间产物。
mDelayerRequest:保存未申请成功,但是接受延迟获得焦点的FocusEntry。
介绍完成员开始介绍申请焦点的流程:
流程一:mFocusHolders和和AudioRequester的冲突:
Log.i(TAG, "Scanning focus holders...");final ArrayList<FocusEntry> losers = new ArrayList<FocusEntry>();for (FocusEntry entry : mFocusHolders.values()) {Log.d(TAG, "Evaluating focus holder: " + entry.getClientId());// 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 == CarAudioContext.NOTIFICATION)&& (entry.getAudioFocusInfo().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.getAudioFocusInfo().getClientId())) {if (entry.getAudioContext() == 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.Log.i(TAG, "Replacing accepted request from same client");replacedCurrentEntry = entry;continue;} else {// Trivially reject a request for a different USAGELog.e(TAG, String.format("Client %s has already requested focus for %s - cannot request focus "+ "for %s on same listener.",entry.getClientId(),entry.getAudioFocusInfo().getAttributes().usageToString(),afi.getAttributes().usageToString()));return AudioManager.AUDIOFOCUS_REQUEST_FAILED;}}@AudioManager.FocusRequestResult int interactionResult = mFocusInteraction.evaluateRequest(requestedContext, entry, losers, allowDucking,allowDelayedFocus);if (interactionResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {return interactionResult;}if (interactionResult == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {delayFocusForCurrentRequest = true;}}
流程二:mFocusLosers和和AudioRequester的冲突:
Log.i(TAG, "Scanning those who've already lost focus...");final ArrayList<FocusEntry> blocked = new ArrayList<FocusEntry>();for (FocusEntry entry : mFocusLosers.values()) {Log.i(TAG, entry.getAudioFocusInfo().getClientId());// If this request is for Notifications and a pending focus holder has specified// AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE, then reject the requestif ((requestedContext == CarAudioContext.NOTIFICATION)&& (entry.getAudioFocusInfo().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.getAudioFocusInfo().getClientId())) {if (entry.getAudioContext() == requestedContext) {// This is a repeat of a request that is currently blocked.// Evaluate it as if it were a new request, but note that we should remove// the old pending request, and move it.// We do not want to evaluate the new request against itself.Log.i(TAG, "Replacing pending request from same client");replacedBlockedEntry = entry;continue;} else {// Trivially reject a request for a different USAGELog.e(TAG, String.format("Client %s has already requested focus for %s - cannot request focus "+ "for %s on same listener.",entry.getClientId(),entry.getAudioFocusInfo().getAttributes().usageToString(),afi.getAttributes().usageToString()));return AudioManager.AUDIOFOCUS_REQUEST_FAILED;}}@AudioManager.FocusRequestResult int interactionResult = mFocusInteraction.evaluateRequest(requestedContext, entry, blocked, allowDucking,allowDelayedFocus);if (interactionResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {return interactionResult;}if (interactionResult == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {delayFocusForCurrentRequest = true;}}
流程三:mFocusInteraction.evaluateRequest执行冲突:
public @FocusRequestResult int evaluateRequest(@AudioContext int requestedContext,FocusEntry focusHolder, List<FocusEntry> focusLosers, boolean allowDucking,boolean allowsDelayedFocus) {@AudioContext int holderContext = focusHolder.getAudioContext();Preconditions.checkArgumentInRange(holderContext, 0, mInteractionMatrix.length - 1,"holderContext");synchronized (mLock) {int[] holderRow = mInteractionMatrix[holderContext];Preconditions.checkArgumentInRange(requestedContext, 0, holderRow.length - 1,"requestedContext");switch (holderRow[requestedContext]) {case INTERACTION_REJECT:if (allowsDelayedFocus) {return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;}return AudioManager.AUDIOFOCUS_REQUEST_FAILED;case INTERACTION_EXCLUSIVE:focusLosers.add(focusHolder);return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;case INTERACTION_CONCURRENT:// If ducking isn't allowed by the focus requester, 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.if (!allowDucking|| focusHolder.wantsPauseInsteadOfDucking()|| focusHolder.receivesDuckEvents()) {focusLosers.add(focusHolder);}return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;default:Log.e(TAG, String.format("Unsupported CarAudioContext %d - rejecting request",holderRow[requestedContext]));return AudioManager.AUDIOFOCUS_REQUEST_FAILED;}}}
流程四:blocked处理
for (FocusEntry entry : blocked) {// If we're out of focus it must be because somebody is blocking usassert !entry.isUnblocked();if (permanent) {// This entry has now lost focus foreversendFocusLossLocked(entry.getAudioFocusInfo(), AudioManager.AUDIOFOCUS_LOSS);entry.setDucked(false);final FocusEntry deadEntry = mFocusLosers.remove(entry.getAudioFocusInfo().getClientId());assert deadEntry != null;permanentlyLost.add(entry);} else {if (!allowDucking && entry.isDucked()) {// 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());sendFocusLossLocked(entry.getAudioFocusInfo(),AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);entry.setDucked(false);}// Note that this new request is yet one more reason we can't (yet) have focusentry.addBlocker(newEntry);}}
流程五:losers的处理:
for (FocusEntry entry : losers) {// If we have focus (but are about to loose it), nobody should be blocking us yetassert entry.isUnblocked();int lossType;if (permanent) {lossType = AudioManager.AUDIOFOCUS_LOSS;} else if (allowDucking && entry.receivesDuckEvents()) {lossType = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;entry.setDucked(true);} else {lossType = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;}sendFocusLossLocked(entry.getAudioFocusInfo(), lossType);// The entry no longer holds focus, so take it out of the holders listmFocusHolders.remove(entry.getAudioFocusInfo().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.getAudioFocusInfo().getClientId(), entry);entry.addBlocker(newEntry);}}
流程六:对于永久失去焦点的permanentlyLost这个HashMap进行处理:
for (FocusEntry entry : permanentlyLost) {Log.d(TAG, "Cleaning up entry " + entry.getClientId());removeBlockerAndRestoreUnblockedWaitersLocked(entry);}
流程七:对于AudioRequester接受延迟获得焦点的处理:
if (delayFocusForCurrentRequest) {Log.e(TAG,"delayFocusForCurrentRequest");swapDelayedAudioFocusRequestLocked(afi);return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;}private void swapDelayedAudioFocusRequestLocked(AudioFocusInfo afi) {// If we are swapping to a different client then send the focus loss signalif (mDelayedRequest != null&& !afi.getClientId().equals(mDelayedRequest.getClientId())) {sendFocusLossLocked(mDelayedRequest, AudioManager.AUDIOFOCUS_LOSS);}mDelayedRequest = afi;}
总图如下:
接下来是释放焦点的流程:
流程一:从mFocusHloders和mFocusLoser中分别尝试删除AudioReleaser
private FocusEntry removeFocusEntryLocked(AudioFocusInfo afi) {Log.i(TAG, "removeFocusEntry " + afi.getClientId());// Remove this entry from our active or pending listFocusEntry deadEntry = mFocusHolders.remove(afi.getClientId());if (deadEntry == null) {deadEntry = mFocusLosers.remove(afi.getClientId());if (deadEntry == null) {// Caller is providing an unrecognzied clientId!?Log.w(TAG, "Audio focus abandoned by unrecognized client id: " + afi.getClientId());// This probably means an app double released focused for some reason. One// harmless possibility is a race between an app being told it lost focus and the// app voluntarily abandoning focus. More likely the app is just sloppy. :)// The more nefarious possibility is that the clientId is actually corrupted// somehow, in which case we might have a real focus entry that we're going to fail// to remove. If that were to happen, I'd expect either the app to swallow it// silently, or else take unexpected action (eg: resume playing spontaneously), or// else to see "Failure to signal ..." gain/loss error messages in the log from// this module when a focus change tries to take action on a truly zombie entry.}}return deadEntry;}
流程二:如果从mFocusHolders或mFocusLosers中找到了要删除的FocusEntry,就让mDelayedRequest走一遍evaluateFocusRequestLocked冲突函数,如果冲突结果是AUDIOFOCUS_REQUEST_GRANTED,那mDelayedRequest就成了焦点持有者:
代码一:private void removeBlockerAndRestoreUnblockedWaitersLocked(FocusEntry deadEntry) {attemptToGainFocusForDelayedAudioFocusRequest();removeBlockerAndRestoreUnblockedFocusLosersLocked(deadEntry);}
代码二:private void attemptToGainFocusForDelayedAudioFocusRequest() {if (!mEnabledDelayedFocusRequest || mDelayedRequest == null) {return;}int delayedFocusRequestResults = evaluateFocusRequestLocked(mDelayedRequest);if (delayedFocusRequestResults == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {FocusEntry focusEntry = mFocusHolders.get(mDelayedRequest.getClientId());mDelayedRequest = null;if (dispatchFocusGainedLocked(focusEntry.getAudioFocusInfo())== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {Log.e(TAG,"Failure to signal gain of audio focus gain for "+ "delayed focus clientId " + focusEntry.getClientId());mFocusHolders.remove(focusEntry.getClientId());removeBlockerFromBlockedFocusLosersLocked(focusEntry);sendFocusLossLocked(focusEntry.getAudioFocusInfo(),AudioManager.AUDIOFOCUS_LOSS);logFocusEvent("Did not gained delayed audio focus for "+ focusEntry.getClientId());}}}
流程三:如果mDelayRequest为null的话,就要从mFocusLoser中找下一个焦点持有者了
代码三:private void removeBlockerAndRestoreUnblockedFocusLosersLocked(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.removeBlocker(deadEntry);// Any entry whose blocking list becomes empty should regain focusif (entry.isUnblocked()) {Log.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);dispatchFocusGainedLocked(entry.getAudioFocusInfo());}}}
流程四:如果从mFocusHolders和mFocusLosers中没有找到要释放的这个FocusEntry,并且这个要释放的FocusEntry和mDelayedRequest有着相同的clientId,就把mDelayerRequest赋值为null
private void removeDelayedAudioFocusRequestLocked(AudioFocusInfo afi) {if (mDelayedRequest != null && afi.getClientId().equals(mDelayedRequest.getClientId())) {mDelayedRequest = null;}}
更多推荐
Android CarAudioFocus详解(二)
发布评论