本文已獨家授權 郭霖 ( guolin_blog?) 公眾號發布!
本篇文章主要介紹的是Android應用(WebView加載H5的音頻管理只是其中的一種)如何有效管理或定制音頻的基本內容和一些思路。
事情的起因是這樣的,最近接到個需求WebView加載H5游戲,嗯,霹靂巴拉一頓猛敲基本上就搞定了,針對8.1系統也做了一些適配。好了,驗貨的時間到了,產品用他那傲嬌的手指一頓操作后不耐煩的說,這個應用為什么按下HOME鍵后(Back鍵禁用了 - - !)游戲還有聲音???當時我在想,目標Activity生命周期 onResume()和onPause()不是寫了對應的 myWebView.onResume();以及 myWebView.onPause()的啊啊!難不成那廝又在戲劇性耍我?
帶著黑人小哥頭上3個問號表情的我說到,按下HOME鍵沒有聲音的嘛,我先試試。產品瞬間反轉諂媚一笑說到,下午可以搞定嘛?我說,這個聲音可能是H5游戲引擎技術的問題,我先看下什么問題。然后,就是老套路編譯代碼打開應用插上耳機,進入游戲BGM時間,按下HOME鍵,MMP,聲音真沒消失!!!
好了,遇到問題后我跟大家的常規套路一樣,打開瀏覽器,輸入問題,接著search 結果真是驚呆了我&我的小伙伴,這都是些啥。
怎么還出現了 中間home鍵按下有咯噔的聲音怎么回事?可能是問題沒有描述清楚,更加詳細一點的。嗯,看上去貌似挺靠譜
逐個點進去看看,最后統計匯總下這些博客里面提供的辦法,有使用反射(這個感覺怪怪的就沒有試驗)、有說什么要自定義webview,原生webview控制不住的......(我這本來就是用的第三方輪子)、有說針對生命周期下手的(這種特殊情況就是上面寫的webview.onResume()等等,之前也說了生命周期沒用)、有要我這邊研究H5音頻控件的......(喂喂喂、研究會花時間的好嘛?)、有說使用android與H5交互(也就是Android調H5界面關閉聲音的方法,這個是我之前想到的一種辦法)這個方法貌似可行,但是在后面的一些博客說到這種方案有些問題。。。更有甚者說,在Activity生命周期里面使用 myWebView.reload();這個函數,我也是醉了,拜托這是重新加載好嘛。
看來網上的一些辦法不行(或者搜索姿勢不對),我就帶著問題(描述的很清楚寫了生命周期沒有作用)去各大Android技術群里面詢問有經驗的開發人員,結果這些朋友還是說要在生命周期里面做手腳( - - !),無奈,只得另尋他法。
接著找到了研究游戲的H5技術,我問他們這是什么情況,他們說不清楚。。。然后我說可不可以讓我調取你們的JS腳本,他們說這樣會有一些問題。。。你還是自己想想辦法吧。
萬般無奈,最后突然想到了,不管你是什么BGM,你都是聲音,既然是聲音那么Android肯定提供了一些API供我們調用,果不其然,在Android多媒體找到了AudioManager這個類。
AudioManager(audio翻譯過來就是聲音、音頻):
AudioManager,音頻管理類,它主要提供了豐富的API讓開發者對應用的音量和鈴聲模式進行控制以及訪問。主要內容涉及到音頻流、聲音、藍牙、擴音器、耳機等等。
A:獲取實例
由于音頻管理涉及到多媒體,因此這個AudioManager獲取實例的姿勢是這樣的:
AudioManager audio = (AudioManager)Context.getSystemService(Context.AUDIO_SERVICE);
B:豐富的API
音頻管理類提供了大量的API,這些API是我們經常看到或者用到的,比如,調節音量,我相信對于很多人來說,調節音量這個姿勢是很常見的,比如你打開某視頻APP、某音樂APP其中肯定有調節音量大小的手勢,那么調節音量內部的邏輯可以使用 adjustStreamVolume(int streamType, int direction, int flags)
參數預覽:
streamType :要調整的音頻流類型。類型有以下幾種:
STREAM_VOICE_CALL(電話的音頻流),
STREAM_SYSTEM(系統聲音的音頻流),
STREAM_RING(電話鈴聲的音頻流),
STREAM_MUSIC(用于音樂播放的音頻流)
STREAM_ALARM(警報的音頻流)
區分流類型的目的是讓用戶能夠單獨地控制不同的種類的音頻。但上述音頻種類中,大多數都是被系統限制。除非應用需要做替換鬧鐘的鈴聲的操作,不然的話你只能通過STREAM_MUSIC來播放你的音頻。也就是說我們最常見操作的就是STREAM_MUSIC這個類型。
direction :調整音量的方向。其中: ADJUST_LOWER(減少鈴聲音量),ADJUST_RAISE(增加鈴聲音量)或 ADJUST_SAME(保持之前的鈴聲音量)
flags :一個或多個標志。可能這里的標志不是很好理解,是這樣,AudioManager提供了一些常量,我們可以將這些系統已經準備好的常量設置為這里的flags,比如:
FLAG_ALLOW_RINGER_MODES(更改音量時是否包括振鈴模式作為可能的選項),
FLAG_PLAY_SOUND(是否在改變音量時播放聲音),
FLAG_REMOVE_SOUND_AND_VIBRATE(刪除可能在隊列中或正在播放的任何聲音/振動(與更改音量有關)),
FLAG_SHOW_UI(顯示包含當前音量的吐司),
FLAG_VIBRATE(是否進入振動振鈴模式時是否振動)
比如我現在想要增加音量,就可以這樣寫:
AudioManager .?adjustStreamVolume (AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI);
這句代碼的意思是指:指定調節類型為 音樂的音頻,增大音量,顯示音量圖形示意。舉一反三下面就是降低音量的代碼:
AudioManager .?adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, AudioManager.FLAG_PLAY_SOUND);
比如,我想要獲取手機的音量,可以調取getStreamVolume(int streamType); 這里的streamType指獲得手機的當前流類型的音量,最大值為7(筆者親測 Android5.1系統 中興)最小值為0。
setStreamVolume(int streamType, int index, int flags)這個API顧名思義就是根據音頻流類型去設置音量大小的。主要這里的index不能超過最大索引,也就是7。
比如藍牙相關的API:
isBluetoothA2dpOn() :這個方法是檢查是否打開或關閉了到藍牙耳機的A2DP音頻路由。
isBluetoothScoOn(): 這個方法主要是檢查通信是否使用藍牙SCO。
startBluetoothSco(): 啟動藍牙SCO音頻連接
setBluetoothScoOn(boolean on): 請求使用藍牙SCO耳機進行通信。(設置true 代表用于通信的藍牙SCO; 設置false 即不使用藍牙SCO進行通信)
void stopBluetoothSco(): 停止藍牙SCO音頻連接。
麥克風相關:
setMicrophoneMute(boolean on):設置麥克風靜音開啟或關閉。( 設置true 關閉麥克風也就是麥克風靜音; 設置false,即關閉靜音打開麥克風)
setSpeakerphoneOn(boolean on):這個方法主要是判斷是否打開擴音器(設置true,即打開免提電話; false將其關閉)
isMicrophoneMute():判斷麥克風是否靜音或是否打開(如果麥克風靜音則為true,否則為false)
isMusicActive():判斷是否有音樂處于活躍狀態(如果任何音樂曲目有效,則為true)
等等,具體的更豐富更全面的API可以參考?AudioManager官方文檔
綜上,筆者的這個問題就可以通過AudioManager去進行操作:
首先,寫一個監聽HOME鍵的代碼(我這里使用的是廣播),在廣播里面進行操作,如果按下HOME鍵以后,廣播通知AudioManage關閉聲音,然后打開頁面回到目標Activity,在對應的生命周期里面進行音量的設置
代碼如下:
首先是監聽HOME鍵的廣播:
public class InnerRecevier extends BroadcastReceiver {
private AudioManager mAudioManager;
? ? final String SYSTEM_DIALOG_REASON_KEY ="reason";
? ? final String SYSTEM_DIALOG_REASON_RECENT_APPS ="recentapps";
? ? final String SYSTEM_DIALOG_REASON_HOME_KEY ="homekey";
? ? @Override
? ? public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
? ? ? ? mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
? ? ? ? if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
? ? ? ? ? ? if (reason !=null) {
if (reason.equals(SYSTEM_DIALOG_REASON_HOME_KEY)) {
//按下HOEM鍵后,設置音頻流類型為STREAM_MUSIC,Volume為0(也就是沒有聲音)
mAudioManager.setStreamVolume(STREAM_MUSIC,0,0);
//? ? ? ? ? ? ? ? ? ? Toast.makeText(context, "點擊了Home鍵", Toast.LENGTH_SHORT).show();
? ? ? ? ? ? ? ? }else if (reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
//? ? ? ? ? ? ? ? ? ? Toast.makeText(context, "多任務鍵被監聽", Toast.LENGTH_SHORT).show();
? ? ? ? ? ? ? ? }
}
}
}
}
接著,在 目標Activity 注冊廣播,(別忘了清單文件注冊):
void initHomeBroadCast() {
? ?//創建廣播
? ? InnerRecevier innerReceiver =new InnerRecevier();
? ? //動態注冊廣播
? ? IntentFilter intentFilter =new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
? ? //啟動廣播
? ? registerReceiver(innerReceiver, intentFilter);
}
最后,對生命周期onRestart()方法里面,通過AudioManage設置音量即可
@Override
protected void onRestart() {
????super.onRestart();
? ? //設置音量 ?音頻流類型為STREAM_MUSIC,Volume為6 第二個索引不能超過最大索引
? ? mAudioManager.setStreamVolume(STREAM_MUSIC, 6, 0);
}
測試:成功解決了WebView加載H5按下HOME鍵的聲音問題。
哦~這個產品貌似忘了電源鍵也是可以關閉界面的吧 ~ ~ ~
進一步思考:
我們知道,一款手機可能會有多個應用去播放音頻,(手機安裝多款音視頻播放器這個是很常見的現象、同時打開多個音視頻播放器也是很正常的)
試想如果不有效的處理應用的音頻,會出現什么情況?我們在聽歌的同時可能還會聽到啪啪啪的聲音。(注:這里的啪啪啪指觀看羽毛球視頻)為了防止多個音樂播放應用同時播放音頻,
谷歌技術團隊使用音頻焦點(Audio Focus)來控制音頻的播放。也就是,當且僅當apk獲取到音頻焦點成功以后,才可以播放音頻!
首先,如何獲取音頻焦點?獲取音頻焦點通過( AudioManager類 )這個方法:
? public int? requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint)
來獲取音頻焦點,這個方法有三個參數,下面仔細分析下這三個參數
參數一:OnAudioFocusChangeListener(音頻焦點發生改變時候的監聽),這個OnAudioFocusChangeListener是AudioManager的一個內部接口,本質是監聽 音頻焦點的狀態。
比如,是否獲取了焦點、焦點是否失去、焦點暫時失去等狀態 ,通過源碼可以得知,它有四種狀態,分別是:
狀態一:AudioManager.AUDIOFOCUS_GAIN
狀態二:AudioManager.AUDIOFOCUS_LOSS
狀態三:AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
狀態四:AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
以上四種狀態分別代表的意思如下:
狀態一:即應用獲取了焦點,獲取了焦點即正常播放音頻
狀態二:即應用徹底失去了焦點,不能播放音頻
狀態三:即應用暫時失去焦點(比如我們按下HOME鍵、電源鍵等)
狀態四:應用暫時失去焦點,但是這個DUCK(DUCK翻譯:鴨子、閃避、躲避)狀態比較特殊,這種狀態意味著其它應用可以繼續播放,僅僅是在這一刻降低自己應用的音量,直到重新獲取到音頻焦點后恢復正常音量。應用場景有,比如在播放音樂的時候突然出現一個短暫的短信提示聲音,僅僅是把歌曲的音量暫時調低,使得用戶能夠聽到短信提示聲,在此之后便立馬恢復正常播放;再比如語言導航等等
注意:一旦結束了播放,需要確保調用abandonAudioFocus()方法。也就是告知系統我們不再需要獲取焦點且注銷AudioManager.OnAudioFocusChangeListener監聽器。
因此我們可以有如下代碼去監聽狀態:
? ? //焦點發生改變的監聽
? ? private AudioManager.OnAudioFocusChangeListener afChangeListener = new AudioManager.OnAudioFocusChangeListener(){
? ? ? ? @Override
? ? ? ? public void onAudioFocusChange(int focusChange) {
? ? ? ? ? ? Log.i(TAG, "onAudioFocusChange: == "+focusChange);
? ? ? ? ? ? if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT){
? ? ? ? ? ? //暫停播放? 停止播放
? ? ? ? ? ? ? ? Log.i(TAG, "onAudioFocusChange: == AUDIOFOCUS_LOSS_TRANSIENT 暫停播放 停止播放");
? ? ? ? ? ? }else if (focusChange == AUDIOFOCUS_GAIN){
? ? ? ? ? ? //獲取焦點? 繼續播放
? ? ? ? ? ? ? ? Log.i(TAG, "onAudioFocusChange: == AUDIOFOCUS_GAIN 獲取焦點? 繼續播放");
? ? ? ? ? ? }else if (focusChange == AUDIOFOCUS_LOSS){
? ? ? ? ? ? //徹底丟失焦點
? ? ? ? ? ? ? ? mAudioManager.abandonAudioFocus(afChangeListener);
? ? ? ? ? ? ? ? Log.i(TAG, "onAudioFocusChange: == AUDIOFOCUS_LOSS");
? ? ? ? ? ? }
? ? ? ? }
? ? };
參數二:streamType(流類型)這個上面也說了,一般設置為STREAM_MUSIC
參數三:durationHint (持續時間) 根據源碼得知有以下幾種固定寫法:
A:AUDIOFOCUS_GAIN_TRANSIENT? 焦點請求是臨時
B: AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK? 如果它的音頻輸出被暫停,那么之前成功獲取焦點的就可以繼續播放
C: AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 臨時的(語音備忘記錄或語音識別)
D:AUDIOFOCUS_GAIN 也就是焦點請求(這個用的較多)
另外源碼可以得知,這個獲取音頻焦點的返回值只有兩種:
一種是:AUDIOFOCUS_REQUEST_FAILED = 0
一種是:AUDIOFOCUS_REQUEST_GRANTED = 1,其中,AUDIOFOCUS_REQUEST_GRANTED代表的就是獲取音頻焦點成功
所以,獲取音頻焦點的寫法如下:
? ? ? ? int result = mAudioManager.requestAudioFocus(afChangeListener,
? ? ? ? ? ? ? ? // Use the music stream.
? ? ? ? ? ? ? ? AudioManager.STREAM_MUSIC,
? ? ? ? ? ? ? ? // Request permanent focus.
? ? ? ? ? ? ? ? AudioManager.AUDIOFOCUS_GAIN);
//? ? ? 如果請求結果AudioManager.AUDIOFOCUS_REQUEST_GRANTED 則表明 請求成功
? ? ? ? if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
? ? ? ? }
思考:AudioManager的強大之處在于可以捕獲音頻流類型、藍牙相關、麥克風相關等等等,怎么使用主觀能動性全在開發者手中;另外通過音頻焦點、生命周期以及主動設置音量,可以幫助我們有效主動管理音頻。
如果這篇文章對您有開發or學習上的些許幫助,希望各位看官留下寶貴的star,謝謝。
Ps:著作權歸作者所有,轉載請注明作者, 商業轉載請聯系作者獲得授權,非商業轉載請注明出處(開頭或結尾請添加轉載出處,添加原文url地址),文章請勿濫用、開源項目僅供學習交流、也希望大家尊重筆者的勞動成果,謝謝。