Android View绘制1 performTraversals流程

一 概述

我们在Android窗口管理5 理解ViewRootImpl中已经简单介绍过 performTraversals 方法,接下来我们展开进行深入分析。

我们知道整个 Activity 显示出来之前需要经过 View 的绘制,首先,我们先来看一下 Activity 中 Window 的几个区域:
在这里插入图片描述
在这里插入图片描述
Overscan(过扫描区)

overscan 是电视机特有的概念,上图中黄色的区域就是 overscan 区域,指的是电视机四周一圈的黑色区域,成为overscan (过扫描) 区域,这个区域也是显示屏的一部分,但通常不能显示。如果窗口的某些内容画在这个区域里,它在某些电视上就会看不到。为了避免这种情况发生,通常要求 UI 不要画在屏幕的边角上,而是预留一定的空间。因为 Overscan 的区域大小随着电视不同而不同,它一般由终端用户通过 UI 指定,(比如说 GoogleTV 里就有确定 Overscan 大小的应用,更多关于 Overscan 的内容涉及到早起电视机的显示缺陷问题,对这方法面比较感兴趣的读者可以自行搜索)。

OverscanScreen,Screen
OverscanScreen 是包含 Overscan 区域的屏幕大小,而 Screen 则为去除 Overscan 区域后的屏幕区域,OverscanScreen > Screen。

Restricted and Unrestricted
有些区域是被系统保留的,比如说手机屏幕上方的状态栏和下方的导航栏,根据是否包括这些预留的区域,Android 把区域分为 Unrestricted Area 和 Resctrited Aread,前者包括这部分预留区域,后者则不包含,Unrestricted area > Rectricted area。

RestrictedOverScanScreen
包括 overscan 区,不包含导航栏、因此这个区域上面到屏幕的顶部,下面就到导航栏的顶部。
  
RestrictedScreen
这个区域不包含 overscan 区域不包含导航栏。
  
UnRestrictedScreen
不包含屏幕的 overscan 区域,包含状态栏和导航栏。
  
stableFullScreen
包含状态栏、输入法、不包含导航栏。
  
Decor区域
不包含状态栏、不包含导航栏、包含输入法区域。
  
Curren
不包含状态栏、不包含导航栏、不包含输入法区域。
  
Insects
insets “边衬” 的定义如上图所示,其本质就是一块屏幕区域的边衬。图二中的示意:

假如 content 区域周围有一圈类似于状态栏的东西,此时整个界面就是 content 内容区+一圈状态栏,那么这一圈状态栏就叫做“内容区边衬 (content insects) ";同理我们还可以得到”可见区边衬(visible insects)“。

在大多数情况下,Activity 窗口的内容区域和可见区域的大小是一致的,而状态栏和输入法窗口所占用的区域又称为屏幕装饰区 (decorations)。WMS 服务实际上就是需要根据屏幕以及可能出现的状态栏和输入法窗口的大小来计算出 Activity 窗口的整体大小及其过扫描区域边衬和可见区域边衬的大小。

PhoneWindowManager 一共定义了 10 种区域,剩下的 system 区域、Stable 区域、Content 区域的范围和 Decor 区域相同。这些区域在不同场合含义不同,但是值是相同的。

需要注意的是,overscan 区域和智能手机屏幕上的"大黑边"不是一会事,前者是软件层面的问题,后者是硬件层面问题,这里不做过多解释。

理解了上述的区域划分之后,我们正式开始本文的分析。此篇文章我们重点分析 performTraversals() 方法,因为这个方法是三大流程的开端,在它其中做了很多的事情。

二 ViewRootImpl.performTraversals

ViewRootImpl 类中的 performTraversals() 方法,这个方法大约有 800 行代码,我们分段来分析,这里我们尽量省略了一些不相关的代码。

阶段一:计算窗口期望尺寸

    private void performTraversals() {
   
   
        //mView就是DecorView根布局,记录ViewRootImpl管理
        //的View树的根节点,通过setView方法传进来的
        final View host = mView;    
        //mAdded指DecorView是否被成功加入到window中,在setView()中被赋值为true
        if (host == null || !mAdded)    
            return;

        mIsInTraversal = true;      //是否正在遍历
        mWillDrawSoon = true;       //是否马上绘制View
        boolean windowSizeMayChange = false; //视图的大小可能改变
        boolean surfaceChanged = false;     //界面改变
        WindowManager.LayoutParams lp = mWindowAttributes;

        int desiredWindowWidth;//顶层视图DecorView窗口的期望宽高
        int desiredWindowHeight;
        //DecorView视图是否可见
        final int viewVisibility = getHostVisibility();
        //视图可见性改变
        final boolean viewVisibilityChanged = !mFirst
                && (mViewVisibility != viewVisibility ||
                mNewSurfaceNeeded
                // Also check for possible double visibility update,
                // which will make current viewVisibility value 
                // equal to mViewVisibility and we may miss it.
                || mAppVisibilityChanged);
        ......
        //用来保存窗口宽度和高度,来自于全局变量mWinFrame
        //这个mWinFrame保存了窗口最新尺寸
        Rect frame = mWinFrame; 
        //在构造方法中mFirst已经设置为true,表示是否是第一次
        //被请求traversals,在后面的代码中被赋值false
        if (mFirst) {
   
   
            mFullRedrawNeeded = true; //是否需要全部重绘
            mLayoutRequested = true; //是否要求重新Layout界面
            
            final Configuration config =
                mContext.getResources().getConfiguration();
            // 初始化期望窗口长宽
            if (shouldUseDisplaySize(lp)) {
   
   //如果窗口的类型是状态栏,输入法等
                // NOTE -- system code, won't try to do compat mode.
                Point size = new Point();
                //设置窗口尺寸为屏幕真实尺寸,不剪去任何装饰的尺寸
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
            } else {
   
   
                desiredWindowWidth = mWinFrame.width();
                desiredWindowHeight = mWinFrame.height();
            }
            ......
        } else {
   
   
            //除了第一次被请求执行traversals以外,它的当前宽度
            //和高度就等于成员变量mWinFrame/frame中的宽度和高度
            desiredWindowWidth = frame.width();
            desiredWindowHeight = frame.height();
           
            if (desiredWindowWidth != mWidth ||
                desiredWindowHeight != mHeight) {
   
   
                //mWidth此时代表的是上一次执行该方法的时候的frame.width()值
                //如果此时两值不相等,那就说明视图改变需要重新测量绘制了。
                mFullRedrawNeeded = true;   //需要重新绘制标志位
                mLayoutRequested = true;    //要求重新Layout标志位
                windowSizeMayChange = true; //Window的尺寸可能改变
            }
        }

这段代码中主要交代了 Activity 当前的 Window 的宽高是如何得出的。

本段代码结尾的地方,关于 desiredWindowWidth 和 mWidth 的比较,我们需要介绍一下:首先这个 mWidth 和 mHeight 也是用来描述 Activity 窗口当前宽度和高度的,与 desiredWindowWidth 和 desiredWindowHeight 的区别是它们的值是由应用程序进程上一次主动请求 WMS 服务计算得到的,并且会一直保持不变到应用程序进程下一次再次请求 WMS 服务来重新计算为止 (这个在下文的代码中也有所体现)。

如果 Activity 窗口不是第一次被请求执行 traversals,并且 Activity 窗口上一次请求 WMS 服务计算得到的宽度 mWidth 和高度 mHeight 不等于 Activity 窗口的当前宽度 desiredWindowWidth 和当前高度 desiredWindowHeight,那么就说明 Activity 窗口的大小发生了变化,这时候变量 windowSizeMayChange 的值就会被标记为 true,以便接下来可以对 Activity 窗口的大小变化进行处理。
  
阶段二:再计算窗口期望尺寸并开始测量流程

        ......
        boolean insetsChanged = false;

        boolean layoutRequested = mLayoutRequested &&
            (!mStopped || mReportNextDraw);
        if (layoutRequested) {
   
   
            final Resources res = mView.getContext().getResources();
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值