adb shell dumpsys window和adb shell dumpsys window windows可以用来dump 相应的Window信息。
问题
读WMS 代码的时候,或许会想回答下面的几个问题(问题的回答不在本文💨)。
Window出错的提示- 常用的状态栏修改的理想方式
Window属性的设置是怎么起作用的- 如何比较好的处理
Dialog、PopupWindow。为什么Dialog不能使用ApplicationContext Window到底是什么,系统是怎么去识别一个Window的?多个Window重叠的时候是怎么展示的?层级关系是怎么解决的?像mac os、Windows(微软)都是有窗口的概念Window的展示区域是怎么计算的
流程分析
这里整体的流程是startActivity以后的操作。这里为了流程更加的清晰(更加简单😄),所以对打开的Activity设置<item name="android:windowDisablePreview">true</item>取消了startWindow。
先画一个整体的时序图。
第2步,方法参数是int
第3步,执行以后会生成mDecor(代表的是顶层的Window中的顶层View)和mContentParent(代表的是R.id.content中的内容)。最后调用mLayoutInflater.inflate(layoutResID, mContentParent);将设置的layoutResID加载进mContentParent。
第4步,generateDecor生成的是DecorView。
第5步,generateLayout生成的是R.id.content节点的View。这里会去获取Window的属性。aosp/frameworks/base/core/res/res/value/attrs.xml中定义了Window 的属性,而Window 的属性则是设置的Window 的主题
总结:上面3-6步过程结束以后就相当于创建完PhoneWindow以及构造完里面的View(此时还没有加入到Window)。
这里介绍一个类
android.view.Display。代表的是展示的逻辑大小(相当于是一个虚拟的大小),有两种不同的描述。
- 应用显示:代表的是除了系统装饰(statusBar、navigationBar等)以后的展示。
- 实际显示:代表的是包含了系统装饰的展示。
但是实际的物理展示大小跟
Display代表的意思不一样,Display可能会比设备的屏幕要小。
第7步,WindowManagerImpl#addView是Activity#makeVisible调用的。
第8步,adjustLayoutParamsForSubWindow不是每次都执行。比较重要的是会赋值token。
第9步,addView,首先WindowManagerGlobal是一个单例。看代码10、11、12行会将View、ViewRootImpl、WindowManager.LayoutParams分别保存在WindowManagerGlobal3个ArrayList中。由于WindowManagerGlobal是一个单例,所以在一个进程中所有Window的信息都会保存在这3个ArrayList中。
|
|
第11步,com.android.server.wm.Session,是通过进程间调用以后在服务端的处理。此时客户端挂起。
|
|
com.android.server.wm.Session代表的是什么,仅仅是用来跟WMS 交互吗?这里看相关代码的时对Session很迷糊,Session是IPC 的服务端,而每一个客户端的ViewRootImpl都有一个IWindowSession$Stub$Proxy,客户端和WMS 不直接进行IPC 通信,而是客户端与Session通信,Session直接与WMS 交互。
第12步,WindowManagerService#addWindow,这个方法比较复杂,后面会分成几个步骤分析。
这里有一些迷惑的内容:
ActivityRecord$Token、WindowToken、AppWindowToken、WindowState。
ActivityRecord$Token是ActivityManagerService标识的一个Activity。(即服务端表示的一个客户端的Activity)WindowState是WindowManagerService标识的一个Window。(Window在客户端是一个抽象的概念,在服务端使用WindowState表示)WindowToken和AppWindowToken:一个WindowToken里面保存了一个IBinder对应的所有WindowState信息(比如一个Activity和里面的Dialog对应的是同一个WindowToken)。AppWindowToken是继承WindowToken,代表记录的是一个ActivityRecord$Token对应的所有的WindowState信息。不是用来表示一个Window的标识的。
|
|
上面是ActivityManagerService中的两个重要的成员变量。
mWindowMap的key 对应的是ViewRootImpl$W(直接在system_process 进程做setView操作)或IWindow$Stub$Proxy(整个过程setView中从客户端传递过来的ViewRootImpl$W对象)mTokenMap的key 对应的则是ActvityRecord$Token(这里是一个IBinder对象,不一定都是ActvityRecord$Token,对Activity来讲就是一个ActvityRecord$Token;对PopupWindow来讲是ViewRootImpl$W)。
第13步,checkAddPermission权限检查。
第15步,addWindowToListInOrderLocked是将WindowState加入到WMS 中的流程:涉及到Window层级计算。打开一个Activity的时候有可能会执行两次addWindowToListInOrderLocked,原因是打开Activity的时默认有一个预览界面的,不过这里的流程是手动去掉了startWindow。
|
|
主要有3个步骤:
- 第8行的
addAppWindowToListLocked会计算出position,后面会分析。 - 第14行将当前的
WindowState加入到指定的位置(tokenWindowsPos)。(这里可以举一个例子,一般新Activity的时候tokenWindowsPos 为0,而在Activity中打开Dailog的时候tokenWindowsPos 为1) - 第23行,因为是
Activity,所以还需要将WindowState加入到AppWindowToken中。
这里的流程只是分析了
startActivity的,但是这里还有几个关键的判断需要注意下。第4行win.mAttachedWindow不为null 代表添加进来的是子Window会直接执行到addAttachedWindowToListLocked代表的是添加子Window。第10行addFreeWindowToListLocked则代表添加的是系统Window。(这里的逻辑可以分别使用PopupWindow和Toast来分析)
第16步,addAppWindowToListLocked是将应用Window(常见的是Activity和Dialog)加入到DisplayContent#mWindows(按照z-order 进行排序的)的流程。
|
|
主要有3个步骤:
- 第17行开始的两个循环遍历,是用来查找将要加入的
WindowState的参考的WindowState所在的Task 以及所对应的Token。(上面的代码注释的比较详细,但是还是总结一下,这里的每个Activity会分到不同的Task,每个Task里面也包含了很多的Token,会从后向前遍历Task寻找,对每一个Task里面的Token也是从后向前遍历,这样最终找到的餐参考值可能是当前WindowState对应的Task的上一个Task) - 第42行开始的两个循环遍历,计算参考的
WindowState。(这里比较关键的是每次都尽量取下面的值,第49行) - 第73行将
WindowState加入进去。
第3行的
DisplayContent和第14行的Task,需要解释一下。DisplayContent记录的是一个显示屏里的内容(比如DisplayContent#mWindows记录的是屏幕中的所有WindowState)。Task比较类似TaskRecord。
第17步,addAppWindowToTokenListLocked一般是已经加入然后这里是实际加入的Activity(startActivity先展示预览界面然后才展示Activity或者是打开Dialog的流程)。实现的功能和第16步是一样的。
|
|
主要分2种情况:
- 第6行,代表的是
Activity。 - 第11行,代表的是
Dialog。
总结:第15-17是关键步骤,主要是将需要展示Window加入到WindowManagerService的过程。完成以后所有的Window会按照z-order 升序保存在DisplayContent#mWindows中。
第19步,getInsetHintLw计算展示区域的大小。计算出来的值都是Inset(边衬区),里面的值实际上是距离展示区域的上下左右的值。这里计算出来的值可能跟最终实际展示的数据关系不大。
第20步,assignLayersLocked计算出WindowState的layer,用于最终Window的展示层级。
|
|
可以重点看看第12行判断语句,看第一个判断逻辑w.mBaseLayer == curBaseLayer,意思是当前的baseLayer 和上一个WindowState相同的时候就在上一个WindowState#mLayer的基础上+5,否则就会重新一次新的WindowState#mLayer计算。
总结:到此为止完成了Activity#setContentView的第一个流程。从代码的逻辑上看,是将WMS 中的mWindowMap的数据更新到DisplayContent#mWindows中,同时计算了DisplayContent#mWindows的layer 的值。此时对系统(WMS)来说,已经知道了展示WindowState以及每一个WindowState的层级关系。(后续的逻辑就是需要将这些WindowState绘制出来了)
代码总结
- PhoneWindow:在应用层代表的一个Window
- WindowState:在WMS 代表的是一个Window
- DisplayContent:代表显示屏幕的信息
- Task:WMS 里面中的一个Task 的概念,Task 对应的id 还与Activity 的TaskRecord 对应的信息是一致的。主要是用来计算
- Session:上面有介绍
- WindowToken、AppWindowToke:上面有介绍

入口是WindowManagerervice。