美文网首页
android5.1音量调节播放调试音

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

作者: 97690CE50CC872D | 来源:发表于2017-08-19 11:37 被阅读77次

    设置中音量相关的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设置模块音量调节流程启发

    相关文章

      网友评论

          本文标题:android5.1音量调节播放调试音

          本文链接:https://www.haomeiwen.com/subject/ebktdxtx.html