前言
最近線上用戶反饋了一些語音喚醒功能無法使用的問題(即無法通過語音來和應用交互),因此專門梳理了Android應用對音頻焦點的處理邏輯并提出一些優化思路,下文是一次總結整理。
音頻焦點出現的緣由、作用
為什么會有音頻焦點的出現?兩個或兩個以上的 Android 應用可同時向同一輸出流播放音頻,系統會將所有音頻流混合在一起。為了避免所有音樂應用同時播放,Android 引入了“音頻焦點”的概念,一次只能有一個應用獲得音頻焦點。當應用需要輸出音頻時,它需要請求獲得音頻焦點,獲得焦點后,就可以播放聲音了。
不過,Android系統雖然可以對音頻焦點進行分配,但不能強制讓上一個占有音頻焦點的應用程序放棄使用音頻。即應用程序A正在前臺使用語音喚醒功能的時候,應用B撥打了一個語音電話過來,這時Android系統會把音頻焦點分配給應用B,但應用A的語音喚醒功能并沒有被剝奪、仍然可以使用。那么應用B的語音電話功能就會受到影響。
這時就需要約定一個規則,當應用程序失去音頻焦點的時候,就需要暫停使用音頻或降低音頻音量,把音頻通道讓給占有音頻焦點的應用程序使用。如果應用違背上述規則,不釋放音頻通道,可能會引發大量客訴、流失掉很多用戶。
音頻焦點的使用核心
Android應用通過調用 requestAudioFocus()(請求音頻焦點) 和 abandonAudioFocus()(放棄音頻焦點) 來管理音頻焦點。應用還必須為這兩個方法注冊監聽器 AudioManager.OnAudioFocusChangeListener ,以便接收回調并管理自己的音量。
音頻焦點具體的代碼使用
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
AudioManager.OnAudioFocusChangeListener afChangeListener =
new AudioManager.OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
// 永久失去音頻焦點
// 立即停止播放
}
else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
// 暫時性失去焦點
// 播放暫停
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
// 暫時性失去焦點
// 聲音變低,繼續播放
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// 獲取到音頻焦點
// 聲音恢復到正常音量,開始播放
}
}
};
int result = audioManager.requestAudioFocus(afChangeListener,
// 使用音頻流
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// 音頻焦點請求成功,執行相應邏輯
} else {
// 音頻焦點請求失敗,執行相應邏輯
}
播放完成后,放棄音頻焦點:
audioManager.abandonAudioFocus(afChangeListener);
使用中注意的點:
① 為 requestAudioFocus 方法傳入的第3個參數:
- 如果計劃在將來一段時間內播放音頻,并且希望前一個持有音頻焦點的應用停止播放,則應該請求永久性的音頻焦點 (AUDIOFOCUS_GAIN)。
- 如果只希望在短時間內播放音頻,并且希望前一個持有音頻焦點的應用暫停播放,則應該請求暫時性的焦點 (AUDIOFOCUS_GAIN_TRANSIENT)。
- 如果只希望在短時間內播放音頻,并允許前一個持有焦點的應用在降低音量的情況下繼續播放,則應該請求“降低音量”的暫時性焦點(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) 。這兩個音頻會混合到音頻流中同時輸出。
② AudioManager.OnAudioFocusChangeListener回調回來的音頻焦點類型,如下:
- AUDIOFOCUS_GAIN:獲取得到音頻焦點。
- AUDIOFOCUS_LOSS:永久性失去音頻焦點,后續不會再收到 AUDIOFOCUS_GAIN 回調。應用應立即暫停播放,此時其他應用會播放音頻。
- AUDIOFOCUS_LOSS_TRANSIENT:暫時性失去音頻焦點,應用應暫停播放。當搶占焦點的應用放棄焦點時、自己的應用可以收到 AUDIOFOCUS_GAIN 回調。
- AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:暫時性失去音頻焦點,應用應降低音量。當搶占焦點的應用放棄焦點時、自己的應用可以收到 AUDIOFOCUS_GAIN 回調。
開發中遇到的問題 & 解決思路
大多數情況下,按照上述流程使用音頻焦點是沒有問題的。但線上有部分用戶反饋無法使用語音喚醒功能(即無法通過語音來和應用交互),經過排查,發現有如下可優化的地方:
-
問題1:不是所有應用都會將音頻焦點釋放,有的應用也會不釋放,比如手機系統的錄音機。這看似違背最上面說的音頻焦點管理規則,但錄音機是有它特殊性的,不管它位于前臺還是后臺,都應該擁有錄音的功能。這就會導致只要不殺死錄音機應用進程,它就會一直占用音頻焦點,導致其他應用無法使用音頻焦點。或者是其他應用把原本屬于本應用的音頻焦點搶占了,也會導致本應用無法使用音頻焦點,比如某些應用內的語音通話功能就會搶占音頻焦點。
處理策略:當AudioManager.OnAudioFocusChangeListener監聽器監聽到音頻焦點已失去時(AUDIOFOCUS_LOSS、AUDIOFOCUS_LOSS_TRANSIENT),就主動通過彈窗或toast提示用戶【關閉其他應用或重啟手機】,以期望達到讓其他應用釋放音頻焦點的目的。
-
問題2:正常來說,當B應用把音頻焦點釋放后,A應用是可以重新獲取到音頻焦點的回調(AUDIOFOCUS_GAIN),但某些Android手機可能會有系統bug,即不會主動通知A應用音頻焦點已經回來了,A應用無法拿到AUDIOFOCUS_GAIN這個回調,A應用也就無法再恢復播放音頻。
處理策略:增加音頻功能的調用時機。比如在切頁或者做其他操作的時候,主動嘗試使用語音喚醒的音頻功能,而不是通過AudioManager.OnAudioFocusChangeListener監聽器被動地等待回調。當主動嘗試使用語音喚醒成功時,說明音頻焦點已經回來了。