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调整,这样就完成了一轮操作。