Android:窗口管理器WindowManager

Android:窗口管理器WindowManager

在这里插入图片描述

导言

本篇文章主要是对Android中与窗口(Window)有关的知识的介绍,主要涉及到的有:

  1. Window
  2. WindowManager
  3. WindowManagerService

主要是为了更进一步地向下地深入Android屏幕渲染的知识(虽然窗口可能并算不上)。

窗口(Window)

Q:什么是窗口

实际上Android上的窗口指的并不是具体的手机窗口而是一个抽象的概念,它本质上也是一个View,我会把窗口理解成一组有关联的View

ActivityManagerActivityManagerService的关系类似,WindowManager中方法的实现也是通过远程调用WindowManagerService实现的:

在这里插入图片描述

窗口的属性

窗口的类型

Window的类型大体来分有三种,我们可以在源文件中找到具体的对应:
在这里插入图片描述

    1. 应用程序窗口:最常见的,顶层应用的显示窗口
    1. 子窗口:需要依附在其他窗口的窗口
    1. 系统窗口: Toast,系统输入法窗口,系统错误窗口等

另外,每种窗口还有其对应的TYPE值,这个值主要是用来确定窗口的显示层次的,应用程序窗口的TYPE值在1-99范围内,子窗口在1000-1999,系统窗口在2000-2999。至于这个TYPE值会如何影响显示层次呢?这里我们可以简单的将这个TYPE值看做是一个z轴的坐标值,也就是垂直于手机屏幕的距离,数值越大,其离手机屏幕就越远,那么显示的优先级也会越高。

当然,实际的情况比这要复杂,会涉及到一些加权的计算,这里我们先简单这样理解即可。

窗口的标志

窗口的标志决定了窗口的一些响应特性,这里直接给出一些常用的flag理解一下:

Flag描述
FLAG_ALLOW_LOCK_WHILE_SCREEN_ON只要窗口可见,就允许在开启状态的屏幕上锁屏
FLAG_NOT_FOCUSABLE窗口不能获得输入焦点,在设置该标志的同时也会将FLAG_NOT_TOUCH_MODAL设置
FLAG_NOT_TOUCHABLE窗口不接受任何触摸事件
FLAG_NOT_TOUCH_MODAL将该窗口区域之外的触摸事件传递给其他的Window,而自己只会处理窗口区域内的触摸事件
FLAG_KEEP_SCREEN只要窗口可见,就会一直保持长亮
FLAG_LAYOUT_NO_LIMITS允许窗口显示在手机屏幕之外

Window的具体实现类PhoneWindow

这个PhoneWindow我们应该在Activity的setContentView方法中有提及到,这里再简单回顾一下:

    public void setContentView(@LayoutRes int layoutResID) {initViewTreeOwners();getDelegate().setContentView(layoutResID);}//Activity.java中public void setContentView(View view, ViewGroup.LayoutParams params) {getWindow().setContentView(view, params);initWindowDecorActionBar();}

当我们调用Activity的setContentView方法时首先会根据后面传入的xml布局文件初始化整棵视图树,之后会获取到Activity自身对应的Window对象,也就是描述Activity该如何显示的一个View,之后再调用该Window的setContentView方法,那这个Window对象是在何处被初始化的呢?答案是在Activity的attach方法中,该方法是在ActivityThread中被调用的:

final void attach(...) {attachBaseContext(context);mFragments.attachHost(null /*parent*/);mWindow = new PhoneWindow(this, window, activityConfigCallback);......mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);}

可以清楚的看到此处将Activity对应的PhoneWindow对象实例创建了出来,并将这个对象与一个WindowManager对象绑定起来,所以说上面Activity调用的setContentView最终是由这个PhoneWindow对象实例来完成的,最终就会在PhoneWindow中安装一个DecorView,DecorView作为整个PhoneWindow中的第一个View(实际上的根View),并把xml中的内容填充进DecorView的内容部分。

WindowManager(窗口管理者)

WindowManager接口

接下来我们从源码角度先分析一下WindowManager:

public interface WindowManager extends ViewManager

可以看到WindowManager本质上是一个继承了ViewManager接口的一个接口,因为ViewManager比较简单,我们先来看ViewManager接口:

public interface ViewManager
{public void addView(View view, ViewGroup.LayoutParams params);public void updateViewLayout(View view, ViewGroup.LayoutParams params);public void removeView(View view);
}

首先这个类有一段注释,大概是说:这个接口是让你在Activity中添加或者移除子View的
实际上这三个方法也很直白,addView方法用于添加子View,updateViewLayout用于更新子View,而removeView方法用于移除子View。

从WindowManager继承了ViewManager这个角度我们也可以看出来Window实际上就是View,WindowManager只不过是在ViewManager接口的基础上添加了对窗口管理的逻辑,包括Window的类型,显示层级等处理。额外的逻辑中根据Window添加了两个方法:

  1. public Display getDefaultDisplay() (该方法已经废弃,用Context.getDisplay()进行替代):得到WindowManager所管理的屏幕 (Display)
  2. public void removeViewImmediate(View view) (同步方法,立即移除一个View,会触发View.onDetachFromWindow回调)

Window绑定WindowManager

一开始给出的一个简单的示意图中我们已经明确了一点:Window是由WindowManager进行管理的,并且在上一段中我们知道Window是在ActivityThread调用的attach方法之中通过mWindow.setWindowManager方法来绑定的,这一小段之中我们就来稍微看一眼这个方法的逻辑:

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,boolean hardwareAccelerated) {mAppToken = appToken;mAppName = appName;mHardwareAccelerated = hardwareAccelerated;if (wm == null) {wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);}mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);}public WindowManagerImpl createLocalWindowManager(Window parentWindow) {return new WindowManagerImpl(mContext, parentWindow, mWindowContextToken);}private WindowManagerImpl(Context context, Window parentWindow,@Nullable IBinder windowContextToken) {mContext = context;mParentWindow = parentWindow;mWindowContextToken = windowContextToken;}

可以看到这个方法主要会涉及到三个方法间的跳转,第一个方法中首先会通过Binder通信获取到系统服务之一的WindowService,之后就会跳转到第二个方法中,创建并返回一个WindowManagerImpl的实例。然后第三个方法创建这个示例的时候实际上就是对传入的数据进行了一个简单的封装,就是将需要绑定的Window对象,上下文对象Context,以及可以与WindowService进行通信的IBinder对象进行了一个封装:
在这里插入图片描述
我觉得这样做的目的也很明显,这样一下WindowManagerImpl同时持有了需要被操作的Window提供操作服务的WindowService的通信手段,这样一来就可以借助WindowService来操作Window对象了:
在这里插入图片描述
最后,我们可以来看一看WindowManagerImpl的addView方法:

    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyTokens(params);mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,mContext.getUserId());}

可以看到WindowManagerImpl自身并不实现addView方法,而是将其委托给mGlobal实现,这个mGlobal实际上是一个WindowManagerGlobal对象,所有的WindowManagerImpl对象都是将其委托给WindowManagerGlobal对象实现的,而WindowManagerGlobal又是一个单例的对象,所以说实际上所有的WindowManagerImpl都是通过过一个对象来实现对View的操作的。

另外提一嘴,这里WindowManagerImpl将实现分为了抽象和具体两个部分,用到了桥接模式

//Global是单例的
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
//DCL单例
public static WindowManagerGlobal getInstance() {synchronized (WindowManagerGlobal.class) {if (sDefaultWindowManager == null) {sDefaultWindowManager = new WindowManagerGlobal();}return sDefaultWindowManager;}
}

WindowManager关联类

实际上在上面介绍Window的过程中我们已经差不多已经把关联类介绍过了,此处借用进阶解密中的一张图来总结:
在这里插入图片描述

ViewRootImpl–WindowManager与Window的中转站

ViewRootImpl的职责

ViewRootImpl顾名思义就是名义上的View视图树的根节点,它有着多种职责:

  1. View树的根并且管理整颗视图树
  2. 触发View的测量,布局和绘制
  3. 输入事件的中转站
  4. 管理Surface
  5. 负责与WMS进行通信

关于ViewRootImpl与WMS的通信,具体是通过一个Session进行的,可以看以下这张图:
在这里插入图片描述

ViewRootImpl存储Window

当我们需要将之前创建的PhoneWindow添加到屏幕上时,显然就需要调用到WindowManageraddView方法了,具体我们也知道是会委托到WindowManagerGlobal来执行相关的操作,我们直接跳进WindowManagerGlobal来看看相关的逻辑:

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {........一些错误检查 ViewRootImpl root;View panelParentView = null;//上锁synchronized (mLock) {//加载参数//判断当前View是否重复添加........IWindowSession windowlessSession = null;........if (windowlessSession == null) {//如果Session为空就新生成一个ViewRootImplroot = new ViewRootImpl(view.getContext(), display);} else {//如果Session为空就新生成一个ViewRootImpl,并且把Session传入root = new ViewRootImpl(view.getContext(), display,windowlessSession);}//设置相关的布局参数view.setLayoutParams(wparams);//维护三个列表 //Views列表mViews.add(view);//ViewRootImpl列表mRoots.add(root);//布局参数列表mParams.add(wparams);try {//调用ViewRootImpl的setView绑定Windowroot.setView(view, wparams, panelParentView, userId);} catch (RuntimeException e) {if (index >= 0) {removeViewLocked(index, true);}throw e;}}}

相关的重要注释已经在上面的代码处标注出来了,我们可以发现这个方法中动态地维护了WindowManagerGlobal中的三个列表:

@UnsupportedAppUsage
private final ArrayList<View> mViews = new ArrayList<View>();
@UnsupportedAppUsage
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
@UnsupportedAppUsage
private final ArrayList<WindowManager.LayoutParams> mParams =new ArrayList<WindowManager.LayoutParams>();

可以看到他们都带有@UnsupportedAppUsage说明是不支持其他非系统App调用的,第一个列表维护的是被添加的View,第二个列表维护的是生成的ViewRootImpl,第三个列表是Window的布局参数。

而在这个addView的具体方法中,会先生成一个对应的ViewRootImpl对象作为整颗视图树的根节点,之后还会将被添加的Window和这个根节点绑定起来,这样根节点就可以管理这整颗视图树了。

读到这里相信大家也知道我为什么称ViewRootImpl为WindowManager与Window之间的中转站了:ViewRootImpl作为根节点管理整个Window,当Window中有请求发出的时候第一时间给ViewRootImpl进行处理,然后ViewRootImpl再通过WindowManagerGlobal的Binder机制与WindowManagerService间接地进行通信。

题外话:在子线程真的不能更新UI吗

首先我们需要刷新UI的话首先也是需要通过ViewManager接口中的updateViewLayout方法发起的,在具体实现中是交给WindowManagerGlobal实现的:

    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {if (view == null) {throw new IllegalArgumentException("view must not be null");}if (!(params instanceof WindowManager.LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");}final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;//1-------1view.setLayoutParams(wparams);synchronized (mLock) {int index = findViewLocked(view, true);ViewRootImpl root = mRoots.get(index);mParams.remove(index);mParams.add(index, wparams);root.setLayoutParams(wparams, false);}}

这段方法中最重要的就是注释一处的view.setLayoutParams(wparams)方法中,这个方法还会进行一次跳转,最终会执行到ViewRootImpl的scheduleTraversals方法中:

    void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);notifyRendererOfFramePending();pokeDrawLockIfNeeded();}}

可以看到在这里会通过Handler向ViewRoot的handler对象发送一个同步屏障和Runnable任务,这个任务的具体内容如下:

final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}
}void doTraversal() {
if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);if (mProfile) {Debug.startMethodTracing("ViewAncestor");}performTraversals();if (mProfile) {Debug.stopMethodTracing();mProfile = false;}}
}

实际上就是执行performTraversals()方法,这个方法我们可很熟悉,就是开启三大流程的方法,而这个过程中一旦涉及到performLayout方法的执行就会进行一个线程的检查:

    public void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {//检查线程checkThread();mLayoutRequested = true;scheduleTraversals();}}

上面出现的checkThread()方法就是导致我们平时无法在主线程更新UI的原因,具体逻辑如下:

void checkThread() {if (mThread != Thread.currentThread()) {throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");}
}

这个方法是在ViewRootImpl中执行的,也就是说他检查的是ViewRootImp的mThread线程是否是当前的线程,至于这个mThread是在哪里被赋值的,实际上是在其构造函数中被赋值的;

所以说,并不是只有主线程不能更新UI,而是只有创建ViewRootImpl实例的线程才能更新UI。一般情况下ViewRootImpl的创建都是在ActivityThread,也就是主线程中进行的,所以说才会说只有主线程能更新UI。

那有没有别的方法可以让我们在子线程更新UI呢?实际上是有的,比如我们可以使用SurfaceView或者TextureView,这些特殊View的绘制过程与一般的View不同,并且他们可以单独持有一个Surface。

我们也可以自己在代码中添加View,然后让添加View和更新View的操作放在一个线程里跑就好了。

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

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

相关文章

打破尺寸记录!荷兰QuTech研发16量子点阵列新技术

承载16个量子点交叉条阵列的量子芯片&#xff0c;可无缝集成到棋盘图案&#xff08;图片来源&#xff1a;网络&#xff09; 由荷兰代尔夫特理工大学(TU Delft)和荷兰应用科学研究组织(TNO)组建的荷兰量子计算研究中心QuTech的研究人员开发了一种用相对较少的控制线来控制大量量…

Python 算法高级篇:图的表示与存储优化

Python 算法高级篇&#xff1a;图的表示与存储优化 引言 1. 什么是图&#xff1f;2. 图的基本概念3. 图的表示方法3.1. 临接矩阵表示临接矩阵的优点&#xff1a;临接矩阵的缺点&#xff1a; 3.2. 邻接表表示邻接表的优点&#xff1a;邻接表的缺点&#xff1a; 4. 优化的存储方法…

【C++笔记】C++继承

【C笔记】C继承 一、继承的概念二、继承的语法和权限三、父类和子类成员之间的关系3.1、子类赋值给父类(切片)3.2、同名成员 四、子类中的默认成员函数4.1、构造函数4.2、拷贝构造4.3、析构函数 五、C继承大坑之“菱形继承”5.1、什么是“菱形继承”5.2、解决方法 一、继承的概…

C++深度优化(DFS)算法的应用:收集所有金币可获得的最大积分

涉及知识点 深度优化(DFS) 记忆化 题目 节点 0 处现有一棵由 n 个节点组成的无向树&#xff0c;节点编号从 0 到 n - 1 。给你一个长度为 n - 1 的二维 整数 数组 edges &#xff0c;其中 edges[i] [ai, bi] 表示在树上的节点 ai 和 bi 之间存在一条边。另给你一个下标从 0…

ArcGIS笔记13_利用ArcGIS制作岸线与水深地形数据?建立水动力模型之前的数据收集与处理?

本文目录 前言Step 1 岸线数据Step 2 水深地形数据Step 3 其他数据及资料 前言 在利用MIKE建立水动力模型&#xff08;详见【MIKE水动力笔记】系列&#xff09;之前&#xff0c;需要收集、处理和制作诸多数据和资料&#xff0c;主要有岸线数据、水深地形数据、开边界潮位驱动数…

位(bit)、字节(byte)、字、英文字符、中文字符的关系详解(涵盖字符编码)

目录 0 引言1 位、字节、字2 字符编码2.1 为什么要有字符编码2.2 字符编码的种类有哪些拓展&#xff1a;ANSI 编码 3 英文字符与中文字符的区别 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;C专栏&#x1f4a5; 标题&#xff1a;位&#xff08;…

至高直降3000元,微星笔记本双11爆款推荐、好评有礼拿到手软

今年双11来的更早一些&#xff0c;微星笔记本先行的第一波雷影17促销活动&#xff0c;就已经领略到玩家们满满的热情。开门红高潮一触即发&#xff0c;微星笔记本双11活动周期至高直降3000元&#xff0c;众多爆款好货已经开启预约预售&#xff1a;有硬核玩家偏爱的性能双雄&…

聚观早报 |2024款飞凡R7官宣;小米14新配色材质

【聚观365】10月27日消息 2024款飞凡R7官宣 小米14新配色材质 金山办公2023第三季度业绩 IBM2023第三季度业绩 新东方2024财年第一季度业绩 2024款飞凡R7官宣 飞凡汽车官宣&#xff0c;2024款飞凡R7将于11月上市&#xff0c;新车将搭载飞凡巴赫座舱&#xff0c;同时超过1…

Node编写重置用户密码接口

目录 前言 定义路由和处理函数 验证表单数据 实现重置密码功能 前言 接前面文章&#xff0c;本文介绍如何编写重置用户密码接口 定义路由和处理函数 路由 // 重置密码的路由 router.post(/updatepwd, userinfo_handler.updatePassword) 处理函数 exports.updatePasswo…

php之 角色的权限管理(RBAC)详解

RBAC&#xff08;Role-based access control&#xff09;是一种常见的权限管理模型&#xff0c;通过将用户分配至特定的角色&#xff0c;以及为角色分配访问权限&#xff0c;实现了权限管理的目的。以下是关于RBAC的详细解释&#xff1a; 角色&#xff1a;RBAC模型的核心是角色…

65、内网安全-域环境工作组局域网探针方案

目录 案例1-基本信息收集操作演示案例2-网络信息收集操作演示案例3-用户信息收集操作演示案例4-凭据信息收集操作演示案例5-探针主机域控架构服务操作演示涉及资源 我们攻击内网一般是借助web攻击&#xff0c;直接进去&#xff0c;然后再去攻击内网&#xff0c;那么攻击的对象一…

搞懂 MySql 的架构和执行流程

搞懂 MySql 的架构和执行流程 1、MySQL 的三层架构2、SQL 的执行流程2.1、连接器2.2、解析器2.3、预处理器2.4、优化器2.5、执行器2.6、存储引擎 3、关于Select 的两个顺序 1、MySQL 的三层架构 MySQL的三层结构包括&#xff1a; 连接层&#xff1a;负责与MySQL客户端之间的通…

matlab中类的分别之handle类和value类——matlab无法修改类属性值的可能原因

写在之前&#xff08;吐槽&#xff09; 最近由于变化了一些工作方向&#xff0c;开始需要使用matlab进行开发&#xff0c;哎哟喂&#xff0c;matlab使用的我想吐&#xff0c;那个matlab编辑器又没代码提示&#xff0c;又没彩色&#xff0c;我只好用vscode进行代码编辑&#xf…

13.6性能测试理论

一.什么是性能测试 1.定义: 测试人员借助性能测试工具(LoadRunner等),模拟系统在不同场景下(使用高峰期等),对应的性能指标是否达到预期. 2.性能测试和功能测试的区别: a.功能测试依靠人工,性能测试依靠工具. b)功能测试要求软件能正常运行,不管什么场景,性能测试要求软件…

嵌入式 Tomcat 调校

SpringBoot 嵌入了 Web 容器如 Tomcat/Jetty/Undertow&#xff0c;——这是怎么做到的&#xff1f;我们以 Tomcat 为例子&#xff0c;尝试调用嵌入式 Tomcat。 调用嵌入式 Tomcat&#xff0c;如果按照默认去启动&#xff0c;一个 main 函数就可以了。 简单的例子 下面是启动…

故障诊断入门书籍资料免费领取

前言 本期分享免费提供9本故障诊断领域相关的书籍资料&#xff0c;可自行下载 一、主要内容 二、书籍获取

VR结合|山海鲸虚拟展厅解决方案

方案背景 虚拟现实技术是另一项革命性的创新&#xff0c;它可以将用户带入一个完全虚拟的环境中。借助VR头盔和控制器&#xff0c;用户可以亲临虚拟现实中&#xff0c;与数字世界互动&#xff0c;仿佛置身于其中。 山海鲸根据用户实际需求变化将数字孪生与虚拟现实技术相结合…

Web攻防06_sqlmap的使用

文章目录 参考链接&#xff1a; SQLMAP简介支持五种不同的注入模式 数据猜解-库表列数据权限操作引出权限&#xff1a;引出文件&#xff1a;引出命令&#xff08;执行命令&#xff09;&#xff1a; 提交方法-POST&HEAD&JSONPost注入cookie注入注入请求头中&#xff08;…

【1++的Linux】之进程间通信

&#x1f44d;作者主页&#xff1a;进击的1 &#x1f929; 专栏链接&#xff1a;【1的Linux】 文章目录 一&#xff0c;进程间通信的目的二&#xff0c;管道 一&#xff0c;进程间通信的目的 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程资源共享&#xff1a;…

深度学习:张量 介绍

张量[1]是向量和矩阵到 n 维的推广。了解它们如何相互作用是机器学习的基础。 简介 虽然张量看起来是复杂的对象&#xff0c;但它们可以理解为向量和矩阵的集合。理解向量和矩阵对于理解张量至关重要。 向量是元素的一维列表&#xff1a; 矩阵是向量的二维列表&#xff1a; 下标…