本文主要是介绍分析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)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!