学习笔记:Android音量调节(一)Volume音量键的处理流程
VolumeUI 模块使用了 MVP 架构完成设计的,如下图: VolumeUI MVP的架构图.png1 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,其实就是一个回调
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);
}
...
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调整,这样就完成了一轮操作。
网友评论