前面文章分析了申请调用的流程,同时也有一篇执行流程的文字描述,但是总觉得不够清晰,让我们来用图示和代码结合说下这部分:
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 USAGE
Log.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 request
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 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 USAGE
Log.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 us
assert !entry.isUnblocked();
if (permanent) {
// This entry has now lost focus forever
sendFocusLossLocked(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 focus
entry.addBlocker(newEntry);
}
}
流程五:losers的处理:
for (FocusEntry entry : losers) {
// If we have focus (but are about to loose it), nobody should be blocking us yet
assert 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 list
mFocusHolders.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 back
mFocusLosers.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 signal
if (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 list
FocusEntry 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 requests
Iterator<FocusEntry> it = mFocusLosers.values().iterator();
while (it.hasNext()) {
FocusEntry entry = it.next();
// Remove the retiring entry from all blocker lists
entry.removeBlocker(deadEntry);
// Any entry whose blocking list becomes empty should regain focus
if (entry.isUnblocked()) {
Log.i(TAG, "Restoring unblocked entry " + entry.getClientId());
// Pull this entry out of the focus losers list
it.remove();
// Add it back into the focus holders list
mFocusHolders.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;
}
}