Android-高级 UI-03-View 绘制流程

155 阅读5分钟

总体流程

Android 的 View 绘制流程是 GUI 系统的核心机制,其过程可概括为 测量(Measure)→ 布局(Layout)→ 绘制(Draw)  三个阶段,通过递归遍历 View 树实现界面渲染。以下是详细流程解析:


一、核心流程三阶段

  1. 测量(Measure)

    • 作用:确定 View 的尺寸(宽/高)。

    • 实现逻辑

      • 父容器向子 View 传递 MeasureSpec(封装测量模式和尺寸的 32 位整型值),子 View 根据自身 LayoutParams(如 match_parentwrap_content)和父容器约束计算最终尺寸123

      • 测量模式包含三种:

        • EXACTLY:精确尺寸(如固定数值或 match_parent)。
        • AT_MOST:最大限制(如 wrap_content)。
        • UNSPECIFIED:无限制(用于滚动视图等特殊场景)23
  2. 布局(Layout)

    • 作用:确定 View 在父容器中的位置(坐标)。

    • 实现逻辑

      • 父容器根据子 View 的测量结果(measuredWidth/Height)和自身布局规则(如 LinearLayout 的方向)计算子 View 的四个顶点坐标(lefttoprightbottom13
      • 此过程自顶向下递归完成,最终形成 View 树的层次结构3
  3. 绘制(Draw)

    • 作用:将 View 渲染到屏幕上。

    • 实现逻辑

      • 使用 Canvas 和 Paint 在 onDraw() 方法中绘制内容(如背景、文本、图像)。
      • 绘制顺序:背景 → 自身内容 → 子 View → 装饰(如滚动条)134
      • 硬件加速可通过 Canvas 直接操作 GPU 渲染,提升性能13

二、关键组件与机制

  1. ViewRootImpl 与流程入口

    • 绘制流程由 ViewRootImpl 的 performTraversals() 方法触发,该方法整合了测量、布局和绘制三个阶段34
    • DecorView(窗口的顶级 View)通过 ViewRootImpl 与 WindowManagerService 交互,完成界面更新3
  2. MeasureSpec 的生成规则

    • 子 View 的 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 共同决定23
    • 例如:父容器为 EXACTLY 模式时,子 View 的 match_parent 会继承父容器的剩余空间,而 wrap_content 会限制为父容器的最大可用空间2

三、优化策略

  1. 减少布局嵌套

    • 使用 ConstraintLayout 替代多层嵌套的 LinearLayout/RelativeLayout,降低测量复杂度13
  2. 延迟加载与复用

    • 通过 ViewStub 延迟加载复杂布局,减少初始渲染时间1
  3. 硬件加速与绘制缓存

    • 在 AndroidManifest 中启用硬件加速(android:hardwareAccelerated="true"),并合理使用 setDrawingCacheEnabled(true) 缓存绘制结果13
  4. 避免过度绘制

    • 使用开发者选项中的 Show GPU Overdraw 检测过度绘制区域,优化背景和透明度设置1

image.png

image.png

image.png

image.png

image.png

1、 View树的绘制流程是谁负责的?

view树的绘制流程是通过ViewRootImpl去负责绘制的,ViewRoot这个类的命名有点坑,最初看到这个名字,翻译过来是view的根节点,但是事实完全不是这样,ViewRoot其实不是View的根节点,它连view节点都算不上,它的主要作用是View树的管理者,负责将DecorView和PhoneWindow“组合”起来,而View树的根节点严格意义上来说只有DecorView;每个DecorView都有一个ViewRoot与之关联,这种关联关系是由WindowManager去进行管理的;
image.png\

2、 view的添加(创建)

image.png

view的绘制流程

* viewRootImpl.performTraversals()方法执行三大流程
image.png

(一)View的初始化

www.jianshu.com/p/003dc36af…

(二) 绘制的准备

www.jianshu.com/p/2f4e7e9e5…

(三) onMeasure

  1. 系统为什么要有measure过程?
    view控件的宽高,在Android中有三种配置方式(具体值,match_parent,wrap_content),在view嵌套的时候,父控件和子控件的不同配置组合后,产生的实际结果不同,所有需要通过不断测量来计算出最终在屏幕上显示的宽高.
  2. measure过程都干了点什么事?
    由于Android有自适应尺寸机制,这样view的尺寸不好确定,通过测量view各种尺寸经过计算后换成在屏幕中显示的像素大小

image.png

1) 上图第1列分析:
DecorView本身没有measure方法,他继承自FrameLayout,FrameLayout继承自ViewGroup,ViewGroup继承自VIew,所以实际上调用的是view的measure方法

2) 上图第2列分析:
调用的是view的measure方法后,走DecorView的onMeasure方法,从而调用FrameLayout的onMeasure方法,然后通过调用measureChildWithMargins方法,该方法中通过getChildMeasureSpec方法获取参数,然后子view调用measure方法。当然measure方法可能会调用多次,因为布局的嵌套。到这里DecorView层的measure分析完了。

3) 上图第3列分析:
第三列对应我们调用的的setContentView(R.layout.xxxlinerlayout);方法,这里面以根布局为LinerLayout为例,进行后续的measure. LinearLayout会先根据mOrientation的横向排版还是竖直排版顺序,进行两个方向的测量。因为LinearLayout这个ViewGroup实际上就是把所有的子View按照横竖两个方向进行布局,最后的子view.layout,view如果是ViewGroup还需要重复该过程

onMeasure()方法中常用的方法:

  1. getChildCount():获取子View的数量;
  2. getChildAt(i):获取第i个子控件;
  3. subView.getLayoutParams().width/height:设置或获取子控件的宽或高;
  4. measureChild(child, widthMeasureSpec, heightMeasureSpec):测量子View的宽高;
  5. child.getMeasuredHeight/width():执行完measureChild()方法后就可以通过这种方式获取子View的宽高值;
  6. getPaddingLeft/Right/Top/Bottom():获取控件的四周内边距;
  7. setMeasuredDimension(width, height):重新设置控件的宽高

www.jianshu.com/p/4f8b5c559…

(四) onLayout

  1. 系统为什么要有layout过程?
  2. layout过程都干了点什么事?
    image.png

www.jianshu.com/p/577afa53c…

  • 上图省略了addview到viewRootImpl.performTraversals()的过程

(五) onDraw

  1. 系统为什么要有draw过程?
  2. draw过程都干了点什么事?
    image.png

www.jianshu.com/p/a4fb6a02a…

(六) 硬件渲染(上)

www.jianshu.com/p/c84bfa909…

(七) 硬件渲染(下)

www.jianshu.com/p/4854d9fcc…