android5.1音量调节播放调试音

设置中音量相关的Fragment调整到了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" />

这分别代表多媒体、闹钟、来电铃声和系统通知音量。所以在UI层面上用户是通过调节VolumeSeekBarPreference这个seekbar来最终对系统音量进行调整。
回头看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);
    设置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中可以看到如下代码
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内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模块互动的一个主要流程。

感谢闲庭信步_wwdandroid5.0设置模块音量调节流程启发

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,049评论 25 707
  • 他的手在移动他的手在移动他的唇在颤抖他的唇在颤抖有机会吗没机会了 她的手在移动她的手在移动她的泪在酝酿她的泪在酝酿...
    大野黄阅读 290评论 0 2
  • 许多人都在沉重的生活压力下摇头叹息:做事难,做人更难!因为生活的种种 际遇常常让人 心灰意冷:做人想要 八面玲珑,...
    人生理想阅读 123评论 0 2