分析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抢占了焦点,或者丢失焦点,然后焦点栈栈顶是你申请的焦点对象,才会回调这个方法
            @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由系统写死了。

  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值