1 VolumeUI 的启动
由于VolumeUI 是继承 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 层。
两件事情:
- VolumeDialogComponent里面会去创建我们的音量条UI的实例对象,也就是VolumeDialogImpl。
- setDefaultVolumeController方法会设置AudioService的回调接口。
2 创建VolumeDialogImpl
首先来看看 VolumeDialogComponent 的构造函数:
frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@Injectpublic 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) {// VolumeDialogControllerImplmController = 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() {@Overridepublic void onStateChanged(State state) {onStateChangedH(state);}...};private final class H extends Handler {...private static final int STATE_CHANGED = 7;public H() {super(Looper.getMainLooper());}@Overridepublic 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()。
这一段代码做了如下几件事情:
- 初始化dialog,设置dialog的布局等等。
- 添加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为valueprivate 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);}@Overridepublic 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() {@Overridepublic 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;@Injectpublic VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator,VolumeDialogControllerImpl volumeDialogController) {mController = volumeDialogController;...}... @Overridepublic 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.Stubimplements AccessibilityManager.TouchExplorationStateChangeListener,AccessibilityManager.AccessibilityServicesStateChangeListener {private final VolumeController mVolumeController = new VolumeController();@Overridepublic 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 {@Overridepublic 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);}@Overridepublic 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() {@Overridepublic void onShowRequested(int reason) {showH(reason);}}private void showH(int reason) {// 显示DialogmDialog.show();}
View 层就完成了一个 Dialog 的显示。
5 VolumeUI小结
这里我们来分析一下VolumeUI整理流程:
- VolumeUI持有VolumeDialogComponent的引用,在调用VolumeUI的start方法时,会判断音量条和安全音量提示是否打开,然后会去注册AudioService的监听。
- VolumeDialogComponent的构造函数会去创建音量条实例-VolumeDialogImpl,同时VolumeDialogImpl会去执行一些初始化的操作,同时添加VolumeDialogControllerImpl的监听回调。
- 注册AudioService的监听是在VolumeDialogControllerImpl里面注册的,当AudioService进行了调整音量的操作后,VolumeDialogControllerImpl会收到通知,同时会将收到的消息回调给VolumeDialogImpl,做出相应的UI调整,这样就完成了一轮操作。