android5.0設定模組音量調節流程
最近剛好修改到相關方面的BUG,在這裡整理總結一下。
設定中音量相關的Fragment調整到了NotificationSettings.java中。
我們可以在R.xml.notification_settings中看到如下
<!-- Media volume -->
<com.android.settings.notification.VolumeSeekBarPreference
android:key="media_volume"
android:icon="@drawable/ic_audio_vol_24dp"
android:title="@string/media_volume_option_title" />
<!-- Alarm volume -->
<com.android.settings.notification.VolumeSeekBarPreference
android:key="alarm_volume"
android:icon="@drawable/ic_audio_alarm_24dp"
android:title ="@string/alarm_volume_option_title" />
<!-- Ring volume -->
<com.android.settings.notification.VolumeSeekBarPreference
android:key="ring_volume"
android:icon="@drawable/ring_notif"
android:title="@string/ring_volume_option_title" />
<!-- Notification volume -->
<com.android.settings.notification.VolumeSeekBarPreference
android:key="notification_volume"
android:icon="@drawable/ring_notif"
android:title="@string/notification_volume_option_title" />
這分別代表多媒體、鬧鐘、來電鈴聲和系統通知音量。所以在UI層面上使用者是通過調節VolumeSeekBarPreference這個seekbar來最終對系統音量進行調整。
回頭看NotificationSettings.java中初始化流程
//sound是包含上面4個SeekBar的PreferenceCategory
final PreferenceCategory sound = (PreferenceCategory) findPreference(KEY_SOUND);
initVolumePreference(KEY_MEDIA_VOLUME, AudioManager.STREAM_MUSIC);
initVolumePreference(KEY_ALARM_VOLUME, AudioManager.STREAM_ALARM);
//判斷是否支援通話,支援移除NOTIFICATION_VOLUME否則移除RING_VOLUME
if (mVoiceCapable) {
mRingOrNotificationPreference =
initVolumePreference(KEY_RING_VOLUME, AudioManager.STREAM_RING);
sound.removePreference(sound.findPreference(KEY_NOTIFICATION_VOLUME));
} else {
mRingOrNotificationPreference =
initVolumePreference(KEY_NOTIFICATION_VOLUME, AudioManager.STREAM_NOTIFICATION);
sound.removePreference(sound.findPreference(KEY_RING_VOLUME));
}
initRingtones(sound);
private final VolumePreferenceCallback mVolumeCallback = new VolumePreferenceCallback();
private VolumeSeekBarPreference initVolumePreference(String key, int stream) {
final VolumeSeekBarPreference volumePref = (VolumeSeekBarPreference) findPreference(key);
設定callback
volumePref.setCallback(mVolumeCallback);
//設定AudioManager的stream,可以在AudioSystem中看到具體int值代表的意義
volumePref.setStream(stream);
return volumePref;
}
mVolumeCallback如下,當你拖動seekbar對音量進行調節時這個callback會回饋響應,在介面上進行相應的調整。
private final class VolumePreferenceCallback implements VolumeSeekBarPreference.Callback {
//SeekBarVolumizer是主要負責對系統音量進行調整的類,會在後面描述
private SeekBarVolumizer mCurrent;
@Override
public void onSampleStarting(SeekBarVolumizer sbv) {
if (mCurrent != null && mCurrent != sbv) {
mCurrent.stopSample();
}
mCurrent = sbv;
if (mCurrent != null) {
mHandler.removeMessages(H.STOP_SAMPLE);
mHandler.sendEmptyMessageDelayed(H.STOP_SAMPLE, SAMPLE_CUTOFF);
}
}
@Override
public void onStreamValueChanged(int stream, int progress) {
if (stream == AudioManager.STREAM_RING) {
mHandler.removeMessages(H.UPDATE_RINGER_ICON);
mHandler.obtainMessage(H.UPDATE_RINGER_ICON, progress, 0).sendToTarget();
}
}
public void stopSample() {
if (mCurrent != null) {
mCurrent.stopSample();
}
}
};
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_PHONE_RINGTONE:
mPhoneRingtonePreference.setSummary((CharSequence) msg.obj);
break;
case UPDATE_NOTIFICATION_RINGTONE:
mNotificationRingtonePreference.setSummary((CharSequence) msg.obj);
break;
case STOP_SAMPLE:
mVolumeCallback.stopSample();
break;
case UPDATE_RINGER_ICON:
updateRingOrNotificationIcon(msg.arg1);
break;
}
}
下面來看VolumeSeekBarPreference.java中可以看到如下程式碼
private SeekBarVolumizer mVolumizer;
final Uri sampleUri = mStream == AudioManager.STREAM_MUSIC ? getMediaVolumeUri() : null;
if (mVolumizer == null) {
mVolumizer = new SeekBarVolumizer(getContext(), mStream, sampleUri, sbvc) {
// we need to piggyback on SBV's SeekBar listener to update our icon
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromTouch) {
super.onProgressChanged(seekBar, progress, fromTouch);
//這裡的mCallback就是VolumePreferenceCallback物件
mCallback.onStreamValueChanged(mStream, progress);
}
};
}
mVolumizer.setSeekBar(mSeekBar);
mIconView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
mCallback.onStreamValueChanged(mStream, mSeekBar.getProgress());
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromTouch) {
super.onProgressChanged(seekBar, progress, fromTouch);
mCallback.onStreamValueChanged(mStream, progress);
}
這裡我們進行下梳理在NotificationSettings.java內initVolumePreference函式的volumePref.setCallback(mVolumeCallback);把VolumePreferenceCallback這個callbak設定到VolumeSeekBarPreference.java內的mCallback中
使得SeekBarVolumizer內的onProgressChanged進行回撥時NotificationSettings.java的相應回撥函式都響應。
現在看下frameworks/base/core/java/android/preference的SeekBarVolumizer.java
private final Context mContext;
private final Handler mHandler;
private final H mUiHandler = new H();
private final Callback mCallback;
private final Uri mDefaultUri;
private final AudioManager mAudioManager;
private final int mStreamType;
private final int mMaxStreamVolume;
private final Receiver mReceiver = new Receiver();
private final Observer mVolumeObserver;
private int mOriginalStreamVolume;
private Ringtone mRingtone;
private int mLastProgress = -1;
private SeekBar mSeekBar;
private int mVolumeBeforeMute = -1;
private static final int MSG_SET_STREAM_VOLUME = 0;
private static final int MSG_START_SAMPLE = 1;
private static final int MSG_STOP_SAMPLE = 2;
private static final int MSG_INIT_SAMPLE = 3;
private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
public SeekBarVolumizer(Context context, int streamType, Uri defaultUri,
Callback callback) {
mContext = context;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mStreamType = streamType;
mMaxStreamVolume = mAudioManager.getStreamMaxVolume(mStreamType);
HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
thread.start();
mHandler = new Handler(thread.getLooper(), this);
mCallback = callback;
mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
mVolumeObserver = new Observer(mHandler);
mContext.getContentResolver().registerContentObserver(
System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
false, mVolumeObserver);
mReceiver.setListening(true);
//mDefaultUri為調整seekbar時聽到的除錯音
if (defaultUri == null) {
if (mStreamType == AudioManager.STREAM_RING) {
defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
} else if (mStreamType == AudioManager.STREAM_NOTIFICATION) {
defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI;
} else {
defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
}
}
mDefaultUri = defaultUri;
mHandler.sendEmptyMessage(MSG_INIT_SAMPLE);
}
public void setSeekBar(SeekBar seekBar) {
if (mSeekBar != null) {
mSeekBar.setOnSeekBarChangeListener(null);
}
mSeekBar = seekBar;
mSeekBar.setOnSeekBarChangeListener(null);
mSeekBar.setMax(mMaxStreamVolume);
mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume);
mSeekBar.setOnSeekBarChangeListener(this);
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
//真正調節系統音量的地方
case MSG_SET_STREAM_VOLUME:
mAudioManager.setStreamVolume(mStreamType, mLastProgress,
AudioManager.FLAG_SHOW_UI_WARNINGS);
break;
case MSG_START_SAMPLE:
onStartSample();
break;
case MSG_STOP_SAMPLE:
onStopSample();
break;
case MSG_INIT_SAMPLE:
onInitSample();
break;
default:
Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
}
return true;
}
//初始化mRingtone,我們知道系統播放的鈴聲最後是通過mRingtone.play()來實現的
private void onInitSample() {
mRingtone = RingtoneManager.getRingtone(mContext, mDefaultUri);
if (mRingtone != null) {
mRingtone.setStreamType(mStreamType);
}
}
private void postStartSample() {
mHandler.removeMessages(MSG_START_SAMPLE);
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
}
//開始播放除錯音
private void onStartSample() {
if (!isSamplePlaying()) {
if (mCallback != null) {
mCallback.onSampleStarting(this);
}
if (mRingtone != null) {
try {
mRingtone.play();
} catch (Throwable e) {
Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e);
}
}
}
}
void postStopSample() {
// remove pending delayed start messages
mHandler.removeMessages(MSG_START_SAMPLE);
mHandler.removeMessages(MSG_STOP_SAMPLE);
mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_SAMPLE));
}
//停止播放除錯音
private void onStopSample() {
if (mRingtone != null) {
mRingtone.stop();
}
}
public void stop() {
postStopSample();
mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
mSeekBar.setOnSeekBarChangeListener(null);
mReceiver.setListening(false);
mHandler.getLooper().quitSafely();
}
public void revertVolume() {
mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
}
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromTouch) {
if (!fromTouch) {
return;
}
postSetVolume(progress);
}
void postSetVolume(int progress) {
// Do the volume changing separately to give responsive UI
mLastProgress = progress;
mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
}
public void onStartTrackingTouch(SeekBar seekBar) {
}
public void onStopTrackingTouch(SeekBar seekBar) {
postStartSample();
}
//判斷是否正在播放鈴聲
public boolean isSamplePlaying() {
return mRingtone != null && mRingtone.isPlaying();
}
public void startSample() {
postStartSample();
}
public void stopSample() {
postStopSample();
}
public SeekBar getSeekBar() {
return mSeekBar;
}
public void changeVolumeBy(int amount) {
mSeekBar.incrementProgressBy(amount);
postSetVolume(mSeekBar.getProgress());
postStartSample();
mVolumeBeforeMute = -1;
}
public void muteVolume() {
if (mVolumeBeforeMute != -1) {
mSeekBar.setProgress(mVolumeBeforeMute);
postSetVolume(mVolumeBeforeMute);
postStartSample();
mVolumeBeforeMute = -1;
} else {
mVolumeBeforeMute = mSeekBar.getProgress();
mSeekBar.setProgress(0);
postStopSample();
postSetVolume(0);
}
}
public void onSaveInstanceState(VolumeStore volumeStore) {
if (mLastProgress >= 0) {
volumeStore.volume = mLastProgress;
volumeStore.originalVolume = mOriginalStreamVolume;
}
}
public void onRestoreInstanceState(VolumeStore volumeStore) {
if (volumeStore.volume != -1) {
mOriginalStreamVolume = volumeStore.originalVolume;
mLastProgress = volumeStore.volume;
postSetVolume(mLastProgress);
}
}
private final class H extends Handler {
private static final int UPDATE_SLIDER = 1;
@Override
public void handleMessage(Message msg) {
if (msg.what == UPDATE_SLIDER) {
if (mSeekBar != null) {
mSeekBar.setProgress(msg.arg1);
mLastProgress = mSeekBar.getProgress();
}
}
}
public void postUpdateSlider(int volume) {
obtainMessage(UPDATE_SLIDER, volume, 0).sendToTarget();
}
}
private final class Observer extends ContentObserver {
public Observer(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
if (mSeekBar != null && mAudioManager != null) {
final int volume = mAudioManager.getStreamVolume(mStreamType);
mUiHandler.postUpdateSlider(volume);
}
}
}
這裡忽視其他所有操作關注一下主要流程,我們知道這個類implements了OnSeekBarChangeListener,這個listener包含了當值發生變化響應的onProgressChanged和開始完成操作的onStartTrackingTouch和onStopTrackingTouch
所以當開始拖動seekbar時onStartTrackingTouch響應無操作,然後onProgressChanged發生變化postSetVolume(progress);從而呼叫最關鍵函式mAudioManager.setStreamVolume(mStreamType, mLastProgress,
AudioManager.FLAG_SHOW_UI_WARNINGS);實現音量調整,然後呼叫onStopTrackingTouch,通過handler呼叫onStartSample函式來播放除錯音,在這過程中通過和setting模組繫結的
callback來對setting模組進行響應,這就時和上層setting模組互動的一個主要流程。