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
分别保存在WindowManagerGlobal
3个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
。