android5.1音量調(diào)節(jié)播放調(diào)試音

設(shè)置中音量相關(guān)的Fragment調(diào)整到了NotificationSettings.java中。
我們可以在R.xml.notification_settings中看到如下
xref: /packages/apps/Settings/res/xml/notification_settings.xml

<!-- Media volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
            android:key="media_volume"
            android:icon="@*android:drawable/ic_audio_vol"
            android:title="@string/media_volume_option_title" />

    <!-- Alarm volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
            android:key="alarm_volume"
            android:icon="@*android:drawable/ic_audio_alarm"
            android:title="@string/alarm_volume_option_title" />

    <!-- Ring volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
            android:key="ring_volume"
            android:icon="@*android:drawable/ic_audio_ring_notif"
            android:title="@string/ring_volume_option_title" />

    <!-- Notification volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
            android:key="notification_volume"
            android:icon="@*android:drawable/ic_audio_ring_notif"
            android:title="@string/notification_volume_option_title" />

這分別代表多媒體、鬧鐘蝗锥、來電鈴聲和系統(tǒng)通知音量。所以在UI層面上用戶是通過調(diào)節(jié)VolumeSeekBarPreference這個seekbar來最終對系統(tǒng)音量進行調(diào)整。
回頭看NotificationSettings.java中初始化流程如下
xref: /packages/apps/Settings/src/com/android/settings/notification/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);
    設(shè)置callback
        volumePref.setCallback(mVolumeCallback);
    //設(shè)置AudioManager的stream,可以在AudioSystem中看到具體int值代表的意義
        volumePref.setStream(stream);
        return volumePref;
}

mVolumeCallback如下舒裤,當(dāng)你拖動seekbar對音量進行調(diào)節(jié)時這個callback會回饋響應(yīng)喳资,在界面上進行相應(yīng)的調(diào)整。

private final class VolumePreferenceCallback implements VolumeSeekBarPreference.Callback {
    //SeekBarVolumizer是主要負責(zé)對系統(tǒng)音量進行調(diào)整的類腾供,會在后面描述
        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中可以看到如下代碼
xref: /packages/apps/Settings/src/com/android/settings/notification/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內(nèi)initVolumePreference函數(shù)的volumePref.setCallback(mVolumeCallback);把VolumePreferenceCallback這個callbak設(shè)置到VolumeSeekBarPreference.java內(nèi)的mCallback中
使得SeekBarVolumizer內(nèi)的onProgressChanged進行回調(diào)時NotificationSettings.java的相應(yīng)回調(diào)函數(shù)都響應(yīng)仆邓。

現(xiàn)在看下/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為調(diào)整seekbar時聽到的調(diào)試音
    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) {
    //真正調(diào)節(jié)系統(tǒng)音量的地方
        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,我們知道系統(tǒng)播放的鈴聲最后是通過mRingtone.play()來實現(xiàn)的
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);
}

//開始播放調(diào)試音
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));
}

//停止播放調(diào)試音
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);
        }
    }
}

這里忽視其他所有操作關(guān)注一下主要流程伴鳖,我們知道這個類implements了OnSeekBarChangeListener节值,這個listener包含了當(dāng)值發(fā)生變化響應(yīng)的onProgressChanged和開始完成操作的onStartTrackingTouch和onStopTrackingTouch 所以當(dāng)開始拖動seekbar時onStartTrackingTouch響應(yīng)無操作,然后onProgressChanged發(fā)生變化postSetVolume(progress);從而調(diào)用最關(guān)鍵函數(shù)mAudioManager.setStreamVolume(mStreamType, mLastProgress, AudioManager.FLAG_SHOW_UI_WARNINGS);實現(xiàn)音量調(diào)整榜聂,然后調(diào)用onStopTrackingTouch搞疗,通過handler調(diào)用onStartSample函數(shù)來播放調(diào)試音,在這過程中通過和setting模塊綁定的 callback來對setting模塊進行響應(yīng)须肆,這就時和上層setting模塊互動的一個主要流程匿乃。

感謝閑庭信步_wwdandroid5.0設(shè)置模塊音量調(diào)節(jié)流程啟發(fā)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市豌汇,隨后出現(xiàn)的幾起案子幢炸,更是在濱河造成了極大的恐慌,老刑警劉巖瘤礁,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阳懂,死亡現(xiàn)場離奇詭異梅尤,居然都是意外死亡柜思,警方通過查閱死者的電腦和手機岩调,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赡盘,“玉大人号枕,你說我怎么就攤上這事≡上恚” “怎么了葱淳?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抛姑。 經(jīng)常有香客問我赞厕,道長,這世上最難降的妖魔是什么定硝? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任皿桑,我火速辦了婚禮,結(jié)果婚禮上蔬啡,老公的妹妹穿的比我還像新娘诲侮。我一直安慰自己,他們只是感情好箱蟆,可當(dāng)我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布沟绪。 她就那樣靜靜地躺著,像睡著了一般空猜。 火紅的嫁衣襯著肌膚如雪绽慈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天辈毯,我揣著相機與錄音久信,去河邊找鬼。 笑死漓摩,一個胖子當(dāng)著我的面吹牛裙士,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播管毙,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼腿椎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了夭咬?” 一聲冷哼從身側(cè)響起啃炸,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卓舵,沒想到半個月后南用,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年裹虫,在試婚紗的時候發(fā)現(xiàn)自己被綠了肿嘲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡筑公,死狀恐怖雳窟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情匣屡,我是刑警寧澤封救,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站捣作,受9級特大地震影響誉结,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜券躁,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一搓彻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嘱朽,春花似錦旭贬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至岸军,卻和暖如春奋刽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背艰赞。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工佣谐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人方妖。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓狭魂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親党觅。 傳聞我的和親對象是個殘疾皇子雌澄,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,330評論 25 707
  • 他的手在移動他的手在移動他的唇在顫抖他的唇在顫抖有機會嗎沒機會了 她的手在移動她的手在移動她的淚在醞釀她的淚在醞釀...
    大野黃閱讀 302評論 0 2
  • 許多人都在沉重的生活壓力下?lián)u頭嘆息:做事難,做人更難杯瞻!因為生活的種種 際遇常常讓人 心灰意冷:做人想要 八面玲瓏镐牺,...
    人生理想閱讀 129評論 0 2