1. 程式人生 > >android5.0設定模組音量調節流程

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模組互動的一個主要流程。