从ViewRootImpl
开始去寻找DecorView
的测量绘制流程,过程涉及到Window
的大小位置的测量。基于api 25(7.1.1)。
DecorView 的测量、布局、绘制流程
下图是整体的绘制的流程。
第1步,这里可以简单理解是系统会ViewRootImpl#performTraversals
来开始绘制。performTraversals
整体做了4件事情:
- performMeasure:用来测量
View
大小 - relayoutWindow:用来测量
Window
大小 - performLayout:用来对
View
布局 - performDraw:处理
View
的绘制
|
|
第35行开始测量View
,第83行是测量完Window
以后发现大小变化以后重新测量View
。
第43行测量Window
。
第91行开始对View
布局。
第124行开始绘制View
。
第3步,ViewRootImpl#measureHierarchy
测量布局大小。这里的测量跟普通View
的测量不一样。自定义View那些事之前有涉及到。
第4步,ViewRootImpl#relayoutWindow
用来测量Window
大小。
这里可以从多个情况去看里面的实现:
- 最普通的
Activity
打开- 设置全屏的
Activity
(WindowManager.LayoutParams.FLAG_FULLSCREEN
)- 隐藏
Navigation
的情况- …
第5步,Session#relayout
最终调用到服务端计算。
|
|
注意从outFrame
开始后面的所有参数都是out 类型的,即服务端计算好以后会将值返回给客户端。
第6步,WindowManagerService#relayoutWindow
的逻辑,主要是创建Surface
的逻辑以及计算Window
大小位置(这个会在下面单独分析)。
第8步,ViewRootImpl#performDraw
。
|
|
会分为硬件和软件两种绘制方法,软件绘制是通过Surface#lockCanvas
获取到Canvas
以后调用View#draw
进行绘制。硬件绘制后续展示。
总结:到此结束了View
的测量(measure
)、布局(layout
)和绘制(draw
)。
Window 测量流程
主要是对上面第6步的展开分析,View
最终有多大需要通过测量Window
以后才能准确知道。最终展示的大小可以通过adb shell dumpsys window windows
查看(查看Frames:
节点就行,里面会有containing=
、parent=
、display=
等)。
先来分析三个类中关于Window
大小:
- 了解
ViewRootImpl
中成员变量的含义 - 了解
WindowState
中成员变量的含义 - 了解
PhoneWindowManager
中成员变量的含义
ViewRootImpl
中成员变量的含义
|
|
上面7个Rect
属性的表达方式不同:mWinFrame
的4个值分别代表的是左上角和右下角的坐标。而以Inset(这里翻译成边衬区)结尾的几个值代表的是距离左、上、右、下的值。
- mPendingVisibleInsets:假设一个Window 的四周都有被一块类似输入框的区域遮住(注意是遮住,那块区域还是存在),得到中间的那一块区域就称为可见区域,而被剔除出来的区域所组成的区域就称为可见边衬区域(Visible Insets)
- mPendingContentInsets:假设一个Window 的四周都有一块类似状态栏的区域,那么将这些区域剔除之后,得到中间的那一块区域就称为内容区域,而被剔除出来的区域所组成的区域就称为内容边衬区域(Content Insets)(来自老罗博客,最下面有地址)
- mPendingStableInsets:代表的是剔除System Bar 所占据的位置以后的区域(不管Status Bar 和Navigation Bar 是否可见都会计算在内)。
WindowState
中成员变量的含义
之前(WMS之addView流程)已经知道了WindowState
是WMS 用来标识一个Window
的。这里主要去看WindowState
中关于大小和位置的参数。
Field | 含义 |
---|---|
mRequestedWidth mRequestedHeight mLastRequestedWidth mLastRequestedHeight |
应用请求Window 的大小 |
mVisibleInsets mLastVisibleInsets mVisibleInsetsChanged |
Window 的可见边衬区 |
mContentInsets mLastContentInsets mContentInsetsChanged |
Window 的内容边衬区 |
mOverscanInsets mLastOverscanInsets mOverscanInsetsChanged |
Window 的过扫描边衬区 |
mStableInsets mLastStableInsets mStableInsetsChanged |
Window 被系统Window 遮住的固定边衬区 |
mOutsets mLastOutsets mOutsetsChanged |
不在Window 的Surface 区域但可以绘制的区域(不理解) |
mFrame mLastFrame mFrameSizeChanged |
最后展示的区域 |
mCompatFrame mContainingFrame mParentFrame mDisplayFrame mOverscanFrame mStableFrame mDecorFrame mContentFrame mVisibleFrame mOutsetFrame mInsetFrame |
PhoneWindowManager
中成员变量的含义
Overscan 区域的意思:Overscan 代表“过扫描”,有些显示屏(比如电视)可能存在失真现象,且越靠近边缘越严重。为了避开这个“固有缺陷”,不少厂商都把扫描调整到画面的5%~10%,这样造成的结果就是画面很可能显示不全,损失的部分称为“Overscan”。
Field | 含义 |
---|---|
mOverscanScreenLeft mOverscanScreenTop mOverscanScreenWidth mOverscanScreenHeight |
屏幕的真实坐标,包含了过扫描的区域。代表的是左上角坐标和屏幕宽高 |
mUnrestrictedScreenLeft mUnrestrictedScreenTop mUnrestrictedScreenWidth mUnrestrictedScreenHeight |
不包含过扫描区域,包含了状态栏的区域的大小 |
mRestrictedOverscanScreenLeft mRestrictedOverscanScreenTop mRestrictedOverscanScreenWidth mRestrictedOverscanScreenHeight |
与mOverscanScreen* 类似,但是可以移动到过扫描区域 |
mRestrictedScreenLeft mRestrictedScreenTop mRestrictedScreenWidth mRestrictedScreenHeight |
屏幕当前的区域,但是不包含状态栏 |
mSystemLeft mSystemTop mSystemRight mSystemBottom |
所有可见的UI元素区域(包含了系统状态栏区域) |
mStableLeft mStableTop mStableRight mStableBottom |
不包含状态栏的区域(不管状态栏是否可见) |
mStableFullscreenLeft mStableFullscreenTop mStableFullscreenRight mStableFullscreenBottom |
与mStable*类似。不包含状态栏的区域 |
mCurLeft mCurTop mCurRight mCurBottom |
包含状态栏、输入法的内容区域 |
mContentLeft mContentTop mContentRight mContentBottom |
内容区域 |
mVoiceContentLeft mVoiceContentTop mVoiceContentRight mVoiceContentBottom |
语音区域 |
mDockLeft mDockTop mDockRight mDockBottom |
输入法区域 |
PhoneWindowManager
中还有一系列关于WindowState
计算的值。下面的9个值都是Rect
,代表的都是左上角和右下角的坐标。
Field | 含义 | 备注 |
---|---|---|
mTmpParentFrame | 父窗口区域 | 一般就是mTmpDisplayFrame |
mTmpDisplayFrame | Window 完整展示区域 |
StatusBar 一般是全屏,因为StatusBar 是下拉展示全屏 |
mTmpOverscanFrame | 左上角(overscanLeft,overscanTop) 右下角(displayWidth-overscanRight,displayHeight-overscanBottom) |
去掉overscan 区域以后的区域 |
mTmpContentFrame | Window 内容区域 |
|
mTmpVisibleFrame | Window 可见区域 |
|
mTmpDecorFrame | ||
mTmpStableFrame | 左上角(0,statusBarHeight) 右下角(displayWidth,displayHeight-navigationBarHeight) |
没有计算overscan |
mTmpNavigationFrame | NavigationBar 对应的坐标 |
|
mTmpOutsetFrame | Surface 区域外 |
流程
首先上一张测量的流程图。
第1步,从WindowManagerService#relayoutWindow
调起的。
第3步,WindowSurfacePlacer#performSurfacePlacementInner
,还有一个调用可以关注下WinowState#reportResized()
。
第5步,WindowSurfacePlacer#performLayoutLockedInner
。测量的关键方法。
|
|
主要是3个方法和2个遍历。
3个方法:
PhoneWindowManager#beginLayoutLw
:初始化值操作。第7行PhoneWindowManager#layoutWindowLw
:测量每一个WindowState
。第30行PhoneWindowManager#finishLayoutLw
:结束操作。第61行
2个遍历:
- 遍历非Stub Window,处理测量流程。第11行
- 遍历Stub Window,处理测量流程(因为子Window 需要依赖父Window 的测量结果)。第38行
第6步,PhoneWindowManager#beginLayoutLw
初始化相关值。
这个方法前部分全是赋值操作,上面介绍过这些值代表的含义。主要看
最后的layoutNavigationBar
和layoutStatusBar
。
|
|
NavigationBar
的计算分3种请求(NavigationBar
可以随着屏幕的旋转出现在底、左、右)。
主要是分析了在底部的逻辑:
第12行计算出NavigationBar
左上角坐标。底部NavigationBar
默认宽度是屏幕宽度。
第14行就可以计算出底部NavigationBar
的在屏幕的位置信息,保存在mTmpNavigationFrame
。然后就会修改一些相关的值。
第39行,调用WindowState#computeFrameLw
(这个方法后续分析)对WindowState
内部属性赋值。layoutStatusBar
主要是执行了WindowState#computeFrameLw
。
第7步,PhoneWindowManager#layoutWindowLw
。这个逻辑会对当前展示的所有Window
单独处理的。这里处理的顺序是从用户看到层级展示的(即优先处理的是layer 比较大的Window)。
所有WindowState
都会走这里的逻辑,这肯定是会有性能影响的,所以执行是有条件的,可以看第5步中的代码。layoutWindowLw
逻辑里面分成了6个case 来计算不用的WindowState
的大小和位置。
|
|
!isDefaultDisplay
:代表不是默认屏(即DisplayContent.mDisplayId != 0
),一般为false 所以不考虑。第16行attrs.type == TYPE_INPUT_METHOD
:代表的是输入法。第17行attrs.type == TYPE_VOICE_INTERACTION
:代表的是语音界面。第18行attrs.type == TYPE_WALLPAPER
:壁纸窗口。第19行win == mStatusBar
:代表的是状态栏。第20行- 其他:代表
Activity
、Dialog
、PopupWindow
、Toast
等。
主要分析下第6种情况,这里面又分成了4种情况:
Window
含有FLAG_LAYOUT_IN_SCREEN
和FLAG_LAYOUT_INSET_DECOR
flag。(典型的应用Window
,Activity
初始化PhoneWindow
的时候会设置)。第51行Window
含有FLAG_LAYOUT_IN_SCREEN
flag,并且View
设置了SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
或者SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
的标志。即全屏显示。第126行attached != null
即子Window
。第129行- 其他情况:窗口必须在所有的装饰里面展示。第130行
本来准备分析一个
Activity
的测量过程的,但是有太多不同配置了。这个如果是有碰到问题,可以具体的根据设置的属性查阅。
后续的处理就是WindowState#computeFrameLw
来计算每一个WindowState
的位置和大小,主要是根据上面PhoneWindowManager#layoutWindowLw
计算出的值。
第8步,PhoneWindowManager#finishLayoutLw
结束,目前没有任何实现。
总结:Window
的测量并不是一个简单的过程,一个最简单的Activity
展示出来就需要考虑输入框、StatusBar
、NavigationBar
等众多Window
,还需要考虑Window
设置的flags 以及View
设置的mSystemUiVisibility 等。
参考
Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析
《深入理解Android内核设计思想》