音頻焦點九問

一 、為什么要發明音頻焦,它是什么?
答:兩個或兩個以上的 Android App可同時向同一輸出流(比如手機的藍牙、手機的喇叭)播放音頻,系統會將所有音頻流(就是音頻數據了)混合在一起。這是一項有意思的技術,但卻會出現混音。為了避免所有音樂應用同時播放,Android 引入了“音頻焦點”的概念。 音頻焦點機制是Android系統提供的一種道德約定,它倡導的東西有三點:
????1、 只有一個App持有音頻焦點;
????2 、播放聲音前申請音頻焦點,不需要播放的時候釋放音頻焦點;
????3 、失去音頻焦點應該暫停播放或者降低音量。
音頻焦點是Android系統進程管理的一個值,這個值就記錄了當前音頻焦點屬于哪個應用,類型等。
二、音頻焦點在Android系統中是怎么表示,怎么管理的?
答:音頻焦點的管理以棧的形式維護在系統進程SystemServer->MediaFocusControl中
private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();
棧頂FocusRequester對象對應的App就是當前持有音頻焦點的App。
App成功申請到音頻焦點時,會在mFocusStack棧頂添加一個FocusRequester對象 ,然后通知棧頂對應的App音頻焦點申請成功,通知棧中其他FocusRequester對象對應的App音頻焦點丟失。
FocusRequester這個類就是音頻焦點表示類

/**
 * @hide
 * Class to handle all the information about a user of audio focus. The lifecycle of each
 * instance is managed by android.media.MediaFocusControl, from its addition to the audio focus
 * stack, or the map of focus owners for an external focus policy, to its release.
 * 隱藏類 所有音頻焦點相關信息封裝類。
 * 每個音頻焦點實例都被MediaFocusControl類鎖管理,它管理著音頻焦點的新增與釋放,音頻焦點棧,外部策略的焦點擁有者等等;
 */
public class FocusRequester {

    // on purpose not using this classe's name, as it will only be used from MediaFocusControl
    private static final String TAG = "MediaFocusControl";
    private static final boolean DEBUG = false;
 /**
     * 它包含一個iBinder,可以感知焦點申請方(App)是否存活
     * 如果App 進程被殺掉,就會通過iBinder通知到對應的FocusRequester,一般就是
     * 通知MediaFocusControl-> mFocusStack中的FocusRequester對象,從而從mFocusStack移除對應的
     * FocusRequester   釋放音頻焦點, 且以后其他進程釋放焦點,也不會分發或者通知給它對應的App
     */
    private AudioFocusDeathHandler mDeathHandler; // may be null
   /**
     * 客戶端回調,當這個FocusRequester對應的App 音頻焦點發生變化
     * 比如重新獲取、丟失時候會給客戶端回調
     */
    private IAudioFocusDispatcher mFocusDispatcher; // may be null
   /**
     * 這個iBinder就是這個FocusRequester 對應的binder服務端-App
     * 它作用有 :
     * 1 當FocusRequester從焦點管理棧退出,比如Abdon后,就進行釋放,那么以后App生死都不會通知到 MediaFocusControl
     * 2 比較兩個FocusRequester一般也是通過他們持有的iBinder對比,比如當一個應用死了,從焦點棧中移除FocusRequester
     * 就是通過對比他們持有的iBinder
     */
    private final IBinder mSourceRef; // may be null
    /**
     * App傳過來的音頻焦點回調的hashcode值,可以認為就是代表一個具體的回調- OnAudioFocusChangeListener
     * 因為一個App可能設置多個的音頻焦點回調 
     */
    private final @NonNull String mClientId;
   /**
     * App包名-ApplicationId
     */
    private final @NonNull String mPackageName;
   /**
     * 申請音頻焦點的App的uid
     * 音頻焦點的申請一般是App跨進程向系統服務SystemServer進程申請的
     * Binder類提供了可以獲取調用方uid的方法,然后寫入到這個FocusRequester中。 
     */
    private final int mCallingUid;
    /**
     * 這個就是管理這個FocusRequester所在的MediaFocusControl,所以這邊是不能為null
     */
    private final MediaFocusControl mFocusController; // never null
  /**
     * 申請焦點的App的targetSdkVersion(App最佳運行Android版本,在這個版本上做了充分測試和適配)
     */
    private final int mSdkTarget;

     /**
     * the audio focus gain request that caused the addition of this object in the focus stack.
     * 標記這個FocusRequester是申請哪種類型的音頻焦點,比如正常焦點類型-AudioManager#AUDIOFOCUS_GAIN
     * 短暫獲取的焦點申請-AudioManager#AUDIOFOCUS_GAIN_TRANSIENT 等等
     */
    private final int mFocusGainRequest;
  /**
     * the flags associated with the gain request that qualify the type of grant (e.g. accepting
     * delay vs grant must be immediate)
     * 音頻焦點是否可以延遲獲取到,通過AudioFocusRequest的 build的方法setAcceptsDelayedFocusGain(true)設置
     * 如果設置為true ,那么如果申請的時候沒有立即給到申請者,那么當其他應用釋放后,申請者依舊可以收到音頻焦點
     */
    private final int mGrantFlags;
    /**
     * the audio focus loss received my mFocusDispatcher, is AudioManager.AUDIOFOCUS_NONE if
     * it never lost focus.
     * 音頻焦點丟失事件是否已經收到,如果從來沒有收到就是AudioManager.AUDIOFOCUS_NONE,如果已經收到
     * 那就通過這個保證不重復收到,只有申請者下次重新申請音頻焦點后恢復狀態才可能會繼續收到焦點丟失事件
     */
    private int mFocusLossReceived;
      /**
     *  whether this focus owner listener was notified when it lost focus
     *  這個值根mFocusLossReceived 關聯的,記錄是否收到過音頻焦點丟失事件,如果已經收到過
     *  那音頻焦點棧,最上面應用釋放音頻焦點后,如果這個FocusRequester在最上面就,那就重新獲取了音頻焦點
     *  需要通知到對應的client,如果沒有收到過音頻焦點丟失通知,那么當最上面的應用釋放音頻焦點后,就算
     *  這個FocusRequester在最上面,也不會通知對應的client重新獲取音頻焦點
     */
    private boolean mFocusLossWasNotified;
    /**
     * the audio attributes associated with the focus request
     * 音頻屬性 -不做展開
     */
    private final @NonNull AudioAttributes mAttributes;
}

三、App失去音頻焦點,還可以播放聲音嗎?
答:可以,但不推薦。上文講到音頻焦點機制是Android系統提供的一種道德約定,所以也可以不必遵守,當失去音頻焦點的時候依舊我行我素繼續播放,但這種體驗很不好。
????比如你是一個視頻應用,一般在應用退到后臺的時候或者來電話的時候會失去音頻焦點,這之后應用繼續播放用戶明顯能感覺到是這個視頻應用有問題。
????再比如一個放音樂的應用,在后臺放音樂,你打開愛奇藝看電影,這時候這個音樂程序雖失去了音頻焦點,但依舊繼續放音樂,那我肯定也非常不爽這個音樂應用。
????綜合來說雖然音頻焦點機制是Android系統提供的一種道德約定,失去音頻焦點依舊可以播放聲音,但是這樣產生的不好體驗很容易被發現,所以大家還是遵守的好。
四、應用通過AudioManager.requestAudioFocus申請音頻焦點后,系統是同步給出申請結果的嗎?
答:是的,除非當時電話類應用占用著音頻焦點+App設置了 可以延遲獲取焦點:

mAudioFocusRequest =
                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                        .setAudioAttributes(mAudioAttributes)
                        .setAcceptsDelayedFocusGain(true)   // 可以延遲獲取焦點
                        .setOnAudioFocusChangeListener(mOnAudioFocusChangeListener)
                        .build();
      mAudioManager.requestAudioFocus(mAudioFocusRequest);

這樣等其他應用釋放焦點后,如果當前申請在焦點管理棧最上方,那就會接收到獲得到音頻焦點的回調。
五、看到類似Log: 09-24 17:05:01.116 W/MediaFocusControl( 2742): requestAudioFocus() from uid/pid 10060/15667 clientId=android~~~ 就代表申請音頻焦點成功了嗎
答:不是的,這個只代表申請流程走到了系統SystemServer進程,如果有電話類應用占用,Binder通信異常等問題都會導致焦點申請失敗,但大部分情況下可以認為是申請成功。
六、音頻焦點申請的詳細流程是怎么樣的?

申請音頻焦點時序圖.png

七、如果播放結束忘記釋放音頻焦點,會有什么影響?系統會回收嗎?
答:會有影響,比如你就需要短暫獲取下焦點做個提示音,就會導致比如你播放完提示音
本來應該繼續放音樂的App獲取不了焦點不繼續播放。系統會回收,但是需要進程死掉。
八、看到類似Log:09-24 17:05:01.116 W/MediaFocusControl :abandonAudioFocus() from uid/pid代表釋放焦點釋放了嗎?
答:同問題五,這個只代表App申請音頻焦點釋放流程走到系統SystemServer進程,如果出現binder通信異常等問題,也會導致音頻焦點釋放失敗,不過絕大部分時候我們可以認為成功釋放了。
九、 音頻焦點釋放的流程是怎么樣的?

釋放音頻焦點時序圖.png

十、音頻焦點申請類型AUDIOFOCUS_GAIN_TRANSIENT和AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK的區別?
答:AUDIOFOCUS_GAIN_TRANSIENT 對應 AUDIOFOCUS_LOSS_TRANSIENT
AUDIOFOCUS_GAIN_TRANSIENT 表示 短暫獲得,一會就釋放焦點,比如你只是想發個notification時用下一秒不到的鈴聲。
AUDIOFOCUS_LOSS_TRANSIENT 表示 短暫的失去音頻焦點,不要自己主動去放棄焦點,可以暫停音樂,但不要釋放資源,因為過系統會把焦點分發給繼續使用。

AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 對應 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
duck 、ducking英文是鴨子,鉆入水中,低頭的意思,在這里就是我們就可以理解低頭的意思。
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 表示短暫獲音頻焦點,之前的音頻焦點使用者雖然會丟失音頻焦點,但無需暫停播放,只需要降低音量就好;
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 表示臨時失去了音頻焦點,但可以以較低的音量的播放音頻; 嗨嗨 低頭播放 不與爭鋒

備注:本文基于Android 11/ SDK-30進行分析解讀

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容