Android 10 音量UI更新解析

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 层。
两件事情:

  1. VolumeDialogComponent里面会去创建我们的音量条UI的实例对象,也就是VolumeDialogImpl。
  2. 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()。
这一段代码做了如下几件事情:

  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为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整理流程:

  1. VolumeUI持有VolumeDialogComponent的引用,在调用VolumeUI的start方法时,会判断音量条和安全音量提示是否打开,然后会去注册AudioService的监听。
  2. VolumeDialogComponent的构造函数会去创建音量条实例-VolumeDialogImpl,同时VolumeDialogImpl会去执行一些初始化的操作,同时添加VolumeDialogControllerImpl的监听回调。
  3. 注册AudioService的监听是在VolumeDialogControllerImpl里面注册的,当AudioService进行了调整音量的操作后,VolumeDialogControllerImpl会收到通知,同时会将收到的消息回调给VolumeDialogImpl,做出相应的UI调整,这样就完成了一轮操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/702855.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux基础命令—进程管理

基础知识 linux进程管理 什么是进程 开发写代码->代码运行起来->进程 运行起来的程序叫做进程程序与进程区别 1.程序是一个静态的概念,主要是指令集和数据的结合,可以长期存放在操作系统中 2.进程是一个动态的概念,主要是程序的运行状态,进程存在生命周期,生命周期结…

YY调音台:直播主播的体验

我是直播平台的主播&#xff0c;日常工作就是在直播间里打游戏、唱歌、聊天之类的。刚开始的时候我的直播工具只有一台电脑&#xff0c;收音也是用的我自己常用的耳机&#xff0c;设备比较简陋&#xff0c;直播间的用户留存率也不高。但是我相信天道酬勤&#xff0c;每天晚上坚…

javaScript数组去重的几种实现方式——适用非引用数据去重

最传统的使用循环遍历 //最传统的使用循环遍历 function getUnique(arr) {let newArr [];for (let i 0; i < arr.length; i) {for (let j i 1; j < arr.length; j) {if (arr[i] arr[j]) {i; //相同丢掉前面的元素}}newArr.push(arr[i]);}return newArr; } 利用Set实…

Seata分布式事务实战XATCC模式

目录 XA模式 XA 模式的使用 Spring Cloud Alibaba整合Seata XA TCC模式 TCC模式接口改造 TCC如何控制异常 Spring Cloud Alibaba整合Seata TCC XA模式 整体机制 在 Seata 定义的分布式事务框架内&#xff0c;利用事务资源&#xff08;数据库、消息服务等&#xff09;对…

【Python从入门到进阶】49、当当网Scrapy项目实战(二)

接上篇《48、当当网Scrapy项目实战&#xff08;一&#xff09;》 上一篇我们正式开启了一个Scrapy爬虫项目的实战&#xff0c;对当当网进行剖析和抓取。本篇我们继续编写该当当网的项目&#xff0c;讲解刚刚编写的Spider与item之间的关系&#xff0c;以及如何使用item&#xff…

【python】0、超详细介绍:json、http

文章目录 一、json二、http2.1 json 读取 request 序列化 三、基本类型3.1 decimal 四、图像4.1 颜色格式转换 一、json import json f open(data.json) # open json file data json.load(f) # 读出 json object for i in data[emp_details]: # 取出一级属性 emp_details, …

云尚办公-0.3.0

5. controller层 import pers.beiluo.yunshangoffice.model.system.SysRole; import pers.beiluo.yunshangoffice.service.SysRoleService;import java.util.List;//RestController&#xff1a;1.该类是控制器&#xff1b;2.方法返回值会被写进响应报文的报文体&#xff0c;而…

ChatRTX安装教程

介于本人一直想将现有的智慧城市的文档结合大模型RAG实现知识库问答助手&#xff0c;借着Chat With RTX的风潮正好将机器人和知识库合二为一&#xff0c;方便以后对众多文件进行查阅。 一、概要 Chat With RTX 是一个 Demo&#xff0c;用来将您自己的资料&#xff08;文档、笔…

关于硅的制造芯片的过程

芯片是如何制作的&#xff1f; 先将硅融化制成硅晶片&#xff0c;再用光刻机印压电路。 bilibili芯片制作视频 硅晶片作为现代芯片的主要元件&#xff0c;广泛用于集成电路。 首先将多晶硅放入特制的密封炉&#xff0c;排除其中空气后加热到1420摄氏度&#xff0c;将融化的硅放…

第三节:kafka sarama 遇到Bug?

文章目录 前言一、先上结果二、刨根问底总结 前言 前面两节&#xff0c;我们已经简单应用了sarama的两个类型Client和ClusterAdmin&#xff0c;其中有一个案例是获取集群的ControllerId&#xff0c;但是在后面的测试过程过程中&#xff0c;发现一个问题&#xff0c;返回的Cont…

vue菜单栏跳转方案

vue菜单栏跳转方案 <template><div><el-container style"height: 100vh"><el-aside width"200px" style"background-color: #b3c0d1"><el-menuopen"handleOpen"close"handleClose"select"h…

MongoDB聚合运算符:$bitXor

文章目录 语法用法举例 $bitXor聚合运算符返回整数或长整数数组元素按位异或的结果。 语法 { $bitXor: { [ <expression1>, <expression2>, ... ] }用法 如果操作数包括整型和长整型值&#xff0c;MongoDB会对计算出的整数结果进行符号扩展&#xff0c;并返回长…

处理器分支预测(Branch predictor)原理和实现

C++实例 我们先给一个实例,在windows系统下,使用VisualStudio的debug模式,编译和运行程序: #include <algorithm> #include <ctime> #include <iostream>int main(){// Generate dataconst unsigned arraySize = 32768;int data[arraySize];for (unsig…

循环队列和链表队列

循环队列&#xff1a; #include <iostream> using namespace std; const int MAX_SIZE 100; template <class DataType> /* 循环队列可以想象成一个环形&#xff0c;里面有一个个的格子&#xff0c;也就是环形数组 front表示首个&#xff08;但是这不会一直是0&a…

【PyQt5桌面应用开发】3.Qt Designer快速入门(控件详解)

一、Qt Designer简介 Qt Designer是PyQt程序UI界面的实现工具&#xff0c;可以帮助我们快速开发 PyQt 程序的速度。它生成的 UI 界面是一个后缀为 .ui 的文件&#xff0c;可以通过 pyiuc 转换为 .py 文件。 Qt Designer工具使用简单&#xff0c;可以通过拖拽和点击完成复杂界面…

仿12306校招项目业务二(列车检索)

目录 验证数据 加载城市数据 查询列车站点信息 查询列车余票信息 构建列车返回数据 12306 项目中列车数据检索接口路径 &#xfeff; TicketController的pageListTicketQuery&#xfeff;。 GetMapping("/api/ticket-service/ticket/query")public Result<T…

查看笔记本电池健康状态-windows11

在 Windows 11 中获取详细的电池报告 Windows 11 中内置的 Powerfg 命令行选项来生成电池报告。 在任务栏上选择“搜索”&#xff0c;键入“cmd”&#xff0c;长按&#xff08;或右键单击&#xff09;“命令提示符”&#xff0c;然后选择“以管理员身份运行” ->“是”。 …

Mac使用K6工具压测WebSocket

commend空格 打开终端&#xff0c;安装k6 brew install k6验证是否安装成功 k6 version设置日志级别为debug export K6_LOG_LEVELdebug执行脚本&#xff08;进入脚本所在文件夹下&#xff09; k6 run --vus 100 --duration 10m --out csvresult.csv script.js 脚本解释&…

自定义神经网络三之梯度和损失函数激活函数

文章目录 前言梯度概述梯度下降算法梯度下降的过程 optimize优化器 梯度问题梯度消失梯度爆炸 损失函数常用的损失函数损失函数使用原则 激活函数激活函数和损失函数的区别激活函数Relu-隐藏层激活函数Sigmoid和Tanh-隐藏层Sigmoid函数Tanh&#xff08;双曲正切&#xff09; &l…

【前端】nginx 反向代理,实现跨域问题

前面讲跨域的问题&#xff0c;这篇 C# webapi 文章里面已经说过了。在上述文章中是属于从服务器端去允许访问的策略去解决跨域问题。而这里是从客户端的角度利用反向代理的方法去解决跨域问题。 反向代理&#xff1a;其原理就是将请求都接收到一个中间件&#xff08;中间地址&a…