在前面兩個小節小節中,已經講解了音量調節的最重要部分,該小節講解一下使用音量鍵,或者在Setting界面調節滑動條調節音量時,其處理流程是怎麼樣的。
使用音量鍵控制音量會涉及兩個系統,一個是輸入系統,一個是音頻系統,我們需要從源頭開始分析,我們先來看看輸入系統,在以前我們講解輸入系統時,我們曾經說過,對于輸入事件,我們會分為很多個stage進行處理:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPR1kMjpXT4FkeNBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZwpmL0MTNwQDNwATMyATNwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
分為輸入法之前的處理以及輸入法之後的處理,我們打開ViewRootImpl.java檔案,對于音量調節鍵,我們關心的是在輸入法之後的處理:
處理過程,他會調用該類中的onProcess函數,在這之前我們先來梳理一下,他的處理流程:輸入系統會把輸入事件發送給位于最前面的APP(activity),activity收到輸入事件之後,會把他發送給自己wind,wind發送給deocview,deocview再把他發送給輸入焦點,所謂的輸入焦點就是一個控件,這個控件位于最前面,他接受使用者的輸入:
如果其上的wind,deocview,以及輸入焦點都不能處理這個事件,我們還有一個兜底工作,對于音量鍵,就是由兜底工作完成的,當然,我們也可以在輸入焦點上重寫輸入函數,對音量鍵進行處理,這樣的話,如果重寫的APP位于最前面,我們按下音量鍵,接不能操控聲音了,具體行為按照編寫的程式為準。一般來說,我們不會重新音量鍵程式。也就是說,在兜底的工作中,實作對音量鍵對音量的控制。下面是調用的時序圖:
現在我們檢視onProces函數:
protected int onProcess(QueuedInputEvent q) {
/*處理案件事件*/
processKeyEvent(q);
/*該就是我們前面提到的處理流程*/
// Deliver the key to the view hierarchy.
mView.dispatchKeyEvent(event)
// Apply the fallback event policy.
if (mFallbackEventHandler.dispatchKeyEvent(event)) {
其上dispatchKeyEvent就是我們的音量處理相關函數,其實作在PhoneFallbackEventHandler.cpp中實作:
public boolean dispatchKeyEvent(KeyEvent event) {
/*如果按下音量鍵*/
if (action == KeyEvent.ACTION_DOWN) {
return onKeyDown(keyCode, event);
} else {//如果松開音量鍵
return onKeyUp(keyCode, event);
}
其處理流程如下:
這裡就不一步一步對源碼進行追蹤了,下面是一些文字的總結:
a. 音量鍵處理流程
音量鍵:
如果APP沒有重寫它的處理函數,
音量鍵的處理将交給PhoneFallbackEventHandler來處理,
它會調用AudioService.adjustSuggestedStreamVolume( 調整"推薦的流"的音量):
根據時序,我們打開AudioService.java中的adjustSuggestedStreamVolume函數:
/*
direction:增加還是減少音量
suggestedStreamType:推薦的StreamType(最後的選擇)
*/
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,String callingPackage, String caller, int uid) {
/*獲得目前活躍的StreamType,如果沒有活躍的,則使用推薦值*/
streamType = getActiveStreamType(suggestedStreamType);
/*手機*/
case AudioSystem.PLATFORM_VOICE:
/*如果正在通話中*/
if (isInCommunication()) {
/*并且使用藍牙裝置*/
if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION) == AudioSystem.FORCE_BT_SCO) {
/*傳回藍牙的StreamType類型*/
return AudioSystem.STREAM_BLUETOOTH_SCO;
} else {//沒有使用藍牙裝置
return AudioSystem.STREAM_VOICE_CALL;
}
/*如果推薦之後為USE_DEFAULT_STREAM_TYPE*/
} else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
/*電視,如機頂盒*/
/*如果最近聽過音樂*/
if (isAfMusicActiveRecently(StreamOverride.sDelayMs)) {
/*傳回音樂STREAM_MUSIC*/
return AudioSystem.STREAM_MUSIC;
case AudioSystem.PLATFORM_TELEVISION:
/*永遠方傳回音樂*/
return AudioSystem.STREAM_MUSIC;
default://平闆之類的
//和電話處理類似
這樣我們就解答了前面的問題,如何獲得推薦的StreamType, stream = getActiveStreamType(…)。
為了減少使用者的設定難度,我們把Stream歸為幾類:
我們設定StreamType時,他會導緻好幾種音量被設定,這就是分組,或者别名alias。我們來看看這别名分組是怎麼起作用單的?其調用流程如下:
根據前面getActiveStreamType函數,已經獲得了活躍的StreamType,在根據流程,執行到:
打開:
private class AudioHandler extends Handler {
private void setDeviceVolume(VolumeStreamState streamState, int device) {
synchronized (VolumeStreamState.class) {
// Apply volume
streamState.applyDeviceVolume_syncVSS(device);
// Apply change to all streams using this one as alias
int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
if (streamType != streamState.mStreamType &&
mStreamVolumeAlias[streamType] == streamState.mStreamType) {
// Make sure volume is also maxed out on A2DP device for aliased stream
// that may have a different device selected
int streamDevice = getDeviceForStream(streamType);
if ((device != streamDevice) && mAvrcpAbsVolSupported &&
((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
}
mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
}
}
}
// Post a persist volume msg
sendMsg(mAudioHandler,
MSG_PERSIST_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
PERSIST_DELAY);
}
注釋比較詳細,就不進行講解了,小結如下:
a.2 音量設定的"alias"如何起作用:
set volume for stream; // 設定"推薦的流"的音量
if (mStreamVolumeAlias[other stream] == stream)
set volume for other stream; // 設定同屬一個alias的其他流的音量
對于不同的裝置(電話、TV、平闆), mStreamVolumeAlias指向不同的數組
下面我們看看怎麼去設定stream的音量,根據時序圖,其最終版會調用到:
我們打開AudioFlinger找到找到:
status_t AudioFlinger::setStreamVolume(audio_stream_type_t stream, float value,audio_io_handle_t output)
/*設定mStreamTypes的值*/
mStreamTypes[stream].volume = value;
/*設定其他線程的StreamVolume*/
if (thread == NULL) {
for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
mPlaybackThreads.valueAt(i)->setStreamVolume(stream, value);
}
} else {
thread->setStreamVolume(stream, value);
}
小節如下:
a.3 怎麼設定流的音量:
設定audioflinger中的數組: mStreamTypes[stream].volume = value;
設定PlaybackThread中的數組: mStreamTypes[stream].volume = value;
下面我們分析音量滑動條的流程。
音量滑動條
音量滑動條是一個seekBar控件,打開seekBar.java,
@Override
void onProgressRefresh(float scale, boolean fromUser, int progress) {
super.onProgressRefresh(scale, fromUser, progress);
當我們滑動滑動條的時候onProgressRefresh函數會被調用,我們怎麼去設定音量呢?有兩種方法:
1.重寫onProgressRefresh函數,在裡面去設定音量。
2.添加Listener。
為了大家友善,文字描述第二種方法:
b. 音量滑動條處理流程
b.1 通過下面檔案定義音量滑動條:
packages/apps/Settings/res/xml/notification_settings.xml
該檔案定義了多個VolumeSeekBarPreference
每個VolumeSeekBarPreference要跟一個SeekBar綁定
b.2 在VolumeSeekBarPreference的綁定函數onBindView中,
設定了對應的SeekBar的SeekBarChangeListener (一個SeekBarVolumizer對象)
b.3 當SeekBar被滑動時, 它的onProgressRefresh被調用
該函數會調用 mOnSeekBarChangeListener.onProgressChanged
b.4 mOnSeekBarChangeListener.onProgressChanged去設定音量
b.5 每一個SeekBar對應一個stream,
滑動SeekBar時會設定該stream的音量,
也會去設定同屬一個alias(同一分組)的其他stream的音量
(後兩分鐘無記錄)