Android 10 VolumeUI 更新(二)

简介: 学习笔记

VolumeUI MVP的架构图.png

1 VolumeUI 的启动

 由于VolumeUI 是继承 SystemUI 的,所以它的启动方式和 SystemUI 的启动方式一样,见SystemUI 启动流程


 直接看 VolumeUI 的start()方法

// frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
    @Override
    public void start() {
        boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
        boolean enableSafetyWarning =
            mContext.getResources().getBoolean(R.bool.enable_safety_warning);
        mEnabled = enableVolumeUi || enableSafetyWarning;
        if (!mEnabled) return;
        mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning);
        setDefaultVolumeController();
    }
    private void setDefaultVolumeController() {
        DndTile.setVisible(mContext, true);
        if (D.BUG) Log.d(TAG, "Registering default volume controller");
        mVolumeComponent.register();
     }

 VolumeUI 启动的时候做了一些初始化的操作、并且会创建一个 VolumeDialogComponent 对象,从名字可以看出,它代表 VolumeUI 组件,通过它可以创建整个MVP。

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

两件事情:

 1.VolumeDialogComponent里面会去创建我们的音量条UI的实例对象,也就是VolumeDialogImpl。

 2.setDefaultVolumeController方法会设置AudioService的回调接口。

2 创建VolumeDialogImpl

 首先来看看 VolumeDialogComponent 的构造函数:

// frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
    @Inject
    public VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator,
            VolumeDialogControllerImpl volumeDialogController) {
        mContext = context;
        mKeyguardViewMediator = keyguardViewMediator;
        mController = volumeDialogController;
        mController.setUserActivityListener(this);
        // Allow plugins to reference the VolumeDialogController.
        Dependency.get(PluginDependencyProvider.class)
                .allowPluginDependency(VolumeDialogController.class);
        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();
        applyConfiguration();
        Dependency.get(TunerService.class).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
                VOLUME_SILENT_DO_NOT_DISTURB);
    }
    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) {
        initDialog();
        mAccessibility.init();
        mController.addCallback(mControllerCallbackH, mHandler);
        mController.getState();
        Dependency.get(ConfigurationController.class).addCallback(this);
    }
    private final VolumeDialogController.Callbacks mControllerCallbackH
            = new VolumeDialogController.Callbacks() {
        @Override
        public void onStateChanged(State state) {
            onStateChangedH(state);
        }
        ...
    };
  private final class H extends Handler {
        ...
        private static final int STATE_CHANGED = 7;
        public H() {
            super(Looper.getMainLooper());
        }
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                ...
                case STATE_CHANGED: onStateChangedH(mState); break;
            }
        }
    }
    ...

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

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

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

这一段代码做了如下几件事情:

 1.初始化dialog,设置dialog的布局等等。

 2.添加VolumeDialogController的回调,当VolumeDialogController接收到AudioService的回调之后,通过Callback将事件继续通知给Dialog去做出响应的处理。这里的两个参数,一个是回调各个状态的接口,一个是在主线程初始化的Handler。



 通过init()方法里的 mController.addCallback(mControllerCallbackH, mHandler);进入到VolumeDialogControllerImpl,代码如下:

// frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@Singleton
public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable {
    protected C mCallbacks = new C();
    ...
    // 添加回调监听
    public void addCallback(Callbacks callback, Handler handler) {
        mCallbacks.add(callback, handler);
        callback.onAccessibilityModeChanged(mShowA11yStream);
    }
    class C implements Callbacks {
        // Callbacks作为key,Handler为value
        private final HashMap<Callbacks, Handler> mCallbackMap = new HashMap<>();
        public void add(Callbacks callback, Handler handler) {
            if (callback == null || handler == null) throw new IllegalArgumentException();
            mCallbackMap.put(callback, handler);
        }
        @Override
        public void onStateChanged(final State state) {
            final long time = System.currentTimeMillis();
            final State copy = state.copy();
            for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
                entry.getValue().post(new Runnable() {
                    @Override
                    public void run() {
                        entry.getKey().onStateChanged(copy);
                    }
                });
            }
            Events.writeState(time, copy);
        }
    }
    ...
}

 这里C是Callbacks的实现类,并且在内部有一个Map,用来存放对应的Callbacks以及Handler

 在VolumeDialogControllerImpl收到来自AudioService的方法之后,就会调用mCallbacks的方法,由于调用的地方是在工作线程,所以在这里通过Handler转化为了UI线程去调用,在对应的实现地方就可以直接改变UI了。

Callbacks代码如下:

// frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@ProvidesInterface(version = VolumeDialogController.VERSION)
@DependsOn(target = StreamState.class)
@DependsOn(target = State.class)
@DependsOn(target = Callbacks.class)
public interface VolumeDialogController {
    @ProvidesInterface(version = Callbacks.VERSION)
    public interface Callbacks {
        int VERSION = 1;
        void onShowRequested(int reason);
        void onDismissRequested(int reason);
        void onStateChanged(State state);
        void onLayoutDirectionChanged(int layoutDirection);
        void onConfigurationChanged();
        void onShowVibrateHint();
        void onShowSilentHint();
        void onScreenOff();
        void onShowSafetyWarning(int flags);
        void onAccessibilityModeChanged(Boolean showA11yStream);
        void onCaptionComponentStateChanged(Boolean isComponentEnabled, Boolean fromTooltip);
    }
}
3 注册VolumeController

 接着来看setDefaultVolumeController,这个比较重要:

// frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@Singleton
public class VolumeDialogComponent implements VolumeComponent, TunerService.Tunable,
        VolumeDialogControllerImpl.UserActivityListener{
    private final VolumeDialogControllerImpl mController;
    @Inject
    public VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator,
            VolumeDialogControllerImpl volumeDialogController) {
        mController = volumeDialogController;
        ...
    }
    ...  
    @Override
    public void register() {
        mController.register();
    }
    ...
}

 VolumeDialogComponent调用VolumeDialogControllerImpl的方法:

// frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
    protected final VC mVolumeController = new VC();
    public void register() {
        try {
            // 向Audio Manager注册了一个Binder,其实就是一个回调
            setVolumeController();
            setVolumePolicy(mVolumePolicy);
            showDndTile(mShowDndTile);
        try {
            mMediaSessions.init();
        } catch (SecurityException e) {
            Log.w(TAG, "No access to media sessions", e);
        }
        } catch (SecurityException e) {
            Log.w(TAG, "Unable to set the volume controller", e);
            return;
        }
    }
    protected void setVolumeController() {
        try {
            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 层的关联。

4 音量UI显示

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

 这里调用AudioManager的setVolumeController方法去设置了音量控制的回调接口:

// frameworks/base/services/core/java/com/android/server/audio/AudioService.java
public class AudioService extends IAudioService.Stub
        implements AccessibilityManager.TouchExplorationStateChangeListener,
            AccessibilityManager.AccessibilityServicesStateChangeListener {
    private final VolumeController mVolumeController = new VolumeController();
    @Override
    public void setVolumeController(final IVolumeController controller) {
        ...
        mVolumeController.setController(controller);
    }
    public static class VolumeController {
        private IVolumeController mController;
        public void setController(IVolumeController controller) {
            mController = controller;
            mVisible = false;
        }
        // 音量发生改变就会调用这个方法
        public void postVolumeChanged(int streamType, int flags) {
            if (mController == null)
                return;
            try {
                mController.volumeChanged(streamType, flags);
            } catch (RemoteException e) {
                Log.w(TAG, "Error calling volumeChanged", e);
            }
        }
        ...
    }
}

 在AudioService里面定义了一个内部类VolumeController,持有IVolumeController的引用,当音量发生改变就会调用VolumeController的方法,然后调用IVolumeController的方法,最终回调到SystemUI的VolumeDialogControllerImpl的VC类中。

// frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@Singleton
public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable {
    ...
    private final class VC extends IVolumeController.Stub {
        @Override
        public void volumeChanged(int streamType, int flags) throws RemoteException {
            // 收到AudioService调用的方法
            mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget();
        }
    }
    private final class W extends Handler {
        private static final int VOLUME_CHANGED = 1;
        W(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break;
                ...
            }
        }
    }
    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方法
            mCallbacks.onStateChanged(mState);
        }
        if (showUI) {
            // UI 更新
            mCallbacks.onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED);
        }
        ...
        return changed;
    }
    ...
}

 这里的mWork是通过子线程的Looper去初始化的,所以onVolumeChangedW也是在子线程执行的,那么我们mCallbacks的方法也是在子线程执行的,这里的分析也是和上面的第2小点的分析对应上了。

 根据 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 的显示。

5 VolumeUI小结

这里我们来分析一下VolumeUI整理流程:

 1、VolumeUI持有VolumeDialogComponent的引用,在调用VolumeUI的start方法时,会判断音量条和安全音量提示是否打开,然后会去注册AudioService的监听。

 2、VolumeDialogComponent的构造函数会去创建音量条实例-VolumeDialogImpl,同时VolumeDialogImpl会去执行一些初始化的操作,同时添加VolumeDialogControllerImpl的监听回调。

 3、注册AudioService的监听是在VolumeDialogControllerImpl里面注册的,当AudioService进行了调整音量的操作后,VolumeDialogControllerImpl会收到通知,同时会将收到的消息回调给VolumeDialogImpl,做出相应的UI调整,这样就完成了一轮操作。

相关文章
|
5月前
|
Android开发
Android如何显示音标
Android如何显示音标
37 1
|
5月前
|
Android开发
Android Uri转File方法(适配android 10以上版本及android 10以下版本)
Android Uri转File方法(适配android 10以上版本及android 10以下版本)
677 0
|
API Android开发
android:descendantFocusability
android:descendantFocusability
55 0
|
传感器 机器学习/深度学习 自然语言处理
Android 12 还没用上,Android 13 已经来了!
Android 12 还没用上,Android 13 已经来了!
280 0
Android 12 还没用上,Android 13 已经来了!
|
安全 数据可视化 编译器
Now in Android #13 - 最新 Android 动态分享
Now in Android #13 - 最新 Android 动态分享
Now in Android #13 - 最新 Android 动态分享
|
Android开发
android 版本号大小比较
android 版本号大小比较
462 0
|
算法 Android开发
Android的路接下来该怎么走?
其实想写这篇文章好久了,很多小伙伴们也经常在群里探讨android移动开发者的走向,一部分人都想多快好省,间歇性踌躇满志、持续性混吃等死 ,只想用CV的开发模式们快速完成工作,然后回家王者农药。其实这种现象很普遍,我想告诉你的是 ,只要你走对方向,不断地学习,android的春天依然是健在的。
8855 0
|
XML Android开发 数据格式
Android学习--深入探索RemoteViews
版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/u013132758。 https://blog.csdn.net/u013132758/article/details/81435591 什么是RemoteViews RemoteViews表示的是一个View结构,它可以在其他进程中显示,由于它在其他进程中显示,为了能够及时更新它的界面,RemoteViews提供了一组基础的操作来跨进程更新它的界面。
1581 0
|
XML Android开发 数据格式
|
XML Android开发 数据格式
android-iconify 使用详解
android-iconify 使用详解 有图有真相 1、android-iconify简介 iconify的github地址:https://github.
1091 0