从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_DECORflag。(典型的应用Window,Activity初始化PhoneWindow的时候会设置)。第51行Window含有FLAG_LAYOUT_IN_SCREENflag,并且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内核设计思想》