一.引言
今天我们来说说Android Audio系统中一套十分重要的机制-AudioFocus机制。AudioFoucs机制的设计主要是为了解决每个音源之间播放冲突的问题。系统建议每个音源播放的实例都应该去遵守的规范,但是呢它并不是一个强制需要遵守的规范,做音源的app的童鞋还是有必要了解下这个机制呢,下面呢让我们从源码的角度深度剖析一下AndroidFocus机制。在阅读文章前确保自己对Binder和Handler机制有一定的了解哦,要不然有些地方可能很难理解哈。
懒人党可以直接看后面总结,有感兴趣的知识点,可以认真阅读一下~~
二.AudioService
在说AudioFocus机制之前有必要先了解一下AudioManager和AudioService这两个类。了解Android服务的朋友都知道,Android自带的系统服分为native服务和Java层服务,Java层服务我们了解最深的应该就是ActivityManagerService了,它作为四大组件的调度者,在Android中扮演着十分重要的角色。它运行在system_server进程中,调用者经过Binder机制与它进行通信。它的代理对象就是ActivityManager。
同理我们的AudioService和AudioManager也是如此。
1.AudioService的启动
SystemServer#startOtherService()
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
.........
.........
//省略部分代码
if (!isArc) {
mSystemServiceManager.startService(AudioService.Lifecycle.class);
} else {
String className = context.getResources()
.getString(R.string.config_deviceSpecificAudioService);
try {
mSystemServiceManager.startService(className + "$Lifecycle");
} catch (Throwable e) {
reportWtf("starting " + className, e);
}
}
.........
.........
//省略部分代码
}
以上代码就是AudioService启动的代码。可以看出Android10以后呢系统启动AudioService是根据XML里面配置好的类名来启动这个服务的。所以我们可以定义自己的AudioService,只要修改一个配置文件就可以了。
2.AudioFocus机制的使用
第一种:Android 9.0之前使用
private void doRequestAudioFocus(){
//获取AudioService代理对象AudioManager
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.requestAudioFocus(new AudioManager.OnAudioFocusChangeListener() {
//这个OnAudioFocusChangeListener是用来监听焦点的变化的,主动申请,不会走这里的回调
//只有别的App抢占了焦点,或者丢失焦点,然后焦点栈栈顶是你申请的焦点对象,才会回调这个方法
@Override
public void onAudioFocusChange(int focusChange) {
}
},AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN);
//AudioManager.AUDIOFOCUS_GAIN 表示长焦点还是短焦点 一共有四种焦点类型
//public static final int AUDIOFOCUS_GAIN = 1;
//public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2;
//public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3;
//public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4;
}
第二种:Android 9.0及以后
@RequiresApi(api = Build.VERSION_CODES.O)
private void doRequestAudioFocus(){
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
AudioFocusRequest audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setOnAudioFocusChangeListener(new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
}
},mHandler)//这个mHandler很重要哈
.setAudioAttributes(new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).setUsage(AudioAttributes.USAGE_MEDIA).build())
.build();
audioManager.requestAudioFocus(audioFocusRequest);
}
释放焦点同理这里就不多说了,第二种方案,根据AudioAttributes的contentType和usage的值,系统会去底层帮我们去找播放实例对应的底层播放通路。第一种方案,其实就是根据我们requestAudioFocus的第二个参数值,streamType去寻找底层的播放通路的。说的直白点就是音频流类型是多媒体,铃声,还是游戏神马的**下面敲黑板了哈**,这里强调两个东西,一个就是这里的音频流类型,还有就是第二种方案中的传进去的mHandler这个东西,后面我们会说到。
三.源码剖析
下面废话不多说了,让我们撸起袖子干源码!!!!
上面说了两种抢占焦点的方案,其实最后系统走的都是同一个方法,我们就不多说了,直接以第二种方案来讲。
一.抢占焦点requestAudioFocus
1.AudioManager#requestAudioFocus
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {
//.......省略部分代码
//.......
//将所有抢占焦点的实例存放在一个map集合里面
registerAudioFocusRequest(afr);
//获取AudioService的代理对象
final IAudioService service = getService();
//.....
//这个ClientId非常的重要哈,它是根据我们传进来的Listener来算出来,如果是同一个AudioFocusListener那么这个
//clientId就是相同的。
final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
final BlockingFocusResultReceiver focusReceiver;
synchronized (mFocusRequestsLock) {
try {
// 从这里开始就到了AudioService里面去了,执行了Binder调用,我们直接去看AudioService的代码哈
status = service.requestAudioFocus(afr.getAudioAttributes(),
afr.getFocusGain(), mICallBack,
mAudioFocusDispatcher,
//这个mAudioFocusDispathcer很重要哈,它是一个本地的binder对象
//后面AudioService通知客户端角度丢失和回落这个起到了很大的作用。
clientId,
getContext().getOpPackageName() /* package name */, afr.getFlags(),
ap != null ? ap.cb() : null,
sdk);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
// default path with no external focus policy
return status;
}
if (mFocusRequestsAwaitingResult == null) {
mFocusRequestsAwaitingResult =
new HashMap<String, BlockingFocusResultReceiver>(1);
}
//构造一个BlockingFocusResultReceiver对象放到下面的map集合里面
focusReceiver = new BlockingFocusResultReceiver(clientId);
mFocusRequestsAwaitingResult.put(clientId, focusReceiver);
}
//......
return focusReceiver.requestResult();
}
2.AudioService#requestAudioFocus
public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
IAudioPolicyCallback pcb, int sdk) {
// permission checks
if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) {
//注意了哈 这里普通引用程序是抢占不了电话的焦点的。会直接返回抢占焦点失败
//判断是否剖是电话应用就是根据这个clientId来对比的,如果是通过调用requestAudioFocusForCall抢占的焦点
//这个ClientId会被设为“AudioFocus_For_Phone_Ring_And_Calls”
if (AudioSystem.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) {
if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_PHONE_STATE)) {
Log.e(TAG, "Invalid permission to (un)lock audio focus", new Exception());
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
}
}
//省略部分代码
//.......
//焦点管理的具体逻辑都在MediaFocusControl这个类里面 very important
return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
clientId, callingPackageName, flags, sdk,
forceFocusDuckingForAccessibility(aa, durationHint, Binder.getCallingUid()));
}
**这里呢补充一个知识点哈:**我们看下requestAudioFocusForCall这个方法
@UnsupportedAppUsage
public void requestAudioFocusForCall(int streamType, int durationHint) {
final IAudioService service = getService();
try {
service.requestAudioFocus(new AudioAttributes.Builder()
.setInternalLegacyStreamType(streamType).build(),
durationHint, mICallBack, null,
AudioSystem.IN_VOICE_COMM_FOCUS_ID,
getContext().getOpPackageName(),
AUDIOFOCUS_FLAG_LOCK,
null /* policy token */, 0 /* sdk n/a here*/);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
可以看到这个方法是不支持被打上了@UnsupportedAppUsage的注解,表面这个方法除了系统自己去调用的哈,我们普通App是没办法调用的,毕竟电话状态是一个非常隐私和重要的状态哈。其他的和我们之前说的都一样它的clientId被设置成了 AudioSystem.IN_VOICE_COMM_FOCUS_ID其实就是“AudioFocus_For_Phone_Ring_And_Calls”。
3.MediaFocusControl#requestAudioFocus
下面开始我们的重头戏哈,焦点栈的逻辑都在下面这个方法中得以体现了。
protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb,
IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName,
int flags, int sdk, boolean forceDuck) {
// 先ping一下,看看请求焦点的过程中,客户端挂没挂哈
if (!cb.pingBinder()) {
Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
//.....
synchronized(mAudioFocusLock) {
//可以看到哈我们的焦点栈是有大小限制的最大是100个,这个就要求什么呢?前面我们不是说到了ClientId这个东东吗
//ClientId这个东西最好要复用哦,要不然焦点栈满了,你可就抢占不到焦点了。复用了之后你抢占无数次,焦点栈都不
//满!下面的逻辑可以看出来滴,我可不乱说!
// private static final int MAX_STACK_SIZE = 100;
if (mFocusStack.size() > MAX_STACK_SIZE) {
Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
//对比一下是否是电话焦点,如果是标志位改成true代表进入电话状态
//这个boolean值很重要哈,我们下面会看到
boolean enteringRingOrCall = !mRingOrCallActive
& (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
if (enteringRingOrCall) { mRingOrCallActive = true; }
final AudioFocusInfo afiForExtPolicy;
if (mFocusPolicy != null) {
// construct AudioFocusInfo as it will be communicated to audio focus policy
afiForExtPolicy = new AudioFocusInfo(aa, Binder.getCallingUid(),
clientId, callingPackageName, focusChangeHint, 0 /*lossReceived*/,
flags, sdk);
} else {
afiForExtPolicy = null;
}
// handle the potential premature death of the new holder of the focus
// (premature death == death before abandoning focus)
// Register for client death notification
//(1)这里呢对客户端做了个死亡监听,如果客户端挂了,当然是要把这个焦点从焦点栈移除掉的
AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
try {
cb.linkToDeath(afdh, 0);
} catch (RemoteException e) {
// client has already died!
Log.w(TAG, "AudioFocus requestAudioFocus() could not link to "+cb+" binder death");
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
//这里做了个判断如果焦点栈不为空,且焦点栈的栈顶元素和当前的clientId一样,那就什么都不做
if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
final FocusRequester fr = mFocusStack.peek();
if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
// unlink death handler so it can be gc'ed.
// linkToDeath() creates a JNI global reference preventing collection.
cb.unlinkToDeath(afdh, 0);
notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
//.....省略部分代码
}
//(2)这个方法会去查询焦点栈里面是否有clientId一样的对象,如果有就把他移除掉,这说明了系统维护的焦点栈里面
//是不会存在相同clientId的焦点对象的,所以说如果你的clientId一样,无论抢多少次,焦点栈都是不会满滴!
//还有个要注意的是后面两个参数都是传的false 后面会说到
removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
//构造FocusRequester对象
final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
clientId, afdh, callingPackageName, Binder.getCallingUid(), this, sdk);
//...省略部分代码
{
// 如果焦点栈不为空通知原来栈顶的焦点你丢失了焦点
// 此时按理说,上一个抢占了焦点的音源应该停止播放了哈
if (!mFocusStack.empty()) {
//(3)
propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck);
}
// 将前面构造的FocusRequester对象压入焦点栈
mFocusStack.push(nfr);
nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
}
notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
if (ENFORCE_MUTING_FOR_RING_OR_CALL & enteringRingOrCall) {
//如果是电话焦点还需要在check一下
//(4)
runAudioCheckerForRingOrCallAsync(true/*enteringRingOrCall*/);
}
}//synchronized(mAudioFocusLock)
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
以上大体上,焦点抢占的逻辑,我们已经分析完了。下面看下我们打了数字标记的几个方法。
4.MediaFocusControl#AudioFocusDeathHandler
protected class AudioFocusDeathHandler implements IBinder.DeathRecipient {
private IBinder mCb; // To be notified of client's death
AudioFocusDeathHandler(IBinder cb) {
mCb = cb;
}
public void binderDied() {
synchronized(mAudioFocusLock) {
//如果客户端死了,移除焦点栈中的焦点
//这个死亡监听机制我们再写服务的时候经常会用到的,不了解的朋友可以学习一下哈
if (mFocusPolicy != null) {
removeFocusEntryForExtPolicy(mCb);
} else {
removeFocusStackEntryOnDeath(mCb);
}
}
}
}
5.MediaFocusControl#removeFocusStackEntry
我们看下移除焦点栈中拥有相同clientId的对象的实现
private void removeFocusStackEntry(String clientToRemove, boolean signal,
boolean notifyFocusFollowers) {
// 判断是否栈顶的和当前申请焦点的clientID一样
if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
{
//一样的话直接出栈,然后释放
FocusRequester fr = mFocusStack.pop();
fr.release();
//如果signadl为true通知新的焦点栈顶的对象,它获取到了焦点,否则不通知
//这个signal在requestAudioFocus时传的时false,栈顶元素肯定时当前抢占焦点的实例,没必要通知
//只有在abandonAudioFocus的时候才会传true,因为焦点栈栈顶元素可能变化,需要通知相应播放实例焦点回落了,可以重新恢复播放逻辑了
if (signal) {
// notify the new top of the stack it gained focus
notifyTopOfAudioFocusStack();
}
//....
} else {
//如果不在栈顶,就通过迭代器去迭代,找到相同的clientId的然后出站,移除掉
//因为不是栈顶所以焦点还是栈顶的,所以不需要做焦点变化的通知操作
Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
while(stackIterator.hasNext()) {
FocusRequester fr = stackIterator.next();
if(fr.hasSameClient(clientToRemove)) {
Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for "
+ clientToRemove);
stackIterator.remove();
// stack entry not used anymore, clear references
fr.release();
}
}
}
}
6.MediaFocusControl#propagateFocusLossFromGain_syncAf()
private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr,
boolean forceDuck) {
final List<String> clientsToRemove = new LinkedList<String>();
// going through the audio focus stack to signal new focus, traversing order doesn't
// matter as all entries respond to the same external focus gain
for (FocusRequester focusLoser : mFocusStack) {
final boolean isDefinitiveLoss =
//调用FocusRequester处理
focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
}
}
7.FocusRequester#handleFocusLossFromGain()
boolean handleFocusLossFromGain(int focusGain, final FocusRequester frWinner, boolean forceDuck)
{
final int focusLoss = focusLossForGainRequest(focusGain);
handleFocusLoss(focusLoss, frWinner, forceDuck);
return (focusLoss == AudioManager.AUDIOFOCUS_LOSS);
}
void handleFocusLoss(int focusLoss, @Nullable final FocusRequester frWinner, boolean forceDuck)
{
//.......
//省略部分代码
final IAudioFocusDispatcher fd = mFocusDispatcher;
if (fd != null) {
if (DEBUG) {
Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to "
+ mClientId);
}
mFocusController.notifyExtPolicyFocusLoss_syncAf(
toAudioFocusInfo(), true /* wasDispatched */);
mFocusLossWasNotified = true;
//这个fd就是我们之前抢占焦点时说的mAudioFocusDispathcer这个对象的服务端代理对象
//通过这个代理对象去调用客户端及App端通知它焦点出现变化了。
fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
}
}
} catch (android.os.RemoteException e) {
Log.e(TAG, "Failure to signal loss of audio focus due to:", e);
}
}
8.AudioManager#mAudioFocusDispatcher
下面我们来看下App焦点回落和焦点丢失,App是怎么收到通知的哈。mAudioFocusDispatcher是AudioManager的成员变量,可以看到它时一个本地的Binder对象哈,也是通过Binder和服务端通信的。
private final IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() {
@Override
public void dispatchAudioFocusChange(int focusChange, String id) {
final FocusRequestInfo fri = findFocusRequestInfo(id);
if (fri != null) {
final OnAudioFocusChangeListener listener =
fri.mRequest.getOnAudioFocusChangeListener();
if (listener != null) {
//还记得咱们上面在说第二种抢占焦点的方案时,传了个Handler进来嘛。
//如果我们没有传这个Handler对象就会用mServiceEventHandlerDelegate的Handler,这个Handler呢是个和主线程Looper绑定的Handler,也就是说它的回调是在主线程的。
//这个时候如果主线程出现了卡顿,你可能就不能及时收到焦点变化的信息啦
//怎么解决这个问题呢?我们可以创建个自己的Handler和子线程绑定Looper绑定的哈
//然后就算主线程卡顿,我们照样可以及时收到焦点变化的通知哈!!!
final Handler h = (fri.mHandler == null) ?
mServiceEventHandlerDelegate.getHandler() : fri.mHandler;
final Message m = h.obtainMessage(
MSSG_FOCUS_CHANGE/*what*/, focusChange/*arg1*/, 0/*arg2 ignored*/,
id/*obj*/);
h.sendMessage(m);
}
}
}
8.AudioManager#mServiceEventHandlerDelegate
private class ServiceEventHandlerDelegate {
private final Handler mHandler;
ServiceEventHandlerDelegate(Handler handler) {
Looper looper;
//可以看到默认是主线程的Looper哈
if (handler == null) {
if ((looper = Looper.myLooper()) == null) {
//获取主线程的Looper对象
looper = Looper.getMainLooper();
}
} else {
looper = handler.getLooper();
}
if (looper != null) {
// implement the event handler delegate to receive events from audio service
mHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSSG_FOCUS_CHANGE: {
final FocusRequestInfo fri = findFocusRequestInfo((String)msg.obj);
if (fri != null) {
final OnAudioFocusChangeListener listener =
fri.mRequest.getOnAudioFocusChangeListener();
if (listener != null) {
Log.d(TAG, "dispatching onAudioFocusChange("
+ msg.arg1 + ") to " + msg.obj);
//这里直接通过我们抢占焦点时传进来的listener回调焦点变化的通知
listener.onAudioFocusChange(msg.arg1);
}
}
} break;
}
至此对于抢占焦点的流程我们已经分析的很清楚了哈,至于释放焦点没什么好说的,和上面的流程差不多。我们再来看看上面遗留的一个问题。就是我们在处理焦点逻辑的时候,判断了一个电话的状态。下面我们来看看电话状态的焦点有什么不一样的哈。
8.MediaFocusControl#runAudioCheckerForRingOrCallAsync
MediaFocusControl在处理抢占焦点逻辑的时候,如果确认是电话焦点,最后还会执行这个方法,我们来看看这个方法做了些什么事。
private void runAudioCheckerForRingOrCallAsync(final boolean enteringRingOrCall) {
new Thread() {
public void run() {
if (enteringRingOrCall) {
try {
Thread.sleep(RING_CALL_MUTING_ENFORCEMENT_DELAY_MS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (mAudioFocusLock) {
//这里确认了如果是电话状态会调用mFocusEnforcer对象的mutePlayersForCall方法,这个方法看名字是会去静音,那么到底是不是呢,我们继续往下看哈
//如果退出电话状态就调用unmutePlayersForCall的方法
//mFocusEnforcer对象是在AudioService中构造MediaFocusControl通过构造方法传进来。其实就是PlaybackActivityMonitor这个对象。我继续往下看。
//USAGES_TO_MUTE_IN_RING_OR_CALL
//private final static int[] USAGES_TO_MUTE_IN_RING_OR_CALL =
{ AudioAttributes.USAGE_MEDIA, AudioAttributes.USAGE_GAME };
if (mRingOrCallActive) {
mFocusEnforcer.mutePlayersForCall(USAGES_TO_MUTE_IN_RING_OR_CALL);
} else {
mFocusEnforcer.unmutePlayersForCall();
}
}
}
}.start();
}
}
//mPlaybackMonitor =
new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
//mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
8.PlaybackActivityMonitor#mutePlayersForCall
public void mutePlayersForCall(int[] usagesToMute) {
synchronized (mPlayerLock) {
//注意了mPlayers存放了所有MediaPlayer和AudioTrack的实例
final Set<Integer> piidSet = mPlayers.keySet();
final Iterator<Integer> piidIterator = piidSet.iterator();
// find which players to mute
while (piidIterator.hasNext()) {
final Integer piid = piidIterator.next();
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
if (apc == null) {
continue;
}
final int playerUsage = apc.getAudioAttributes().getUsage();
boolean mute = false;
for (int usageToMute : usagesToMute) {
if (playerUsage == usageToMute) {
mute = true;
break;
}
}
if (mute) {
try {
sEventLogger.log((new AudioEventLogger.StringEvent("call: muting piid:"
+ piid + " uid:" + apc.getClientUid())).printLog(TAG));
//找到对应音频流的播放实例音量全都设置为0
apc.getPlayerProxy().setVolume(0.0f);
mMutedPlayers.add(new Integer(piid));
} catch (Exception e) {
Log.e(TAG, "call: error muting player " + piid, e);
}
}
}
}
}
//解除静音同理
@Override
public void unmutePlayersForCall() {
if (DEBUG) {
Log.v(TAG, "unmutePlayersForCall()");
}
synchronized (mPlayerLock) {
if (mMutedPlayers.isEmpty()) {
return;
}
for (int piid : mMutedPlayers) {
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
if (apc != null) {
try {
sEventLogger.log(new AudioEventLogger.StringEvent("call: unmuting piid:"
+ piid).printLog(TAG));
apc.getPlayerProxy().setVolume(1.0f);
} catch (Exception e) {
Log.e(TAG, "call: error unmuting player " + piid + " uid:"
+ apc.getClientUid(), e);
}
}
}
mMutedPlayers.clear();
}
}
9.AudioTrack的创建
上面说到了系统中所有AudioTrack和MediaPlayer都会存放在PlaybackActivityMonitor的mPlayers的成员变量里面,我们来看看是不是这么回事。因为MediaPlayer底层还是使用的AudioTrack我们直接看AudioTrack哈。
AudioTrack的构造方法:
private AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int mode, int sessionId, boolean offload)
throws IllegalArgumentException {
//......省略部分代码
//调用父类的baseRegisterPlayer
baseRegisterPlayer();
}
//PlayerBase#baseRegisterPlayer
protected void baseRegisterPlayer() {
int newPiid = AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
//.......
try {
//调用AudioService的trackPlayer方法
newPiid = getService().trackPlayer(
new PlayerIdCard(mImplType, mAttributes, new IPlayerWrapper(this)));
} catch (RemoteException e) {
Log.e(TAG, "Error talking to audio service, player will not be tracked", e);
}
mPlayerIId = newPiid;
}
//AudioService#trackPlayer
public int trackPlayer(PlayerBase.PlayerIdCard pic) {
return mPlaybackMonitor.trackPlayer(pic);
}
//PlaybackActivityMonitor#trackPlayer
public int trackPlayer(PlayerBase.PlayerIdCard pic) {
final int newPiid = AudioSystem.newAudioPlayerId();
if (DEBUG) { Log.v(TAG, "trackPlayer() new piid=" + newPiid); }
final AudioPlaybackConfiguration apc =
new AudioPlaybackConfiguration(pic, newPiid,
Binder.getCallingUid(), Binder.getCallingPid());
apc.init();
sEventLogger.log(new NewPlayerEvent(apc));
synchronized(mPlayerLock) {
//果然如此呀,所有的AudioTrack都被放到mPlayers的集合里面去了
mPlayers.put(newPiid, apc);
}
return newPiid;
}
二.释放焦点abandonAudioFocus
(1)audioManager.abandonAudioFocus(audioFocusChangeListener);
(2)audioManager.abandonAudioFocusRequest(audioFocusRequest);
释放焦点前面的流程和前面的抢占焦点的逻辑差不多。就不多说了我们直接看MediaFocusControl中的逻辑。
MediaFocusControl#abandonAudioFocus
protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa,
String callingPackageName) {
try {
// this will take care of notifying the new focus owner if needed
synchronized(mAudioFocusLock) {
//....省略部分代码
if (mFocusPolicy != null) {
final AudioFocusInfo afi = new AudioFocusInfo(aa, Binder.getCallingUid(),
clientId, callingPackageName, 0 /*gainRequest*/, 0 /*lossReceived*/,
0 /*flags*/, 0 /* sdk n/a here*/);
if (notifyExtFocusPolicyFocusAbandon_syncAf(afi)) {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
}
boolean exitingRingOrCall = mRingOrCallActive
& (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
if (exitingRingOrCall) { mRingOrCallActive = false; }
//做移除焦点的操作这个方法之前已经分析过了哈,这里可以看到signal是传的true
//可以返回去看看
//注意了,如果栈中是A-B-C 这个时候B释放焦点的时候 最后焦点栈中是A-C 只会移除释放那个
removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
if (ENFORCE_MUTING_FOR_RING_OR_CALL & exitingRingOrCall) {
//退出电话焦点和前面的是一样的哈,做解除静音的操作,前面已经说过就不多说了。
runAudioCheckerForRingOrCallAsync(false/*enteringRingOrCall*/);
}
}
} catch (java.util.ConcurrentModificationException cme) {
// Catching this exception here is temporary. It is here just to prevent
// a crash seen when the "Silent" notification is played. This is believed to be fixed
// but this try catch block is left just to be safe.
Log.e(TAG, "FATAL EXCEPTION AudioFocus abandonAudioFocus() caused " + cme);
cme.printStackTrace();
}
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
MediaFocusControl#removeFocusStackEntry
MediaFocusControl#notifyTopOfAudioFocusStack
private void notifyTopOfAudioFocusStack() {
// notify the top of the stack it gained focus
if (!mFocusStack.empty()) {
if (canReassignAudioFocus()) {
//返回栈顶元素调用handleFocusGain通知焦点回落了
mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);
}
}
}
FocusRequester#handleFocusGain
void handleFocusGain(int focusGain) {
try {
final IAudioFocusDispatcher fd = mFocusDispatcher;
if (fd != null) {
if (DEBUG) {
Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to "
+ mClientId);
}
if (mFocusLossWasNotified) {
//Binder调用到相应的App 后面流程之前已经分析过了哈 一模一样 不多说了
fd.dispatchAudioFocusChange(focusGain, mClientId);
}
}
} catch (android.os.RemoteException e) {
Log.e(TAG, "Failure to signal gain of audio focus due to: ", e);
}
}
三.场景模拟
结合前面说的,上面的图片一目了然哈~
四.总结
说了那么多,总结下我们今天学到的知识点。对你有帮助的话,点个收藏点个赞哈~
1. 抢占焦点的两种方式。
2. 9.0以后可以传一个Handler,如果不传,焦点回调默认在主线程,传了,我们的Handler和哪个线程的Looper绑定,回调就在哪个线程。
3. 电话焦点只有系统可以申请,如果是电话焦点,系统会把所有多媒体和游戏的音频流实例全部mute。同理电话焦点释放会解除mute操作。
4. 所有通过AudioTrack和MediaPlayer创建的播放实例都会存在PlaybackActivityMonitor对象的mPlayers的成员变量中。
5. 系统管理的焦点栈有大小限制限制为100.大于100,抢占焦点失败。
6. 电话焦点状态下,其他app的所有抢占焦点的操作都会失败。
7. 我们传的OnAudioFocusListener决定了ClientID,相同的ClientID焦点栈中不会重复存储,OnAudioFocusListener最好进行复用,除了特殊的业务场景。电话焦点的ClientID由系统写死了。