美文网首页程序猿葵花宝典互联网科技Android知识
Android音频焦点详解(下)——源码详解

Android音频焦点详解(下)——源码详解

作者: landptf | 来源:发表于2017-02-20 22:53 被阅读1547次

    转载请注明出处:http://www.jianshu.com/p/e5785dcba952


    本文基于Android7.1.1版本进行分析,主要涉及以下几个文件:
    1 AudioManager --> /frameworks/base/media/java/android/media/
    2 AudioService --> /frameworks/base/services/core/java/com/android/server/audio/
    3 MediaFocusControl -->/frameworks/base/services/core/java/com/android/server/audio/
    4 FocusRequester --> /frameworks/base/services/core/java/com/android/server/audio/
    5 AudioAttributes --> /frameworks/base/media/java/android/media/
    6 AudioFocusInfo --> /frameworks/base/media/java/android/media/
    7 AudioSystem --> /frameworks/base/media/java/android/media/

    之前一直用的是4.4的源码,这两天看了下7.1的源码发现这块内容改动还是挺大的,主要是新增了几个文件,并且对MediaFocusControl类进行了瘦身,代码从2700多行减到了500多行。
    我们从入口方法requestAudioFocus开始,还记得我们是怎么使用该方法的么?
    通过Audio Manager的对象来调用

    mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
    

    具体请参考我的上篇博客点击这里


    从AudioManager开始(有些方法里代码较多,只贴出来部分关键代码,下同)

    public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) {
        int status = AUDIOFOCUS_REQUEST_FAILED;
        try {
            //调用内部重载后的方法
            status = requestAudioFocus(l,
                    new AudioAttributes.Builder()
                            .setInternalLegacyStreamType(streamType).build(),
                    durationHint,
                    0 /* flags, legacy behavior */);
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "Audio focus request denied due to ", e);
        }
        return status;
    }
    

    根据传进来的streamType,构造了一个AudioAttributes对象向下传递,这个AudioAttributes主要是存储了一些音频流信息的属性,后面会用到。接着看

    @SystemApi
    public int requestAudioFocus(OnAudioFocusChangeListener l,
                                 @NonNull AudioAttributes requestAttributes,
                                 int durationHint,
                                 int flags) throws IllegalArgumentException {
        return requestAudioFocus(l, requestAttributes, durationHint,
                flags & AUDIOFOCUS_FLAGS_APPS,
                null /* no AudioPolicy*/);
    }
    

    这里面对falgs进行了与操作,由于之前传进来的是0,所以转换后的结果还是0。接着看

    @SystemApi
    public int requestAudioFocus(OnAudioFocusChangeListener l,
                                 @NonNull AudioAttributes requestAttributes,
                                 int durationHint,
                                 int flags,
                                 AudioPolicy ap) throws IllegalArgumentException {
        // 参数检查
        //...
    
        int status = AUDIOFOCUS_REQUEST_FAILED;
        registerAudioFocusListener(l);
        //获取AudioService实例,这里采用了binder通信,我们只需要知道从此处开始将会进入AudioServie的requestAudioFocus方法
        IAudioService service = getService();
        try {
            status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
                    mAudioFocusDispatcher, getIdForAudioFocusListener(l),
                    getContext().getOpPackageName() /* package name */, flags,
                    ap != null ? ap.cb() : null);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        return status;
    }
    

    这里面重点看一下 registerAudioFocusListener(l);

    private final HashMap<String, OnAudioFocusChangeListener> mAudioFocusIdListenerMap =
                new HashMap<String, OnAudioFocusChangeListener>();
    
    public void registerAudioFocusListener(OnAudioFocusChangeListener l) {
        synchronized (mFocusListenerLock) {
            if (mAudioFocusIdListenerMap.containsKey(getIdForAudioFocusListener(l))) {
                return;
            }
            mAudioFocusIdListenerMap.put(getIdForAudioFocusListener(l), l);
        }
    }
    
    private String getIdForAudioFocusListener(OnAudioFocusChangeListener l) {
        if (l == null) {
            return new String(this.toString());
        } else {
            return new String(this.toString() + l.toString());
        }
    }
    

    这段代码比较好理解了,我们根据“l”来生成一个key,存储在了mAudioFocusIdListenerMap对象中,而值就是OnAudioFocusChangeListener的对象。这里用了HashMap以保证key的唯一性。至于这个map有什么用呢,先不要着急,在后面会用到的。
    我们在调用AudioService的requestAudioFocus时传入了一个 mAudioFocusDispatcher参数,这个又有什么用呢?先不要着急等后面用到的时候再来看。

    好了,我们现在进入AudioService的requestAudioFocus继续分析。

    慢着,AudioManager中还有一个和requestAudioFocus相关的方法,那就是requestAudioFocusForCall,通过名字可以知道这是跟电话相关的接口,看下源码

    public void requestAudioFocusForCall(int streamType, int durationHint) {
        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 */);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
    

    在代码中全局搜索requestAudioFocusForCall,发现只有CallManager中有调用,而该方法被增加了@hide注解,不过第三方应用可以通过一些特殊方式来调用,这里就不展开讲解了。
    留意一下AudioSystem.IN_VOICE_COMM_FOCUS_ID和AUDIOFOCUS_FLAG_LOCK后面会用到。

    不知道大家有没有晕呢!我们先来梳理一下吧!以上的代码均是在AudioManager中,其中requestAudioFocus重载了三次,但只有一个是对外开放的。额外看到了一个为电话而生的requestAudioFocusForCall。


    AudioService直接上源码

    public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
                                 IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
                                 IAudioPolicyCallback pcb) {
        //权限检查,这里面就用到了AudioSystem.IN_VOICE_COMM_FOCUS_ID
        //也就是说如果我们的clientId等于AudioSystem.IN_VOICE_COMM_FOCUS_ID
        //要申请MODIFY_PHONE_STATE的权限,否则会申请焦点失败。
        return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
                clientId, callingPackageName, flags);
    }
    

    AudioService只是做了中转,并没有做实际的操作,具体实现都是在MediaFocusControl中
    下面我们进入MediaFocusControl中

    protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
                                    IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) {
        //...基础检查
    
        //这一块定义了局部变量focusGrantDelayed,从名字上可以知道延迟申请焦点的意思
        //而且只有当canReassignAudioFocus()返回true的时候,focusGrantDelayed才为true,也就是需要延迟申请,详见下方注解1.
        synchronized (mAudioFocusLock) {
                boolean focusGrantDelayed = false;
                if (!canReassignAudioFocus()) {
                if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                } else {
                    focusGrantDelayed = true;
                }
            }
            //如果mFocusStack不为空,并且栈顶的clientId与要申请焦点的clientId相同
            if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
                //得到栈顶元素的FocusRequester对象
                final FocusRequester fr = mFocusStack.peek();
                if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
                    //如果申请的时长和flags都相同,则表示重复申请,直接返回成功
                    return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                }
               //非延迟申请
                if (!focusGrantDelayed) {
                    //如果如果申请的时长和flags有一个不相同,则认为需要重新申请,此时需要将栈顶的元素出栈
                    mFocusStack.pop();
                    fr.release();
                }
            }
            /*说了这么多可能不太好理解,这里举个🌰
            * 先调用mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
            * 再调用mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
            * 两次的申请时长不同,因此会将前一次的申请出栈,然后再处理新的申请
            **/
    
            //移除可能在栈中其他位置存在着相同clientId的元素
            removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
            //构造FocusRequester对象,详见下方注解2.
            final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
                    clientId, afdh, callingPackageName, Binder.getCallingUid(), this);
            //我们前面分析了什么情况下focusGrantDelayed为true,这里重复一遍,也就是我们在打电话的过程中,音乐去申请焦点。
            if (focusGrantDelayed) {
                //将其插入栈中,什么位置呢?遍历mFocusStack,从栈顶开始isLockedFocusOwner(前面介绍过该放法)为true的元素的下方。
                final int requestResult = pushBelowLockedFocusOwners(nfr);
                return requestResult;
            } else {
                if (!mFocusStack.empty()) {
                    //该方法很重要,通知栈中其他元素丢失焦点,详见下方注解3.
                    propagateFocusLossFromGain_syncAf(focusChangeHint);
                }
                //将FocusRequester对象压入栈中
                mFocusStack.push(nfr);
            }
        }
        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    }
    

    注解:
    1 这里面出现了mFocusStack变量,栈结构后进先出,用来维护各client的申请和释放,
    当满足mFocusStack不为空,并且当前栈顶(peek得到栈顶元素,但是并未出栈)的clientId为AudioSystem.IN_VOICE_COMM_FOCUS_ID,这个熟悉吧!或者fr.isLockedFocusOwner,这个FocusOwner又是什么鬼呢!我们需要进入FocusRequester类中来看,这里的mGrantFlags是在FocusRequester的构造方法中初始化的,其实就是前面传进来的flags,而AUDIOFOCUS_FLAG_LOCK也熟悉吧,没印象的回头看一下requestAudioFocusForCall方法我叫你们留意的两个参数。这段代码的最终含义就是如果正在打电话的过程中,其他应用申请焦点会延迟申请。

    //MediaFocusControl.java
    private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();
    private boolean canReassignAudioFocus() {
        if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) {
            return false;
        }
        return true;
    }
    
    private boolean isLockedFocusOwner(FocusRequester fr) {
        return (fr.hasSameClient(AudioSystem.IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner());
    }
    
    //FocusRequester.java
    boolean isLockedFocusOwner() {
        return ((mGrantFlags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0);
    }
    

    2 该方法中初始化了很多个变量,大概有个印象就好,我们在注解3中会详细讲解其中几个关键变量

    FocusRequester(AudioAttributes aa, int focusRequest, int grantFlags,
                   IAudioFocusDispatcher afl, IBinder source, String id, AudioFocusDeathHandler hdlr,
                   String pn, int uid, @NonNull MediaFocusControl ctlr) {
        mAttributes = aa;
        mFocusDispatcher = afl;
        mSourceRef = source;
        mClientId = id;
        mDeathHandler = hdlr;
        mPackageName = pn;
        mCallingUid = uid;
        mFocusGainRequest = focusRequest;
        mGrantFlags = grantFlags;
        mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
        mFocusController = ctlr;
    }
    

    3 通知栈中其他元素丢失焦点流程

    //遍历mFocusStack,调用FocusRequester对象的handleExternalFocusGain方法
    private void propagateFocusLossFromGain_syncAf(int focusGain) {
        Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
        while (stackIterator.hasNext()) {
            stackIterator.next().handleExternalFocusGain(focusGain);
        }
    }
    

    stackIterator.next()得到的是FocusRequester对象,因此查看FocusRequester中handleExternalFocusGain的源码,这里面假设我们传进来的参数是AudioManager.AUDIOFOCUS_GAIN

    void handleExternalFocusGain(int focusGain) {
        //下面分别看一下focusLossForGainRequest和handleFocusLoss
        int focusLoss = focusLossForGainRequest(focusGain);
        handleFocusLoss(focusLoss);
    }
    /**
     * 这个方法比较长,主要关注两个变量gainRequest和mFocusLossReceived
     * mFocusLossReceived这个值是多少呢!我们发现在注解2中FocusRequester的构造方法中进行的赋值
     * mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
     * gainRequest这个是我们传进来的,例如AudioManager.AUDIOFOCUS_GAIN
     * return AudioManager.AUDIOFOCUS_LOSS
     * 若我们传进来的参数是AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
     * 则return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
     */
    private int focusLossForGainRequest(int gainRequest) {
        switch (gainRequest) {
            case AudioManager.AUDIOFOCUS_GAIN:
                switch (mFocusLossReceived) {
                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                    case AudioManager.AUDIOFOCUS_LOSS:
                    case AudioManager.AUDIOFOCUS_NONE:
                        return AudioManager.AUDIOFOCUS_LOSS;
                }
            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
                switch (mFocusLossReceived) {
                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                    case AudioManager.AUDIOFOCUS_NONE:
                        return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
                    case AudioManager.AUDIOFOCUS_LOSS:
                        return AudioManager.AUDIOFOCUS_LOSS;
                }
            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
                switch (mFocusLossReceived) {
                    case AudioManager.AUDIOFOCUS_NONE:
                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                        return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                        return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
                    case AudioManager.AUDIOFOCUS_LOSS:
                        return AudioManager.AUDIOFOCUS_LOSS;
                }
            default:
                Log.e(TAG, "focusLossForGainRequest() for invalid focus request " + gainRequest);
                return AudioManager.AUDIOFOCUS_NONE;
        }
    }
    
    void handleFocusLoss(int focusLoss) {
        try {
            if (focusLoss != mFocusLossReceived) {
                mFocusLossReceived = focusLoss;
                //...
                final IAudioFocusDispatcher fd = mFocusDispatcher;
                if (fd != null) {
                    fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
                }
            }
        } catch (android.os.RemoteException e) {
            Log.e(TAG, "Failure to signal loss of audio focus due to:", e);
        }
    }
    

    重点看一下handleFocusLoss方法的

    fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
    

    通过mFocusDispatcher对象调用了dispatchAudioFocusChange方法,将mFocusLossReceived和mClientId传了进去。现在我们回头一步步看mFocusDispatcher是如何传进来的。
    3.1 FocusRequester构造方法的第四个参数IAudioFocusDispatcher afl
    3.2 MediaFocusControl的requestAudioFocus方法的第四个参数IAudioFocusDispatcher fd
    3.3 AudioService的requestAudioFocus方法的第四个参数IAudioFocusDispatcher fd
    最终我们在AudioManager中找到了

    private final IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() {
        public void dispatchAudioFocusChange(int focusChange, String id) {
            final Message m = mServiceEventHandlerDelegate.getHandler().obtainMessage(
                    MSSG_FOCUS_CHANGE/*what*/, focusChange/*arg1*/, 0/*arg2 ignored*/, id/*obj*/);
            mServiceEventHandlerDelegate.getHandler().sendMessage(m);
        }
    };
    
    private class ServiceEventHandlerDelegate {
        private final Handler mHandler;
        ServiceEventHandlerDelegate(Handler handler) {
            Looper looper;
            if (handler == null) {
                if ((looper = Looper.myLooper()) == null) {
                    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:
                                OnAudioFocusChangeListener listener = null;
                                synchronized (mFocusListenerLock) {
                                    listener = findFocusListener((String) msg.obj);
                                }
                                if (listener != null) {
                                    Log.d(TAG, "AudioManager dispatching onAudioFocusChange("
                                            + msg.arg1 + ") for " + msg.obj);
                                    listener.onAudioFocusChange(msg.arg1);
                                }
                                break;
                             //***
                        }
                    }
                };
            } else {
                mHandler = null;
            }
        }
        Handler getHandler() {
            return mHandler;
        }
    }
    

    在dispatchAudioFocusChange方法中通过mServiceEventHandlerDelegate将事件分发到了另外的线程中,这也是让AudioService从事件分发中抽离出来

    OnAudioFocusChangeListener listener = null;
    synchronized (mFocusListenerLock) {
        //msg.obj就是clientId
        listener = findFocusListener((String) msg.obj);
    }
    if (listener != null) {
        //我们得到了listener之后回调onAudioFocusChange
        //如果当前申请的焦点时长为AudioManager.AUDIOFOCUS_GAIN,则msg.arg1=AudioManager.AUDIOFOCUS_LOSS
        //如果当前申请的焦点时长为AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,则msg.arg1=AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
        //这个转换是在FocusRequester的focusLossForGainRequest方法中进行的
        listener.onAudioFocusChange(msg.arg1);
    }
    //根据clientId在mAudioFocusIdListenerMap中返回对应的OnAudioFocusChangeListener
    //mAudioFocusIdListenerMap是在我们最初调用requestAudioFocus时存储的,不记得的童鞋可以回头看一下
    private OnAudioFocusChangeListener findFocusListener(String id) {
        return mAudioFocusIdListenerMap.get(id);
    }
    

    至此我们终于将焦点改变的消息通知到了应用层注册的onAudioFocusChange方法中。
    requestAudioFocus方法分析完了,abandonAudioFocus方法完全相同的流程,这里就不做过多介绍了,有兴趣的童鞋可以自己看这源码走一遍流程就可以理解了。


    好了,综合前一篇的焦点机制的应用,加上这篇的源码分析,音频焦点也就告一段落了,如果发现有分析错误的地方请及时指出。后续有时间还会再写一些关于音频的文章。

    前两天拍的

    相关文章

      网友评论

      本文标题:Android音频焦点详解(下)——源码详解

      本文链接:https://www.haomeiwen.com/subject/ulwjwttx.html