SystemUI之VolumeUI分析

3,253 阅读3分钟

本文来分析 SystemUI 的 VolumeUI 模块,这个模块比较简单,它使用MVP架构完成设计的,如下图

MVP

本文首先会讲解这个架构如何形成的,然后会分析按下 Power 键后处理流程。

MVP的创建

通过 SystemUI之StatusBar创建 可知,VolumeUI 的入口为 VolumeUI#start()

    // frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
    
    public void start() {
        // ...

        // 创建 VolumeDialogComponent 对象
        mVolumeComponent = SystemUIFactory.getInstance()
                .createVolumeDialogComponent(this, mContext);
        mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning);
        
        // ...
        
        // 启动VolumeUI的功能
        mVolumeComponent.register();
    }

VolumeUI 启动的时候会创建一个 VolumeDialogComponent 对象,从名字可以看出,它代表 VolumeUI 组件,通过它可以创建整个MVP。

VolumeDialogComponent 对象创建完成后,就会调用它的register()方法启动 VolumeUI 功能。它其实就是关联 Presenter 层和 Model 层。

首先来看看 VolumeDialogComponent 的构造函数

    // frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
    
    public VolumeDialogComponent(SystemUI sysui, Context context) {
        // ...

        // build()之后,会调用createDefault(),然后调用Callback
        Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
                .withPlugin(VolumeDialog.class)
                .withDefault(this::createDefault)
                .withCallback(dialog -> {
                    if (mDialog != null) {
                        mDialog.destroy();
                    }
                    mDialog = dialog;
                    mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback);
                }).build();

        // ...省略了Volume Policy功能的代码
    }
    
    protected VolumeDialog createDefault() {
        VolumeDialogImpl impl = new VolumeDialogImpl(mContext);
        impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
        impl.setAutomute(true);
        impl.setSilentMode(false);
        return impl;
    }    

VolumeDialogComponent 通过 createDefault() 创建 VolumeDialogImpl 对象,它代表 View 层,然后通过init() 进行了初始化。

    // frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
    
    public VolumeDialogImpl(Context context) {
        // VolumeDialogControllerImpl
        mController = Dependency.get(VolumeDialogController.class);
    }
    
    public void init(int windowType, Callback callback) {
        // 创建Dialog并设置参数
        initDialog();
        // 设置回调
        mController.addCallback(mControllerCallbackH, mHandler);
    }  

在 VolumeDialogImpl (View层)的构造函数中,创建了 VolumeDialogControllerImpl 对象,它代表了 Presenter 层。

在 init() 中,会向 VolumeDialogControllerImpl (Presenter层) 注册一个回调,也就是 View 层与 Presenter 层建立关联,从而可以通过 Presenter 层控制 View 层。

现在 View 层已经和 Presenter 层关联了,那么 Model 层呢?还记得前面提到的启动 VolumeUI 功能的代码吗?它调用的是 VolumeDialogComponent#register(),它完成的就是 Model 层与 Presenter 的关联,具体调用的是 VolumeDialogControllerImpl#register()

    // frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java

    protected final VC mVolumeController = new VC();
    
    public void register() {
        try {
            // 向Audio Manager注册了一个Binder,其实就是一个回调
            mAudio.setVolumeController(mVolumeController);
        } catch (SecurityException e) {
            Log.w(TAG, "Unable to set the volume controller", e);
            return;
        }
    }

Audio Manager 就是 Model 层,VolumeDialogControllerImpl 向 Audio Manager 注册了一个回调,其实就是 Presenter 层与 Model 层的关联。

音量UI显示

现在MVP框架已经形成,现在就来分析下当按下 Power 键后,VolumeUI 是如何显示UI的。

由于 VolumeDialogControllerImpl 向Audio Manager注册了回调,当按下音量键调整了音量后,VolumeDialogControllerImpl 就会收到回调

    // frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
    
    private final class VC extends IVolumeController.Stub {
        @Override
        public void volumeChanged(int streamType, int flags) throws RemoteException {
            // 调用 onVolumeChangedW()
            mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget();
        }
    }

    boolean onVolumeChangedW(int stream, int flags) {
        final boolean showUI = shouldShowUI(flags);
        final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0;
        final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0;
        final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0;
        boolean changed = false;
        if (showUI) {
            changed |= updateActiveStreamW(stream);
        }
        int lastAudibleStreamVolume = getAudioManagerStreamVolume(stream);
        changed |= updateStreamLevelW(stream, lastAudibleStreamVolume);
        changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream);
        if (changed) {
            mCallbacks.onStateChanged(mState);
        }
        if (showUI) {
            mCallbacks.onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED);
        }
        if (showVibrateHint) {
            mCallbacks.onShowVibrateHint();
        }
        if (showSilentHint) {
            mCallbacks.onShowSilentHint();
        }
        return changed;
    }

根据 flags 决定要执行哪个回调,如果要显示UI,就会回调 onShowRequested() , 而这个回调当然是由 View 层实现的。

    // frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
    
    private final VolumeDialogController.Callbacks mControllerCallbackH
            = new VolumeDialogController.Callbacks() {
        @Override
        public void onShowRequested(int reason) {
            showH(reason);
        }
    }
    
    private void showH(int reason) {
        // 显示Dialog
        mDialog.show();
    }    

View 层就完成了一个 Dialog 的显示。

一点想法 

VolumeUI 当然不只这么只功能,但是只要你懂得了这个 MVP 的设计,分析其它功能就不是什么难事。

另外呢,我在分析代码的时候发现 VolumeDialogComponent 其实可以省略的,它除了创建 View 层外,其实还控制着 Volume Policy 的功能,但是这个功能是由 SettingsProvider 控制的,而最终实现控制 Volume Policy 功能的是 Presenter 层。根据设计模式中的单一职责的原则,Volume Policy 的代码可以完全由 Presenter 实现,从而就可以省略 VolumeDialogComponent 这一环,当然这只是我个人想法。