渲染流程
在 Flutter 中,渲染流程主要分为三个阶段:布局(layout)、绘制(painting)和合成(compositing)
布局阶段包括了创建 widget tree、element tree 和 render object tree 三个步骤。Widget 是 Flutter UI 的基本元素,而 Element 是 widget 的实例,Render Object 是 Element 的对应渲染对象。在这个阶段,Flutter 会将 Widget Tree 转换为 Element Tree,然后再转换为 RenderObject Tree,同时对 RenderObject Tree 进行布局,计算每个 RenderObject 的大小和位置。这些计算结果被存储在每个 RenderObject 中,供后面的绘制和合成使用。
绘制阶段是将布局阶段计算得到的 RenderObject Tree 转换为一组绘制指令,然后将这些指令发送到 GPU 上进行绘制。Flutter 通过将这些绘制指令存储在 Layer Tree 中来实现高效的渲染。Layer Tree 包含了一个由多个 Layer 组成的层次结构,每个 Layer 对应一个独立的绘制指令集合。Flutter 可以根据需要进行优化,比如将多个 Layer 合并成一个更大的 Layer 来减少 GPU 上下文切换的次数,从而提高渲染性能。
合成阶段是将绘制阶段生成的多个 Layer 合并成一个单一的 Layer,然后在 GPU 上显示这个 Layer。Flutter 使用栅格化(rasterization)技术将图形对象转换为像素,并在屏幕上进行显示。
Flutter 的渲染流程具有很高的效率和灵活性。通过将渲染指令存储在 Layer Tree 中,Flutter 可以减少 GPU 上下文切换的次数,并使用栅格化技术将图形对象转换为像素,从而实现高效的渲染。
布局阶段
在Flutter中,构建用户界面是通过widget tree、element tree和render object tree相互配合完成的。它们之间的关系和作用如下:
- Widget tree:是Flutter中一个widget层级结构树,描述了应用程序的用户界面,是Flutter框架的一个基础概念。widget tree由各种widget组成,它们嵌套在一起,形成了应用程序的界面布局。widget是不可变的,即每个widget都是一个完整的描述,当一个widget需要改变时,Flutter会创建一个新的widget实例来替代旧的widget实例。widget tree的构建是在UI Thread(主线程)中完成的。
- Element tree:是widget tree的运行时表示形式,即widget在构建过程中的实例化对象。Element tree中的每个element与widget tree中的每个widget相对应,element是可变的,即element的属性和状态可以在widget生命周期内发生改变。element的作用是管理widget的状态、生命周期和渲染。element tree的构建是在UI Thread中完成的。
- Render object tree:是最终用于绘制widget的树形结构。render object tree与widget tree和element tree是相互独立的。在widget tree的每个widget中,都有一个对应的RenderObject子类对象。这些RenderObject对象一起形成了RenderObject tree,描述了如何绘制widget。每个RenderObject对象都负责自己的渲染和布局。RenderObject tree的构建是在GPU Thread中完成的。
注意事项:
- Widget tree中的widget是
不可变的,每次更新都会生成新的widget实例,这可能会对应用程序的性能产生影响。 - Widget tree和Element tree的构建是在UI Thread中完成的,
如果构建过程太过复杂或耗时,则可能会导致UI卡顿,影响用户体验。 - Render object tree的构建是在GPU Thread中完成的,因此渲染不会影响UI的响应性能,但是一些耗时的渲染可能会导致帧率下降,从而影响用户体验。
- 对于StatefulWidget中的状态变化,Flutter会通过Element tree来执行对应的更新。
当状态变化时,Flutter会比较新旧widget tree的差异,并将这些差异应用于Element tree,从而更新界面。因此,Element tree是一个重要的概念,需要开发者充分理解。 - RenderObject tree通常是
由Flutter框架自动构建的,开发者不需要直接操作。但在一些特殊场景下,如自定义渲染对象或自定义布局,需要开发者手动构建RenderObject tree。
绘制阶段
在Flutter中,绘制(painting)是指将widget渲染成屏幕上的像素点的过程。这个过程由渲染引擎来完成,其中的核心就是在render object tree上执行绘制操作。
- 绘制过程
绘制的过程是指将widget树中每个可见的render object转换为屏幕上的像素点。绘制的流程如下:
- 在Flutter的UI线程中创建一个Layer,称为RenderLayer。
- 在UI线程中调用RenderView的paint方法,在paint方法中触发render object tree的遍历,根据需要绘制的render object创建一个新的Layer,称为PaintLayer。
- 在GPU线程中,将PaintLayer中的像素点按照一定的顺序进行绘制,最终绘制到屏幕上。
- 绘制的优化
绘制是一个比较消耗资源的操作,因此Flutter中提供了多种方式来优化绘制过程,减少不必要的开销:
- 脏矩形优化:只有发生改变的部分才会被重绘。
- 剪裁优化:只绘制需要的部分,减少无效绘制。
- 合并图层:将多个图层合并为一个,减少绘制开销。
- 图层缓存:缓存渲染结果,避免重复绘制。
- 注意事项
避免不必要的重绘:如果在widget的build方法中对widget进行了修改,则会触发widget的重建。如果没有必要,应尽量避免这种操作。使用shouldRepaint方法优化:通过shouldRepaint方法告诉Flutter是否需要重绘widget,可以避免不必要的重绘操作。避免过于复杂的布局:复杂的布局可能会导致过多的绘制开销,从而降低应用的性能。应尽量避免过于复杂的布局。优化图片资源:图片资源占用内存较大,如果使用不当可能会导致应用运行缓慢。可以使用图片压缩、懒加载等技术来优化图片资源。避免频繁的动画效果:动画效果会引起频繁的绘制操作,因此应尽量避免频繁的动画效果。如果需要实现动画效果,可以使用Flutter提供的动画框架来实现。
合成阶段
在 Flutter 中,渲染合成(compositing)是将多个层(layers)合并成一个屏幕图像的过程。这些层可能包括用于绘制UI的Widget层、渲染对象(Render Object)层以及其他图形资源层等。Flutter使用了基于OpenGL的底层图形库来进行图像合成操作。
1.层的顺序很重要:每个层按特定顺序合成到最终屏幕图像中。例如,覆盖在其他层上的层将优先于在其下面的层进行合成。
2.层可以裁剪:层可以通过定义裁剪矩形(clip rect)来指定可见区域。裁剪后的层将只显示其裁剪矩形内的内容,而忽略其余部分。
3.层可以混合:通过指定不同的混合模式(blend mode),层可以与其它层混合。混合模式可以影响层的不透明度和颜色,从而实现各种视觉效果。
4.层可以透明:通过指定不同的不透明度(opacity),可以使层在不完全覆盖下面层时呈现出半透明的效果。
5.层可以缓存:使用Offstage、Opacity、IgnorePointer等控件,可以将一个widget subtree作为单个图层进行缓存。这可以提高性能,并减少屏幕重新绘制的次数。
6.合成不是免费的:合成操作是昂贵的操作,因为它涉及到大量的图像处理和内存复制。因此,应该尽量减少图层的数量和复杂度,以避免不必要的性能损失。
7.使用自定义Painter:如果需要实现高度自定义的绘图,可以使用自定义Painter。自定义Painter是一个类,它定义了绘制操作以及如何响应布局和触摸事件等操作。使用自定义Painter可以实现复杂的图形效果,并与Flutter的渲染和合成流程无缝协作。
总结
- Widget Tree 的构建和更新:在 Flutter 中,Widget Tree 描述了 UI 的逻辑结构,每个 Widget 代表一个 UI 元素,Widget Tree 以根 Widget 作为入口,通过一系列嵌套的子 Widget 形成一棵树形结构。每次更新 UI 时,Flutter 会重新构建整个 Widget Tree。
- Element Tree 的创建和更新:在 Widget Tree 中,每个 Widget 都对应一个 Element 对象,用来保存 Widget 的状态和上下文信息,如父 Widget、是否需要重绘等,同时 Element 中也包含一个 RenderObject 对象。
- Render Object Tree 的创建和更新:Render Object 是对 Widget 进行绘制的基本单位,在 Element 中会创建和保存对应的 RenderObject,即 Render Object Tree,这棵树的结构与 Widget Tree 相似,但是每个节点对应的不再是 Widget,而是对应的 RenderObject。
- Layout:当 Render Object Tree 产生变化时,Flutter 会触发 Layout 过程,即计算 Widget 在屏幕上的位置和大小,同时 RenderObject 也会根据这些信息对自己进行布局。
- Painting:Painting 是将 Widget 转化为实际的像素,即在屏幕上绘制出来。在这一过程中,Flutter 会按照一定的顺序对 RenderObject 进行遍历,根据 RenderObject 的属性进行绘制。
- Compositing:在所有 RenderObject 绘制完成后,Flutter 会对 RenderObject Tree 进行 Compositing,即将不同 RenderObject 的像素合成为最终的图像。