Flutte3.0 遥遥领先系列|一文教你完全掌握flutter渲染核心(3颗树)

1. Flutter渲染3颗树和源码分析

把视图数据的组织和渲染抽象为三部分,即 Widget,Element 和 RenderObject。
那么,Widget到底是什么呢?

Widget是Flutter功能的抽象描述,是视图的配置信息,同样也是数据的映射,是Flutter开发框架中最基本的概念。前端框架中常见的名词,比如视图(View)、视图控制器(View Controller)、活动(Activity)、应用(Application)、布局(Layout)等,在Flutter中都是Widget。

事实上,Flutter的核心设计思想便是“一切皆Widget”

1.1 Widget (描述 UI 渲染的配置信息)

Widget是控件实现的基本逻辑单位,里面存储的是有关视图渲染的配置信息,包括布局、渲染属性、事件响应信息等。

Widget总结: 我们开发最直接的,

绝大多数情况下,我们只需要了解各种Widget特性及使用方法,而无需关心Element及RenderObject!

Widget源码分析:

@immutable
abstract class Widget extends DiagnosticableTree {
  /// Initializes [key] for subclasses.
  const Widget({ this.key });
    
  ///  * The discussions at [Key] and [GlobalKey].
  final Key? key;

  @protected
  @factory
  Element createElement();

  static bool canUpdate(Widget oldWidget, Widget newWidget) { // canupdate方法
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
  @protected
  Widget build(BuildContext context);

那一般我们可以怎么获取布局的大小和位置呢?
Widget大概可以分为三类组合类(紫色标识)、代理类(红色标识)、绘制类(黄色标识)

1). 组合类: Container和Text就是组合类的Widget
2). 代理类: 状态管理的,例如:InheritedWidget用于将一些状态信息传递给子孙Widget。
3). 渲染类: 所有我们在屏幕上看到的UI最终几乎都会通过绘制类的WidgetRenderObjectWidget实现。RenderObjectWidget中有个createRenderObject()方法生成实际渲染的RenderObject对象!

LeafRenderObjectWidget 用于只有渲染功能,无子节点 (如Switch、Radio等)
SingleChildRenderObjectWidget 只含有一个子节点 (如SizedBox、Padding等)
MultiChildRenderObjectWidget 含有多个子节点(如Column、Stack等)
RenderObjectWidget介绍
RenderObjectWidget是一个抽象类。我们通过源码可以看到,这个类中同时拥有创建Element、RenderObject,以及更新RenderObject的方法
RenderObjectWidget本身并不负责这些对象的创建与更新。

1.2 Element (存放上下文,持有 Widget 和 RenderObject)

Element是Widget的一个实例化对象,它承载了视图构建的上下文数据,是连接结构化的配置信息到完成最终渲染的桥梁。
Element同时持有Widget和RenderObject。而无论是Widget还是Element,其实都不负责最后的渲染,只负责发号施令,真正去干活儿的只有RenderObject。那你可能会问,既然都是发号施令,那为什么需要增加中间的这层Element树呢?直接由Widget命令RenderObject去干活儿不好吗?
答案是,可以,但这样做会极大地增加渲染带来的性能损耗。

总结: 中间层, 梳理差异, 提高渲染效率 (diff),

Widget 和 Element 之间是一对多的关系 。

问题:Element是如何创建的?

创建出来后会由framework调用mount方法;

问题: Element会重新创建么?

在 newWidget 与oldWidget的 runtimeType 和 key 相等时会选择使用 newWidget 去更新已经存在的 Element 对象,不然就选择重新创建新的 Element。
Element 持有 RenderObject 和 Widget! Element 是 Widget 和 RenderObject 的粘合剂
Element分为3种, ComponentElement和RenderObjectElement,ProxyElement, 前者负责组合子Element,后者负责渲染, 最后的是代理类
如图:

1). 组合类: ComponentElement主要的子类:
2). 渲染类: RenderObjectElement的主要子类:SingleChildRenderObjectElement、MultiChildRenderObjectElement、RenderObjectToWidgetElement。SingleChildRenderObjectElement
如图: 组合类生成渲染类, 但是渲染类也可以生产出组合类!
而我们也知道 BuildContext 的实现其实是 Element, 所以本质上BuildContext就是当前的Element

 abstract class Element extends DiagnosticableTree implements BuildContext {
 
RenderObject? get renderObject {
    Element? current = this;
    while (current != null) {
      if (current._lifecycleState == _ElementLifecycle.defunct) {
        break;
      } else if (current is RenderObjectElement) {
        return current.renderObject;
      } else {
        Element? next;
        current.visitChildren((Element child) {
          assert(next == null);  // This verifies that there's only one child.
          next = child;
        });
        current = next;
      }
    }
    return null;
  }
    @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    assert(() {
      _debugDoingBuild = true;
      return true;
    }());
    _renderObject = (widget as RenderObjectWidget).createRenderObject(this);
    assert(!_renderObject!.debugDisposed!);
    assert(() {
      _debugDoingBuild = false;
      return true;
    }());
    assert(() {
      _debugUpdateRenderObjectOwner();
      return true;
    }());
    assert(_slot == newSlot);
    attachRenderObject(newSlot);
    super.performRebuild(); // clears the "dirty" flag
  }
     
      @override
  void attachRenderObject(Object? newSlot) {
    assert(_ancestorRenderObjectElement == null);
    _slot = newSlot;
    _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
    _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
    final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
    if (parentDataElement != null) {
      _updateParentData(parentDataElement.widget as ParentDataWidget<ParentData>);
    }
  }

1.2.1 组合类ComponentElement的核心流程和核心方法分析:
1.2.2 渲染类RenderObjectElement 的核心流程和核心方法分析:
1.3.1 重点方法Element.inflateWidget() 重点方法
1.3.2重点方法 Element.mount()
1.3.3 重点方法 RenderObjectElement.performRebuild()
1.3.4 RenderObjectElement.update()
1.3.5 RenderObjectElement.updateChildren()
整个创建, 更新的图片如下:

void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _renderObject = widget.createRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;
}

@override
void performRebuild() {
//更新renderObject
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}

1.3 RenderObject (实际渲染树中的对象)
问题: RenderObject是如何创建的?
@override
  void mount(Element parent, dynamic newSlot) { // 调用 mount方法! 
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this);  // 通过widget,创建RenderObject
    assert(() {
      _debugUpdateRenderObjectOwner();
      returntrue;
    }());
    assert(_slot == newSlot);
    attachRenderObject(newSlot);
    _dirty = false;
  }

如果你去看类似于Text这种组合类的Widget,它也会执行mount方法,但是mount方法中并没有调用createRenderObject这样的方法。
RenderObject才是真正负责绘制的对象,其中包含了paint,layout等方法~
布局和绘制在RenderObject中完成,Skia: 负责合成和渲染!
更新和变化的流程: canupdate方法
如果Widget的配置数据发生了改变,那么持有该Widget的Element节点也会被标记为dirty。
在下一个周期的绘制时,Flutter就会触发Element树的更新,并使用最新的Widget数据更新自身以及关联的RenderObject对象,
接下来便会进入Layout和Paint的流程。而真正的绘制和布局过程,则完全交由RenderObject完成:
布局和绘制完成后,接下来的事情就交给Skia了。在VSync信号同步时直接从渲染树合成Bitmap,然后提交给GPU

  @protected
  @pragma('vm:prefer-inline')
  Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
    if (newWidget == null) {
      if (child != null) {
        deactivateChild(child);
      }
      return null;
    }

    final Element newChild;
    if (child != null) {
      bool hasSameSuperclass = true;
      assert(() {
        final int oldElementClass = Element._debugConcreteSubtype(child);
        final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
        hasSameSuperclass = oldElementClass == newWidgetClass;
        return true;
      }());
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot) {
          updateSlotForChild(child, newSlot);
        }
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot) {
          updateSlotForChild(child, newSlot);
        }
        final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(newWidget);
        if (isTimelineTracked) {
          Map<String, String>? debugTimelineArguments;
          assert(() {
            if (kDebugMode && debugEnhanceBuildTimelineArguments) {
              debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
            }
            return true;
          }());
          FlutterTimeline.startSync(
            '${newWidget.runtimeType}',
            arguments: debugTimelineArguments,
          );
        }
        child.update(newWidget);
        if (isTimelineTracked) {
          FlutterTimeline.finishSync();
        }
        assert(child.widget == newWidget);
        assert(() {
          child.owner!._debugElementWasRebuilt(child);
          return true;
        }());
        newChild = child;
      } else {
        deactivateChild(child);
        assert(child._parent == null);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }

    assert(() {
      if (child != null) {
        _debugRemoveGlobalKeyReservation(child);
      }
      final Key? key = newWidget.key;
      if (key is GlobalKey) {
        assert(owner != null);
        owner!._debugReserveGlobalKeyFor(this, newChild, key);
      }
      return true;
    }());

    return newChild;
  }

abstract class Widget extends DiagnosticableTree {

  final Key? key;

  @protected
  @factory
  Element createElement();

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
  ...
}

canupdate()方法:
canUpdate(…)是一个静态方法,它主要用于在 widget 树重新build时复用旧的 widget ,其实具体来说,应该是:是否用新的 widget 对象去更新旧UI树上所对应的Element对象的配置;通过其源码我们可以看到,只要newWidget与oldWidget的runtimeType和key同时相等时就会用new widget去更新Element对象的配置,否则就会创建新的Element。
Key: 这个key属性类似于 React/Vue 中的key,主要的作用是决定是否在下一次build时复用旧的 widget ,决定的条件在canUpdate()方法中
布局,从RenderBox开始,对RenderObject Tree从上至下进行布局。

abstract class RenderBox extends RenderObject {
  @override
  void setupParentData(covariant RenderObject child) {
    if (child.parentData is! BoxParentData) {
      child.parentData = BoxParentData();
    }
  }

2. Flutter渲染3颗树案例分析

从一个demo分析出Widget,Element 和 RenderObject 3者的关系:

  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      child: Column(
        children: <Widget>[
          SizedBox(height: 300,width: 100,),
          Text("dd"),
          Text("aa"),
          Text("bb"),
        ],
      ),);
  }

2.1代码结构对应的widget

组合widget: 蓝色
渲染widget: 黄色

class Container extends StatelessWidget {  
    
Widget build(BuildContext context) {                                          
  Widget? current = child;                                                    
                                                                                                                               
  if (decoration != null) {                                                   
    current = DecoratedBox(decoration: decoration!, child: current);          
  }                                                                           

2.2 Element:

2.3RenderObject :

举例: container是如何对应RenderDecoratedBox!
Container具体的渲染widget是DecoratedBox! DecoratedBox里面创建RenderObject

class Container extends StatelessWidget {  
     Widget build(BuildContext context) {                                                                                       
     current = DecoratedBox(decoration: decoration!, child: current);          
   }                                                                           
    
class DecoratedBox extends SingleChildRenderObjectWidget {  
    
@override                                                           
RenderDecoratedBox createRenderObject(BuildContext context) {       
  return RenderDecoratedBox(                                        
    decoration: decoration,                                         
    position: position,                                             
    configuration: createLocalImageConfiguration(context),          
  );                                                                
 }       
}

完整的图片过程: 写的widet—>实际widget—>Element----->RenderObject

jietu-1705565019958.jpg

3颗树总结: 三者的关系:
Flutter渲染过程,可以分为这么三步:

首先,通过Widget树生成对应的Element树;
然后,创建相应的RenderObject并关联到Element.renderObject属性上;
最后,构建成RenderObject树,以完成最终的渲染。

可以大致总结出三者的关系是:配置文件 Widget 生成了 Element,

而后创建 RenderObject 关联到 Element 的内部 renderObject 对象上,
最后Flutter 通过 RenderObject 数据来布局和绘制。
理论上你也可以认为 RenderObject 是最终给 Flutter 的渲染数据,它保存了大小和位置等信息,Flutter 通过它去绘制出画面。

Widget的渲染原理

所有的Widget都会创建一个或者多个Element对象 并不是所有的Widget都会被独立渲染!只有继承RenderObjectWidget的才会创建RenderObject对象!(Container就不会创建RenderObject、column和padding这些可以创建RenderObject)
在Flutter渲染的流程中,有三颗重要的树!Flutter引擎是针对Render树进行渲染!

Widget树、Element树、Render树

每一个Widget都会创建一个Element对象 隐式调用createElement方法。Element加入Element树中,它会创建RenderElement、ComponentElement(又分为StatefulElement和StatelessElement)。RenderElement主要是创建RenderObject对象, 继承RenderObjectWidget的Widget会创建RenderElement

创建RanderElement

Flutter会调用mount方法,调用createRanderObject方法 StatefulElement继承ComponentElement,StatefulWidget会创建StatefulElement 调用createState方法,创建State 将Widget赋值给state 调用state的build方法 并且将自己(Element)传出去,build里面的context 就是Widget的Element !
StatelessElement继承ComponentElement,StatelessWidget会创建StatelessElement mount方法 -> firstBuild -> rebuild -> performBuild -> build -> _widget.build -主要就是调用build方法 并且将自己(Element)传出去

问题:
1. 已知widget, 如何找到对应的renderObject?

先找到对应的渲染的widget, 然后在里面找renderObject!
renderObject创建是在渲染的widget里面创建的, 而不是Element创建的!

2. Widget会重新创建么?Element会重新创建么?RenderObject 会重新创建么?

Widget 重新创建,Element 树和 RenderObject 树并不会完全重新创建。
Element什么时候创建?
在每一次创建Widget的时候,会创建一个对应的Element,然后将该元素插入树中

3. flutter的屏幕渲染原理Vsync机制

在计算机系统中,图像的显示需要 CPU、GPU 和显示器一起配合完成:CPU 负责图像数据计算,GPU 负责图像数据渲染,而显示器则负责最终图像显示。

CPU 把计算好的、需要显示的内容交给 GPU,由 GPU 完成渲染后放入帧缓冲区,随后视频控制器根据垂直同步信号(VSync)以每秒 60 次的速度,从帧缓冲区读取帧数据交由显示器完成图像显示。

屏幕渲染原理:

问题: 原生渲染和flutter渲染的共同点和差异是啥?

底层是用skia, 其他封装调用不一样!

问题: Skia是什么?

要想了解Flutter,你必须先了解它的底层图像渲染引擎Skia。

因为,Flutter只关心如何向GPU提供视图数据,而Skia就是它向GPU提供视图数据的好帮手。

Skia是一款用C++开发的、性能彪悍的2D图像绘制引擎,其前身是一个向量绘图软件。2005年被Google公司收购后,因为其出色的绘制表现被广泛应用在Chrome和Android等核心产品上。Skia在图形转换、文字渲染、位图渲染方面都表现卓越,并提供了开发者友好的API。

因此,架构于Skia之上的Flutter,也因此拥有了彻底的跨平台渲染能力。通过与Skia的深度定制及优化,Flutter可以最大限度地抹平平台差异,提高渲染效率与性能。

底层渲染能力统一了,上层开发接口和功能体验也就随即统一了,开发者再也不用操心平台相关的渲染特性了。也就是说,Skia保证了同一套代码调用在Android和iOS平台上的渲染效果是完全一致的。

问题: flutter新引擎impller与skia的区别

字节跳动: 深入解析Flutter下一代渲染引擎Impeller (ok)

渲染的源码分析:

请求 Vsync 信号

SchedulerBinding:

mixin SchedulerBinding on BindingBase {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;

    if (!kReleaseMode) {
      addTimingsCallback((List<FrameTiming> timings) {
        timings.forEach(_profileFramePostEvent);
      });
    }
  }

  /// dart
  /// 调用 C++ 到 Native 层,请求 Vsync 信号
   void scheduleFrame() {
    if (_hasScheduledFrame || !framesEnabled) {
      return;
    }
    assert(() {
      if (debugPrintScheduleFrameStacks) {
        debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
      }
      return true;
    }());
    ensureFrameCallbacksRegistered();
    platformDispatcher.scheduleFrame();
    _hasScheduledFrame = true;
  }

  void ensureFrameCallbacksRegistered() {
    platformDispatcher.onBeginFrame ??= _handleBeginFrame;
    platformDispatcher.onDrawFrame ??= _handleDrawFrame;
  }

void scheduleFrame() native 'Window_scheduleFrame'; // c++方法

  void _handleBeginFrame(Duration rawTimeStamp) {
    if (_warmUpFrame) {
      // "begin frame" and "draw frame" must strictly alternate. Therefore
      // _rescheduleAfterWarmUpFrame cannot possibly be true here as it is
      // reset by _handleDrawFrame.
      assert(!_rescheduleAfterWarmUpFrame);
      _rescheduleAfterWarmUpFrame = true;
      return;
    }
    handleBeginFrame(rawTimeStamp);
  }

  void _handleDrawFrame() {
    if (_rescheduleAfterWarmUpFrame) {
      _rescheduleAfterWarmUpFrame = false;
      // Reschedule in a post-frame callback to allow the draw-frame phase of
      // the warm-up frame to finish.
      addPostFrameCallback((Duration timeStamp) {
        // Force an engine frame.
        //
        // We need to reset _hasScheduledFrame here because we cancelled the
        // original engine frame, and therefore did not run handleBeginFrame
        // who is responsible for resetting it. So if a frame callback set this
        // to true in the "begin frame" part of the warm-up frame, it will
        // still be true here and cause us to skip scheduling an engine frame.
        _hasScheduledFrame = false;
        scheduleFrame();
      });
      return;
    }
    handleDrawFrame();
  }

以安卓为例,最终会执行到 JNI_OnLoad 注册的 Java 接口 AsyncWaitForVsyncDelegate.asyncWaitForVsync,这个接口在 Flutter 启动时初始化。实现内容如下
Flutter引擎启动时,向系统的Choreographer实例注册接收Vsync的回调函数,GPU硬件发出Vsync后,系统会触发该回调函数,并驱动UI线程进行layout和绘制。

new FlutterJNI.AsyncWaitForVsyncDelegate() {
        @Override
        public void asyncWaitForVsync(long cookie) {
          Choreographer.getInstance()
              .postFrameCallback(
                  new Choreographer.FrameCallback() {
                    @Override
                    public void doFrame(long frameTimeNanos) {
                      float fps = windowManager.getDefaultDisplay().getRefreshRate();
                      long refreshPeriodNanos = (long) (1000000000.0 / fps);
                      FlutterJNI.nativeOnVsync(
                          frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);
                    }
                  });
        }
      }


Choreographer.getInstance().postFrameCallback 用于监听系统垂直同步信号,在下一个垂直信号来临时回调 doFrame,通过 FlutterJNI.nativeOnVsync 走到 c++ 中。经过复杂的链路,将下面的任务添加到到 UI Task Runner 中的事件队列中:

lib/ui/window/platform_configuration.cc

void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime) {
  .................
  // 调用 dart 中的 _window.onBeginFrame
  tonic::LogIfError(
      tonic::DartInvoke(begin_frame_.Get(), {Dart_NewInteger(microseconds),}));
  // 执行 microTask                                          
  UIDartState::Current()->FlushMicrotasksNow();
  // 调用 dart 中的 _window.onDrawFrame
  tonic::LogIfError(tonic::DartInvokeVoid(draw_frame_.Get()));
}


当接收到Vsync时,会调用到RendererBinding的drawFrame方法;

[源码路径:flutter/lib/src/rendering/binding.dart]
  @protected
  void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();    // 触发RenderObject执行布局
    pipelineOwner.flushCompositingBits();    //绘制之前的预处理操作
    pipelineOwner.flushPaint();    // 触发RenderObject执行绘制
    renderView.compositeFrame(); // 将需要绘制的比特数据发给GPU
    pipelineOwner.flushSemantics(); // 发送语义化给系统,用于辅助功能等
  }

Window 正是 Flutter Framework 连接宿主操作系统的接口

class Window {
    
  // 当前设备的DPI,即一个逻辑像素显示多少物理像素,数字越大,显示效果就越精细保真。
  // DPI是设备屏幕的固件属性,如Nexus 6的屏幕DPI为3.5 
  double get devicePixelRatio => _devicePixelRatio;
  
  // Flutter UI绘制区域的大小
  Size get physicalSize => _physicalSize;

  // 当前系统默认的语言Locale
  Locale get locale;
    
  // 当前系统字体缩放比例。  
  double get textScaleFactor => _textScaleFactor;  
    
  // 当绘制区域大小改变回调
  VoidCallback get onMetricsChanged => _onMetricsChanged;  
  // Locale发生变化回调
  VoidCallback get onLocaleChanged => _onLocaleChanged;
  // 系统字体缩放变化回调
  VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
  // 绘制前回调,一般会受显示器的垂直同步信号VSync驱动,当屏幕刷新时就会被调用
  FrameCallback get onBeginFrame => _onBeginFrame;

runApp这个方法总的来说就是做了以下的事情

在Flutter的framework层和engine层建立一个连接WidgetsFlutterBinding,注册Vsync回调后,每一帧调用的时候都会触发WidgetsFlutterBinding里面的方法,从而去调用framework层的处理逻辑

为传入的widget构建节点树,将节点树中的RenderObjct树的结果交给enginee层的SingletonFlutterWindow,然后通知到GPU进行渲染

总结:

1)、scheduleFrame 回调 C++ 注册 Vsync 信号

2)、Vsnc 来临,回调 C++ 调用 window.onBeginFrame,这个方法映射到 dart 中的 handleBeginFrame(), window.onDrawFrame

对应 dart 中的 drawFrame()

3)、drawFrame 中,build 阶段根据 widget 生成 element 和 renderObject 树;layout 测量绘制元素的大小和位置;paint 阶段生成 layer 树;

4). 然后Framework层通过Window的render方法回到了Engine层,Engine再向GPU线程提交绘制任务;最终渲染出来

dart请求绘制---->jni----->dart
总结:整个流程由 Dart 发起,通过 C++ 回调到 Native 层,注册一次垂直同步信号的监听。等到信号来到,再通知 Dart 进行渲染。可以看出,Flutter 上的渲染,是先由 Dart 侧主动发起,而不是被动等待垂直信号的通知。这可以解释,比如一些静态页面时,整个屏幕不会多次渲染。并且由于是 Native 层的垂直同步信号,所以也完全适配高刷的设备

上面整个流程下来就是每一次Vsync信号,在Dart层所做的处理。

核心就是执行drawFrame方法。而drawFrame方法主要有三个步骤:
drawFrame() 总结:
1).遍历需要layout的RenderObject,让它执行performLayout方法;整个调用栈是:RenderView.performLayout->child.layout->child.performLayout->child.layout->…;
2). 遍历需要paint的RenderObject,让它执行paint方法;整个调用栈是:RenderView.paint->PaintingContext.paintChild->child.paint->PaintingContext.paintChild->…;
3). 通过PaintingContext.canvas可以把RenderObject绘制的内容绘制到PaintingContext._currentLayer上,最终构造出Scene实例,通过Window.render方法把Scene发送给Engine层,最终由Engine将内容渲染在设备屏幕上。
————————————————

渲染管道7个步骤:

flutter完整的渲染管道涉及到很多步骤:

1). 准备前3个子步骤: 首先得到用户的输入,例如触摸屏幕事件,一些动画可能会随之产生,然后开始构建组件并去渲染它们;
Build:还记得一开始 setState() 将 element 加入了脏集合么?这个阶段,Flutter 会通过 widget 更新所有脏集合中的节点(需要更新)中的 element 与 RenderObject 树。
2). 渲染可以细分为3个子步骤;
2.1. Layout(布局),它的作用是在屏幕上确定每个组件的大小和位置;
2.2. Paint(绘制),它提供一系列方法把组件渲染成用户看到的样子;
2.3. Composite(图层合成)它把绘制步骤生成的图层或者纹理堆叠在一起,按照顺序组织它们,以便它们可以高效的在屏幕上进行呈现,图层合成是组件最终呈现在屏幕上之前很关键的也是最后的一个优化步骤;
3). 最后是光栅化,它把抽象的表现映射成物理像素显示在屏幕上。Paint 阶段会触发 RenderObject 对象绘制,生成第四棵树:Layer Tree,最终合成光栅化后完成渲染。

合成

所有的图层根据大小、层级、透明度等规则计算出最终的显示效果,将相同的图层归类合并,简化渲染树,提高渲染效率

渲染

合并完成后,Flutter会将几何图层数据交由Skia引擎加工成二维图像数据,最终交由GPU进行渲染,完成界面的展示

  • Build:还记得一开始 setState() 将 element 加入了脏集合么

  • Layout:RenderObject 树进行布局测量,用于确定每一个展示元素的大小和位置。

  • Paint:Paint 阶段会触发 RenderObject 对象绘制,生成第四棵树:Layer Tree,最终合成光栅化后完成渲染。

最详细的: dart–>native ----> 屏幕显示的完整过程

1). Flutter 引擎启动时,向系统的 Choreographer 实例注册接收 Vsync 的回调。
2). 平台发出 Vsync 信号后,上一步注册的回调被调用,一系列调用后,执行到 VsyncWaiter::fireCallback。

  1. .VsyncWaiter::fireCallback实际上会执行Animator类的成员函数BeginFrame。
    4). BeginFrame 经过一系列调用执行到 Window 的 BeginFrame,Window 实例是连接底层 Engine 和 Dart framework 的重要桥梁,基本上所以跟平台相关的操作都会由 Window 实例来串联,包括事件,渲染,无障碍等。
    5). 通过 Window 的 BeginFrame 调用到 Dart Framework的RenderBinding 类,其有一个方法叫 drawFrame ,这个方法会去驱动 UI 上的 dirty 节点进行重排和绘制,如果遇到图片的显示,会丢到 IO 线程以及去 worker 线程去执行图片加载和解码,解码完成后,再次丢到 IO 线程去生成图片纹理,由于 IO 线程和 GPU 线程是 share GL context 的,所以在 IO 线程生成的图片纹理在 GPU 线程可以直接被 GPU 所处理和显示。
    6). Dart 层绘制所产生的绘制指令以及相关的渲染属性配置都会存储在 LayerTree 中,通过 Animator::RenderFrame 把 LayerTree 提交到 GPU 线程,GPU 线程拿到 LayerTree 后,进行光栅化并做上屏操作(关于LayerTree我们后面会详细讲解)。之后通过 Animator::RequestFrame 请求接收系统下一次的Vsync信号,这样又会从第1步开始,循环往复,驱动 UI 界面不断的更新。

4.渲染三部曲:

RenderObject 绘制3部曲

4.1 Layout:
measure : 没有看到测量, 这个和layout在一起

Flutter采用深度优先机制遍历渲染对象树,决定渲染对象树中各渲染对象在屏幕上的位置和尺寸。在布局过程中,渲染对象树中的每个渲染对象都会接收父对象的布局约束参数,决定自己的大小,然后父对象按照控件逻辑决定各个子对象的位置,完成布局过程

  void layout(Constraints constraints, { bool parentUsesSize = false }) {
    assert(!_debugDisposed);
    if (!kReleaseMode && debugProfileLayoutsEnabled) {
      Map<String, String>? debugTimelineArguments;
      assert(() {
        if (debugEnhanceLayoutTimelineArguments) {
          debugTimelineArguments = toDiagnosticsNode().toTimelineArguments();
        }
        return true;
      }());
      FlutterTimeline.startSync(
        '$runtimeType',
        arguments: debugTimelineArguments,
      );
    }
    assert(constraints.debugAssertIsValid(
      isAppliedConstraint: true,
      informationCollector: () {
        final List<String> stack = StackTrace.current.toString().split('\n');
        int? targetFrame;
        final Pattern layoutFramePattern = RegExp(r'^#[0-9]+ +Render(?:Object|Box).layout \(');
        for (int i = 0; i < stack.length; i += 1) {
          if (layoutFramePattern.matchAsPrefix(stack[i]) != null) {
            targetFrame = i + 1;
          } else if (targetFrame != null) {
            break;
          }
        }
        if (targetFrame != null && targetFrame < stack.length) {
          final Pattern targetFramePattern = RegExp(r'^#[0-9]+ +(.+)$');
          final Match? targetFrameMatch = targetFramePattern.matchAsPrefix(stack[targetFrame]);
          final String? problemFunction = (targetFrameMatch != null && targetFrameMatch.groupCount > 0) ? targetFrameMatch.group(1) : stack[targetFrame].trim();
          return <DiagnosticsNode>[
            ErrorDescription(
              "These invalid constraints were provided to $runtimeType's layout() "
              'function by the following function, which probably computed the '
              'invalid constraints in question:\n'
              '  $problemFunction',
            ),
          ];
        }
        return <DiagnosticsNode>[];
      },
    ));
    assert(!_debugDoingThisResize);
    assert(!_debugDoingThisLayout);
    final bool isRelayoutBoundary = !parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject;
    final RenderObject relayoutBoundary = isRelayoutBoundary ? this : parent!._relayoutBoundary!;
    assert(() {
      _debugCanParentUseSize = parentUsesSize;
      return true;
    }());

    if (!_needsLayout && constraints == _constraints) {
      assert(() {
        // in case parentUsesSize changed since the last invocation, set size
        // to itself, so it has the right internal debug values.
        _debugDoingThisResize = sizedByParent;
        _debugDoingThisLayout = !sizedByParent;
        final RenderObject? debugPreviousActiveLayout = _debugActiveLayout;
        _debugActiveLayout = this;
        debugResetSize();
        _debugActiveLayout = debugPreviousActiveLayout;
        _debugDoingThisLayout = false;
        _debugDoingThisResize = false;
        return true;
      }());

      if (relayoutBoundary != _relayoutBoundary) {
        _relayoutBoundary = relayoutBoundary;
        visitChildren(_propagateRelayoutBoundaryToChild);
      }

      if (!kReleaseMode && debugProfileLayoutsEnabled) {
        FlutterTimeline.finishSync();
      }
      return;
    }
    _constraints = constraints;
    if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
      // The local relayout boundary has changed, must notify children in case
      // they also need updating. Otherwise, they will be confused about what
      // their actual relayout boundary is later.
      visitChildren(_cleanChildRelayoutBoundary);
    }
    _relayoutBoundary = relayoutBoundary;
    assert(!_debugMutationsLocked);
    assert(!_doingThisLayoutWithCallback);
    assert(() {
      _debugMutationsLocked = true;
      if (debugPrintLayouts) {
        debugPrint('Laying out (${sizedByParent ? "with separate resize" : "with resize allowed"}) $this');
      }
      return true;
    }());
    if (sizedByParent) {
      assert(() {
        _debugDoingThisResize = true;
        return true;
      }());
      try {
        performResize();
        assert(() {
          debugAssertDoesMeetConstraints();
          return true;
        }());
      } catch (e, stack) {
        _reportException('performResize', e, stack);
      }
      assert(() {
        _debugDoingThisResize = false;
        return true;
      }());
    }
    RenderObject? debugPreviousActiveLayout;
    assert(() {
      _debugDoingThisLayout = true;
      debugPreviousActiveLayout = _debugActiveLayout;
      _debugActiveLayout = this;
      return true;
    }());
    try {
      performLayout();
      markNeedsSemanticsUpdate();
      assert(() {
        debugAssertDoesMeetConstraints();
        return true;
      }());
    } catch (e, stack) {
      _reportException('performLayout', e, stack);
    }
    assert(() {
      _debugActiveLayout = debugPreviousActiveLayout;
      _debugDoingThisLayout = false;
      _debugMutationsLocked = false;
      return true;
    }());
    _needsLayout = false;
    markNeedsPaint();

    if (!kReleaseMode && debugProfileLayoutsEnabled) {
      FlutterTimeline.finishSync();
    }
  }


4.2 绘制paint:
void paint(PaintingContext context, Offset offset) { }

布局完成后,渲染对象树中的每个节点都有了明确的尺寸和位置。Flutter会把所有的渲染对象绘制到不同的图层上。
与布局过程一样,绘制过程也是深度优先遍历,而且总是先绘制自身,再绘制子节点。
Flutter提出了与布局边界对应的机制——重绘边界(Repaint Boundary)。在重绘边界内,Flutter会强制切换新的图层,这样就可以避免边界内外的互相影响,避免无关内容置于同一图层引起不必要的重绘
重绘边界的一个典型场景是Scrollview。ScrollView滚动的时候需要刷新视图内容,从而触发内容重绘。而当滚动内容重绘时,一般情况下其他内容是不需要重绘的,这时候重绘边界就派上用场了

最后

为了能够方便大家快速学习Flutter, 这里整理了Flutter学习路线图以及《Flutter Dart 语言编程入门到精通》&《Flutter实战:第二版》帮助大家配置相关环境,学习Flutter 的基本语法以及最后的项目实际利用。

学习路线:
在这里插入图片描述

Dart语言是Flutter的开发语言,所以我们需要掌握Dart语言的基础知识, 由于内容过多,截图展示的目录及部分内容,完整文档领取方式扫描下方二维码即可免费获取!

《Flutter Dart 语言编程入门到精通》

第一章 Dart语言基础

  • 环境准备
  • 基础语法

第二章 Dart 异步编程

  • Dart的事件循环
  • 调度任务
  • 延时任务
  • Future详解
  • async和await
  • lsolate

img

第三章 异步之 Stream 详解

  • 什么是Stream
  • 单订阅流
  • 广播流
  • Stream Transformer
  • 总结

第四章 Dart标准输入输出流

  • 文件操作

img

第五章 Dart 网络编程

  • TCP服务端
  • TCP客户端
  • UDP服务端
  • UDP客户端
  • HTTP服务器与请求
  • WebSocket

第六章 Flutter 爬虫与服务端

  • Dart爬虫开发
  • Dart服务端
  • 创建Flutter项目演示
  • 总结

第七章 Dart 的服务端开发

  • 注册登录实现

第八章 Dart 调用C语言混合编程

  • 环境准备
  • 测试Dart ffi接口
  • 总结

第九章 LuaDardo中Dart与Lua的相互调用

  • Lua C API
  • 创建运行时
  • Dart调Lua
  • Lua调Dart

img

掌握了Dart语言之后,咱们就可以通过实战来掌握Flutter的知识点, 由于内容过多,截图展示的目录及部分内容,完整文档领取方式扫描下方二维码即可免费获取!

《Flutter实战:第二版》

第一章:起步

  • 1.1 移动开发技术简介
  • 1.2 初始Flutter
  • 1.3 搭建Flutter开发环境
  • 1.4 Dart语言简介

第二章:第一个Flutter应用

  • 2.1 计数器应用实例
  • 2.2 Widget简介
  • 2.3 状态管理
  • 2.4路由管理
  • 2.5包管理
  • 2.6 资源管理
  • 2.7 调试Flutter应用
  • 2.8 Flutter异常捕获

在这里插入图片描述

第三章:基础组件

  • 3.1 文本及样式
  • 3.2 按钮
  • 3.3 图片及ICON
  • 3.4 单选开关和复选框
  • 3.5 输入框及表单
  • 3.6 进度指示器

第四章:布局类组件

  • 4.1 布局类组件简介
  • 4.2 布局原理与约束(constraints)
  • 4.3 线性布局(Row和Column)
  • 4.4 弹性布局(Flex)

在这里插入图片描述

第五章:容器类组件

  • 5.1 填充(Padding)
  • 5.2 装饰容器(DecoratedBox)
  • 5.3 变换(Transform)
  • 5.4 容器组件(Container)
  • 5.5 剪裁(Clip)
  • 5.6 空间适配(FittedBox)
  • 5.7 页面骨架(Scaffold)

第六章:可滚动组件

  • 6.1 可滚动组件简介
  • 6.2 SingleChildScrollView
  • 6.3 ListView
  • 6.4 滚动监听及控制

在这里插入图片描述

第七章:功能型组件

  • 7.1 导航返回拦截(WillPopScope)
  • 7.2 数据共享(InheritedWidget)
  • 7.3 跨组件状态共享
  • 7.4 颜色和主题
  • 7.5 按需rebuild(ValueListenableBuilder)
  • 7.6 异步UI更新(FutureBuilder、StreamBuilder)
  • 7.7 对话框详解

第八章:事件处理与通知

  • 8.1 原始指针事件处理
  • 8.2 手势识别
  • 8.3 Flutter事件机制
  • 8.4 手势原理与手势冲突
  • 8.5 事件总线
  • 8.6 通知 Notification

在这里插入图片描述

第九章:动画

  • 9.1 Flutter动画简介
  • 9.2 动画基本结构及状态监听
  • 9.3 自定义路由切换动画
  • 9.4 Hero动画
  • 9.5 交织动画
  • 9.6 动画切换组件(AnimatedSwitcher)
  • 9.7 动画过渡组件

第十章:自定义组件

  • 10.1 自定义组件方法简介
  • 10.2 组合现有组件
  • 10.3 组合实例:TurnBox
  • 10.4 CustomPaint 与 Canvas
  • 10.5 自绘实例:圆形背景渐变进度条
  • 10.6 自绘组件:CustomCheckbox
  • 10.7 自绘组件: DoneWidget
  • 10.8 水印实例: 文本绘制与离屏渲染

img

第十一章:文件操作与网络请求

  • 11.1 文件操作
  • 11.2 通过HttpClient发起HTTP请求
  • 11.3 Http请求库-dio
  • 11.4 实例:Http分块下载

第十二章:Flutter扩展

  • 12.1 包和插件
  • 12.2 Flutter Web

第十三章:国际化

  • 13.1 让App支持多语言
  • 13.2 实现Localizations
  • 13.3 使用Intl包
  • 13.4 国际化常见问题

在这里插入图片描述

第十四章:Flutter核心原理

  • 14.1 Flutter UI 框架(Framework)
  • 14.2 Element、BuildContext和RenderObject
  • 14.3 Flutter启动流程和渲染管线
  • 14.4 布局(Layout)过程
  • 14.5 绘制(一)绘制原理及Layer

第十五章:一个完整的Flutter应用

  • 15.1 Github客户端示例
  • 15.2 Flutter APP代码结构
  • 15.3 Model类定义
  • 15.4 全局变量及共享状态
  • 15.5 网络请求封装
  • 15.6 APP入口及主页
  • 15.7 登录页
  • 15.8 多语言和多主题

img

由于内容过多,截图展示的目录及部分内容,完整文档领取方式扫描下方二维码即可免费获取!

  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值