Android 绘制原理

硬件分工

在计算机硬件中, 通常 CPU 用来处理数据, GPU 用来渲染数据. Android 系统也不例外, 绘制过程首先是 CPU 准备数据, 通过 Driver 层把数据交给 GPU 渲染. 其中 CPU 主要负责 Measure 、Layout 、Record 、Execute 的数据计算工作, GPU 负责 Rasterization(栅格化)、渲染. 由于图形 API 不允许 CPU 直接与 GPU 通信, 而是通过中间的一个图形驱动层(Graphics Driver)来连接这两部分. 图形驱动维护了一个队列, CPU 把 display list 添加到队列中, GPU 从这个队列取出数据进行绘制, 最终才在显示屏上显示出来.

那么无论是 CPU 准备的数据, 还是 GPU 渲染的数据, 都是以一帧一帧的形式来的. 我们所看到的界面也是有一帧一帧的图像连续显示而来. 对于人眼来说, 每秒钟看到60帧则比较流畅了, 即 FPS(Frame Per second) 为60, 1/60 = 0.01666667, 即每 16ms 进行一次准备-渲染操作.

系统变更历史

在 Android 4.1 以前, 每一次渲染的流程可以用下图表示:

横轴表示时间, 每条 VSync 线表示 16ms:

  1. 第一个16ms中, 系统显示缓存块 A 中的第0帧, 同时 CPU 和 GPU 在缓存块 B 中准备第1帧.
  2. 第二个16ms中, 系统显示缓存块 B 中的第1帧, 但是此时 CPU 可能在处理其他事情, 所以在第二个16ms快结束时才开始在缓存块 A 中准备第2帧.
  3. 第三个16ms中, 由于第2帧还没准备好, 所以只能继续显示第1帧, 此时用户就会感知到卡顿.

在 Android 4.1 版本中, 为了解决这些问题推出了 Project Butter, 主要引入 VSync, Triple Buffer和Choreographer.

  • VSync : Vertical Synchronization, 即垂直同步, 可以理解为一种定时中断, GPU 和 CPU 在收到 VSync 信号时开始准备数据.

    引入 VSync 后的渲染流程如下:

    1. 这样每次收到 VSync 中断时, CPU 和 GPU 开始工作. 只要 CPU 和 GPU 的 FPS 略高于 Display 的 FPS, 则每次都能在 16ms 之内准备好下一帧, 能够顺利显示.
    2. 如果 CPU 和 GPU 已经准备完毕, 只要没有收到 VSync 信号, 都不会进行渲染工作.
  • Triple Buffer: 即3个缓存块. 在 Android 4.1 以前, 只有2个缓存块用于准备数据, 两个缓存块交替使用. 在引入 VSync 后, 如果 CPU 和 GPU 的 FPS 比 Display 的 FPS 低, 即不能在 16ms 内准备好数据, 会导致很严重的掉帧效果.

    1. 在第一个16ms中, 系统显示缓存块A, CPU 和 GPU 在缓存块 B 中准备第1帧.
    2. 在第二个16ms中, 由于 GPU 还没有准备好, 所以只能继续显示缓存块 A 的内容, 用户感知到卡顿.

    为了解决这个问题, Android 4.1 引入第三个缓存块:

    1. 在第一个16ms中, 系统显示缓存块A, CPU 和 GPU 在缓存块 B 中准备第1帧.

    2. 在第二个16ms中, 由于 GPU 还没有准备好, 所以只能继续显示缓存块 A 的内容, 用户感知到卡顿. 但此时 CPU 可以在缓存块 C 中准备数据.

    3. 正常显示不会再丢帧.

      但是缓存块并不是越多越好, CPU 和 GPU 准备的数据最好在下一个 16ms 显示, 但是 Triple Buffer 中 CPU 准备的缓存块C, 在第四个 16ms 中才显示, 滞后了 16ms. 所以第三个缓存块主要是备用, 一般来说两个缓存块就够了.

  • Choreographer: 译为舞蹈编排, 起到调度作用, 收到 VSync 信号时调用用户设置的回调函数, 回调类型有三种:

    • CALLBACK_INPUT:优先级最高,和输入事件处理有关。
    • CALLBACK_ANIMATION:优先级其次,和Animation的处理有关。
    • CALLBACK_TRAVERSAL:优先级最低,和UI等控件绘制有关。

invalidate() 调用顺序

  • View.invalidate()
  • View.invalidateInternal() -> 找到 dirty 区域并传给 ViewParent
  • ViewParent.invalidateChild -> 接口, 由 ViewGroup 和 ViewRootImpl 实现
  • ViewGroup.invalidateChild -> 向上递归调用 ViewParent.invalidateChildInParent, 直到 ViewRootImpl
  • ViewRootImpl.invalidateChildInParent -> ViewRootImpl.scheduleTraversals
  • ViewRootImpl.scheduleTraversals -> Choreographer.postCallback(Choreographer.TRAVERSAL)
  • Choreographer 调度到下一个 VSYNC 信号来时再回调. ViewRootImpl.doTraversal
  • ViewRootImpl.doTraversal -> ViewRootImpl.performTraversals 开始绘制