Android音頻焦點詳解(下)——源碼詳解

轉載請註明出處:http://blog.csdn.net/wangjihuanghun/article/details/56069283


耽擱了幾天,最近一直在忙找工作的事情,今天把這篇文章補上。


本文基於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方法完全相同的流程,這裏就不做過多介紹了,有興趣的童鞋可以自己看這源碼走一遍流程就可以理解了。


好了,綜合前一篇的焦點機制的應用,加上這篇的源碼分析,音頻焦點也就告一段落了,如果發現有分析錯誤的地方請及時指出。後續有時間還會再寫一些關於音頻的文章。

前兩天拍的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章