分析Android Framework源码--彻底了解Android AudioFocus机制,肯定有你不知道的知识点(基于Android10.0)

本文主要是介绍分析Android Framework源码--彻底了解Android AudioFocus机制,肯定有你不知道的知识点(基于Android10.0),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一.引言

今天我们来说说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抢占了焦点,或者丢失焦点,然后焦点栈栈顶是你申请的焦点对象,才会回调这个方法@Overridepublic 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() {@Overridepublic 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 policyreturn 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 checksif ((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 importantreturn mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,clientId, callingPackageName, flags, sdk,forceFocusDuckingForAccessibility(aa, durationHint, Binder.getCallingUid()));}

**这里呢补充一个知识点哈:**我们看下requestAudioFocusForCall这个方法

    @UnsupportedAppUsagepublic 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 policyafiForExtPolicy = 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 deathAudioFocusDeathHandler(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 focusnotifyTopOfAudioFocusStack();}//....} 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 referencesfr.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 gainfor (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() {@Overridepublic 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 servicemHandler = new Handler(looper) {@Overridepublic 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 mutewhile (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));//找到对应音频流的播放实例音量全都设置为0apc.getPlayerProxy().setVolume(0.0f);mMutedPlayers.add(new Integer(piid));} catch (Exception e) {Log.e(TAG, "call: error muting player " + piid, e);}}}}}//解除静音同理@Overridepublic 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 {//......省略部分代码//调用父类的baseRegisterPlayerbaseRegisterPlayer();}//PlayerBase#baseRegisterPlayerprotected 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#trackPlayerpublic int trackPlayer(PlayerBase.PlayerIdCard pic) {return mPlaybackMonitor.trackPlayer(pic);}//PlaybackActivityMonitor#trackPlayerpublic 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 neededsynchronized(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 focusif (!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由系统写死了。

这篇关于分析Android Framework源码--彻底了解Android AudioFocus机制,肯定有你不知道的知识点(基于Android10.0)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/446353

相关文章

Python中你不知道的gzip高级用法分享

《Python中你不知道的gzip高级用法分享》在当今大数据时代,数据存储和传输成本已成为每个开发者必须考虑的问题,Python内置的gzip模块提供了一种简单高效的解决方案,下面小编就来和大家详细讲... 目录前言:为什么数据压缩如此重要1. gzip 模块基础介绍2. 基本压缩与解压缩操作2.1 压缩文

Spring事务传播机制最佳实践

《Spring事务传播机制最佳实践》Spring的事务传播机制为我们提供了优雅的解决方案,本文将带您深入理解这一机制,掌握不同场景下的最佳实践,感兴趣的朋友一起看看吧... 目录1. 什么是事务传播行为2. Spring支持的七种事务传播行为2.1 REQUIRED(默认)2.2 SUPPORTS2

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

MySQL中的锁机制详解之全局锁,表级锁,行级锁

《MySQL中的锁机制详解之全局锁,表级锁,行级锁》MySQL锁机制通过全局、表级、行级锁控制并发,保障数据一致性与隔离性,全局锁适用于全库备份,表级锁适合读多写少场景,行级锁(InnoDB)实现高并... 目录一、锁机制基础:从并发问题到锁分类1.1 并发访问的三大问题1.2 锁的核心作用1.3 锁粒度分

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

python中Hash使用场景分析

《python中Hash使用场景分析》Python的hash()函数用于获取对象哈希值,常用于字典和集合,不可变类型可哈希,可变类型不可,常见算法包括除法、乘法、平方取中和随机数哈希,各有优缺点,需根... 目录python中的 Hash除法哈希算法乘法哈希算法平方取中法随机数哈希算法小结在Python中,

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重

Redis的持久化之RDB和AOF机制详解

《Redis的持久化之RDB和AOF机制详解》:本文主要介绍Redis的持久化之RDB和AOF机制,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述RDB(Redis Database)核心原理触发方式手动触发自动触发AOF(Append-Only File)核

如何在Mac上彻底删除Edge账户? 手动卸载Edge浏览器并清理残留文件技巧

《如何在Mac上彻底删除Edge账户?手动卸载Edge浏览器并清理残留文件技巧》Mac上的Edge账户里存了不少网站密码和个人信息,结果同事一不小心打开了,简直尴尬到爆炸,想要卸载edge浏览器并清... 如果你遇到 Microsoft Edge 浏览器运行迟缓、频繁崩溃或网页加载异常等问题,可以尝试多种方

Android DataBinding 与 MVVM使用详解

《AndroidDataBinding与MVVM使用详解》本文介绍AndroidDataBinding库,其通过绑定UI组件与数据源实现自动更新,支持双向绑定和逻辑运算,减少模板代码,结合MV... 目录一、DataBinding 核心概念二、配置与基础使用1. 启用 DataBinding 2. 基础布局