1、为什么会有音频焦点机制?
我们android系统里面会安装各种多媒体软件,如果不制定一个有效合理的规则,各个应用各自为政,那么可能就会出现各种播放器、软件的混音。音频焦点机制规定某一时刻只能有一个应用获取到声音的焦点,这个时候就可以发出声音。当然,在这个应用获取到焦点之前,需要通知其他所用的应用失去焦点。
2、使用音频焦点
//获取焦点
AudioManager mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
mAudioManager.requestAudioFocus(cl, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
requestAudioFocus方法有三个参数
第一个参数:OnAudioFocusChangeListener ,此为一个监听控制器,通过这个监听器可以知道自己获取到焦点或者失去焦点。
第二个参数:streamType音频流类型,焦点获得之后的数据传输类型,这个参数不会影响焦点机制,不同的音频流类型同样遵守一个焦点机制。
第三个参数:durationHint,获得焦点的时间长短,定义了四种类型
a、AUDIOFOCUS_GAIN //长时间获得焦点,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS
b、AUDIOFOCUS_GAIN_TRANSIENT //短暂性获得焦点,用完应立即释放,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
c、AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK //短暂性获得焦点并降音,可混音播放,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
d、AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE //短暂性获得焦点,录音或者语音识别,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
我们通常使用前面三种类型就可以了。
看看OnAudioFocusChangeListener 的实现:
OnAudioFocusChangeListener cl = new OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
switch(focusChange){
case AudioManager.AUDIOFOCUS_LOSS:
//长时间丢失焦点,这个时候需要停止播放,并释放资源。根据不同的逻辑,有时候还会释放焦点
mAudioManager.abandonAudioFocus(cl);
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
//短暂失去焦点,这时可以暂停播放,但是不必要释放资源,因为很快又会获取到焦点
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
//短暂失去焦点,但是可以跟新的焦点拥有者同时播放,并做降噪处理
break;
case AudioManager.AUDIOFOCUS_GAIN:
//获得了音频焦点,可以播放声音
break;
}
}
};
3、获取音频焦点机制流程分析
我们调用AudioManager请求焦点,并在重构方法里面判断参数合法值,然后注册监听,通过Binder通信,和系统服务AudioService通信。我们看到OnAudioFocusChangeListener这个回调监听并没有发送给AudioService,取而代之的是mAudioFocusDispatcher这个参数作为和跨进程回调的桥梁。
requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint)
.......
registerAudioFocusListener(l);
.......
IAudioService service = getService();
try {
status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
mAudioFocusDispatcher, getIdForAudioFocusListener(l),
getContext().getOpPackageName() /* package name */, flags,
ap != null ? ap.cb() : null);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
IAudioFocusDispatcher 的设计很简洁,主要就是把从AudioService获取到的消息通过handler机制,交给另外的线程处理,从代码看到是交给了请求焦点的线程处理。
private final IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() {
public void dispatchAudioFocusChange(int focusChange, String id) {
final Message m = mServiceEventHandlerDelegate.getHandler().obtainMessage(
MSSG_FOCUS_CHANGE/*what*/, focusChange/*arg1*/, 0/*arg2 ignored*/, id/*obj*/);
mServiceEventHandlerDelegate.getHandler().sendMessage(m);
}
};
AudioService是运行在system_server进程里面的系统服务,其中维护了一个栈:Stack mFocusStack,此为维护焦点的关键。
申请焦点主要是如下几点:
a、检查当前栈顶的元素是否是Phone应用占用,如果Phone处于占用状态,那么focusGrantDelayed = true。
b、压栈之前,需要检查当前栈中是否已经有这个应用的记录,如果有的话就删除掉。
c、如果focusGrantDelayed = true,那么就会延迟申请,并把此次请求FocusRequester实例入栈,但是此时记录不是被压在栈顶,而是放在lastLockedFocusOwnerIndex这个位置,也就是打电话这个记录的后面;如果focusGrantDelayed = false,不需要延迟获得焦点,同样创建FocusRequester实例,但是先要通知栈里其他记录失去焦点,然后压入栈顶,最后通知自己获得焦点成功。
boolean focusGrantDelayed = false;
if (!canReassignAudioFocus()) { //这里判断焦点是否处于电话状态
if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
} else {
// request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
// granted right now, so the requester will be inserted in the focus stack
// to receive focus later
focusGrantDelayed = true;
}
}
// focus requester might already be somewhere below in the stack, remove it 此处便是移除栈里面相同clientId的记录
removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
//创建新的FocusRequester实例,为入栈做准备
final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
clientId, afdh, callingPackageName, Binder.getCallingUid(), this);
if (focusGrantDelayed) {
// focusGrantDelayed being true implies we can't reassign focus right
// which implies the focus stack is not empty.延迟
final int requestResult = pushBelowLockedFocusOwners(nfr);
if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
}
return requestResult;
} else {
// propagate the focus change through the stack没有延迟
if (!mFocusStack.empty()) {
propagateFocusLossFromGain_syncAf(focusChangeHint);
}
// push focus requester at the top of the audio focus stack
mFocusStack.push(nfr);
}
notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
}
4、释放音频焦点流程
释放音频焦点会有以下两种情况:
a:如果要释放的应用是在栈顶,则释放之后,还需要通知先在栈顶应用,其获得了audiofocus;
b:如果要释放的应用不是在栈顶,则只是移除这个记录,不需要更改当前audiofocus的占有情况。
private void removeFocusStackEntry(String clientToRemove, boolean signal,
boolean notifyFocusFollowers) {
// is the current top of the focus stack abandoning focus? (because of request, not death)
if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
{ //释放焦点的应用端在栈顶
//Log.i(TAG, " removeFocusStackEntry() removing top of stack");
FocusRequester fr = mFocusStack.pop();
fr.release();
if (notifyFocusFollowers) {
final AudioFocusInfo afi = fr.toAudioFocusInfo();
afi.clearLossReceived();
notifyExtPolicyFocusLoss_syncAf(afi, false);
}
if (signal) {
// notify the new top of the stack it gained focus
notifyTopOfAudioFocusStack();
}
} else {
//释放焦点的应用端不在栈顶
// focus is abandoned by a client that's not at the top of the stack,
// no need to update focus.
// (using an iterator on the stack so we can safely remove an entry after having
// evaluated it, traversal order doesn't matter here)
IteratorstackIterator = mFocusStack.iterator();
while(stackIterator.hasNext()) {
FocusRequester fr = stackIterator.next();
if(fr.hasSameClient(clientToRemove)) {
Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for "
+ clientToRemove);
stackIterator.remove();
fr.release();
}
}
}
}