一、Flutter框架介绍

Flutter是Google在2018年发布的一款跨平台UI框架,使用Dart作为其开发语言,底层使用Skia图形库进行视图渲染,渲染速度和用户体验堪比原生。

二、Flutter渲染原理

Flutter中一帧的渲染可以分为三个过程:

  1. 请求渲染: 类似于React,Flutter中也是调用setState方法来通知刷新UI

    1. 调用setState方法,将需要刷新的RenderObject加入dirtyList中,
    2. 调用window对象scheduleFrame函数,scheduleFrame函数是一个native函数,Dart层只是一个函数声明,具体逻辑是在C++层实现,
    3. C++层的scheduleFrame函数会调用Animator对象进行RequestFrame,最终会通过JNI调用回到Java层,调用Android系统的Choreographer监听下一个Vsync信号。
  2. 绘制:

    1. Choreographer接受到下一个Vsync信号后,会执行之前注册回调函数,这里最终会调用到C++层Animator的DrawFrame函数,
    2. 在DrawFrame函数中会执行Dart环境中window对象的DrawFrame函数,
    3. 在window对象DrawFrame函数中调用flushLayout对RenderObjectTree进行测量和布局,然后调用flushPaint进行绘制,绘制结束后,会生成一颗LayerTree。
  3. 光栅化:

    1. Dart层绘制结束后,会调用window的render方法将LayerTree同步到C++环境中,window的render也是一个native方法,具体的实现在C++层

    2. C++层的Render方法会调用Animator对象的Render方法,在Render方法中会将LayerTree加入到LayerTreePipeLine中,这是一个典型的生产者消费者模式。

    3. Dart UI线程负责将绘制好的LayerTree加入pipeline中,GPU线程负责从pipeline中获取LayerTree进行光栅化,pipeline中最多存在2个LayerTree。

    4. CPU线程从pipeline中获取到LayerTree后,会调用Rasterizer的Draw方法进行光栅化,这里的光栅化实际上是通过递归遍历LayerTree将每一个Layer通过SKCanvas绘制到FrameBuffer

    5. FlutterActivity启动时会创建GLContext,也就是OpneGL的上下文环境,当光栅化需要创建SKCanvas时,会通过GLContext获取FBO(FrameBufferObject),然后通过FBO创建SKSurface,在通过SKSurface创建SKCanvas

    6. 当LayerTree绘制结束后,会通过SwrapBuffer方法将数据提交给显示器

Flutter渲染流程介绍

三、Paint过程

Flutter的绘制主要包括两步:第一步是在Dart遍历执行RenderObject的paint方法,完成不同RenderObejct的绘制,生成一个LayerTree。第二步是在C++层将这颗LayerTree最终渲染到屏幕。

一个PictureLayer节点

  • Dart层LayerTree的生成过程:
  1. 创建PictureLayer,添加到RootLayer,形成一个LayerTree,这里以只有两个节点的LayerTree为例
  2. 创建Dart层的Canvas对象,首先会在C++层创建SKPictureRecord,然后通过SKPictureRecord创建一个SKCanvas,Dart层的Canvas对象只是C++中SKCanvas对象的一个代理。Dart环境中在Canvas对象上所有绘制操作,都会被记录到SKPictureRecord里面
  3. 遍历RenderObjectTree,调用RenderObject对象的paint方法,不同的RenderObeject重写paint方法,通过Canvas对象,进行绘制自己
  4. 遍历结束后,调用SKPictureRecord的endRecording方法,结束记录,并且生成一个SKPicture对象,在Dart层对应一个Picture对象。SKPicture可以看一做是一帧屏幕截图,源码中SKPicture也是可以直接转成Image。
  5. 将Dart层生成的Picture对象赋值给PictureLayer,这样Dart层的LayerTree创建完成
  6. 将Dart层的LayerTree同步到C++,通过GPU线程进行光栅化处理。

Flutter渲染流程介绍

  • C++层渲染LayerTree
  1. FlutterActivity启动时,会在C++层完成GL环境的初始化和GLConetxt的创建
  2. FlutterSurfaceView的Surface创建完成后会同步到C++,通过这个Surface在C++层创建AndroidEGLSurface,作为GL的目标渲染Surface
  3. 通过AndroidEGLSurface创建SKSurface,在这个SKSurface上绘制的东西都会代理到AndroidEGLSurface
  4. 通过SKPicture创建SKCanvas,然后遍历LayerTree,通过SKCanvas绘制所有的Layer节点,完成光栅化处理,最终将光栅化后的数据刷新到手机屏幕上完成渲染。

Flutter渲染流程介绍
多个PictureLayer节点

绘制过程中真正复杂的是当界面需要重绘时,如何减少重绘范围,提高绘制性能。比如在上一节中,所有的RenderObject都绘制到了同一个PictureLayer上面,因此当RenderObjectTree中任何一个节点需要重绘时,都需要重新绘制整颗RenderObjectTree,生成新的Picture对象,绘制效率是非常低的。

Flutter中会对整个UI界面进行了图层划分,进行重绘时,可以通过图层复用,减少绘制量,提升绘制性能。

每个RenderObject都会有一个重新绘制边界(RepaintBoundary)的标记,表示该RenderObject是否需要在一个独立的图层进行绘制,比如下图,红色节点的RenderObject表示设置了RepaintBoundary标记,该RenderObject及其子节点都会绘制在一个独立的PictureLayer,整颗RenderObjectTree生成的LayerTree就会有两个PictureLayer节点,每一个PictureLayer节点都有自己独立的Canvas对象。当红色的RenderObject或者他的子节点需要进行重绘时,只需要重新绘制PictureLayer2然后在与之前生成的PictureLayer1进行合成即可。

Flutter渲染流程介绍
在Flutter提供的常用的RenderObejct中已经帮开发者设置好了哪些RenderObject需要进行标记RepaintBoundary,平常的开发过程中无需关心RepaintBoundary。当开发者需要自定义RenderObject时,可以结合具体场景判断是否需要设置RepaintBoundary提升重绘性能。