美文网首页
深入Flutter的Rendering层(一)--- 从runA

深入Flutter的Rendering层(一)--- 从runA

作者: MJun钧 | 来源:发表于2020-01-28 21:34 被阅读0次

    本文所有源码版本为Flutter 1.9.1,部分源码会删除assert和debug部分
    转载请注明出处,谢谢

    0、本系列文章

    1. 深入Flutter的Rendering层(一)--- 从runApp到三棵树的构建
    2. 深入Flutter的Rendering层(二)--- 布局layout与绘制paint

    1、前言

    使用Flutter的Widget写了一段时间,用是挺好用,但就是越写心里越没底,感到疑惑的问题越来越多,例如:

    • 我写的Widget是怎么渲染出来呢?
    • 为什么有时候Container是撑满父亲,有时候又不是?
    • 我写的Widget明明这么复杂,为啥可以被频繁build重新创建,性能还这么好?
    • 我们都知道有Widget树、Element树、RenderObject树,但是为什么要设计这么多层?Element树究竟有啥用?
    • 那个渲染溢出(overflow)的错误提示好烦啊,它是怎么出来的?
    • ...

    如果你也有这些问题,本系列文章可以解答以上所有疑惑。本系列会沿着Widget层,往更深的Rendering层,从源码+一些个人理解,清晰地分析从Widget到渲染整个链路。

    在分析过程中,我们也会接触到很多Rendering层才接触到的概念,例如RenderView、Layer、PaintingContext、PipelineOwner、BuildOwner、Window等等,这些概念会在解析过程中结合代码逐一说明。

    2、分层

    下图是简化后的Flutter分层架构图:


    架构图

    一般Flutter开发时,只会用到上图中的Widgets以及其上面的Material & Cupertino,只做开发的话熟悉这块就足够了。但是如果想深入学习其原理,还需要往底层研究。本系列会大部分地涉猎到Rendering层的代码,也会少量地关联到dart:ui。

    关于这个图的更多说明,请看这篇文章:https://www.jianshu.com/p/8e714a204898

    3、关于Widget、Element和RenderObject

    我们都知道Widget都有一个对应的Element和RenderObject。Widget类里有一个必须实现的方法是createElement,通过这个方法我们就可以知道Widget对应的Element是哪个;

    那么Widget对应的RenderObject呢?

    图源:https://zhuanlan.zhihu.com/p/36577285

    从上图可以知道:

    • 展示型Widget都是继承于RenderObjectWidget,而每个RenderObjectWidget都必须实现createRenderObject方法用来构建对应的RenderObject。这个就是展示型Widget对应的RenderObject
    • 而我们平时写的Widget还有很多现成的Widget(如Container、Text)都是组合型Widget(Stateless和Stateful)。如果追踪源码你会发现组合型Widget的build方法中会构建真正的RenderObjectWidget。因此对于组合型Widget,它对应的RenderObject就是这个RenderObjectWidget构建的RenderObject。

    同样,下文所说的Widget对应的Element和RenderObject就是上述的情况。
    下面就从源码分析Rendering层。

    4、从runApp切入

    “我写的Widget是怎么渲染出来呢?” --- 接下来我们会围绕着这个问题去分析源码。

    其实网上也有不少分析Flutter应用启动的高质量文章,如这篇http://gityuan.com/2019/06/29/flutter_run_app/
    本文也有类似分析流程,但是会更多关注渲染层,并且一些相关的概念也会更深说明解释。

    4.1 入口

    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: Container(),
        );
      }
    }
    

    这是最常见的Flutter应用代码,其中MyApp Widget就是我们写的Widget。

    4.2 runApp

    void runApp(Widget app) {
      WidgetsFlutterBinding.ensureInitialized()    //[见小节4.3]
        ..attachRootWidget(app)    //[见小节4.4]
        ..scheduleWarmUpFrame();    //[见小节4.5]
    }
    

    可以看到runApp只要调用了三个函数,下面都有详细分析。

    4.3 ensureInitialized

    class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
      static WidgetsBinding ensureInitialized() {
        if (WidgetsBinding.instance == null)
          WidgetsFlutterBinding();
        return WidgetsBinding.instance;
      }
    
    abstract class BindingBase {
      BindingBase() {
        ...
        initInstances();
        ...
      }
    }
    

    WidgetsFlutterBinding继承抽象类BindingBase,并且有7个mixin,因此在构造函数时会调用最后一个mixin类的initInstances()方法。同时由于每个mixin类都会调用super.initInstances(),所以最终会按倒序调用所有mixin类的initInstances(),最先调用是WidgetsBinding ,最后是GestureBinding。

    4.3.1 时序图

    下面是ensureInitialized()方法的时序图,里面只画出了渲染相关的函数调用:


    ensureInitialized 时序图

    4.3.2 RendererBinding.initInstances

    mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
      @override
      void initInstances() {
        super.initInstances();
        _instance = this;
        _pipelineOwner = PipelineOwner(      // 注释①
          onNeedVisualUpdate: ensureVisualUpdate,
          onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
          onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
        );
        window    // 注释②
          ..onMetricsChanged = handleMetricsChanged
          ..onTextScaleFactorChanged = handleTextScaleFactorChanged
          ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
          ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
          ..onSemanticsAction = _handleSemanticsAction;
        initRenderView();      // [见小节4.3.3]
        _handleSemanticsEnabledChanged();
        assert(renderView != null);
        addPersistentFrameCallback(_handlePersistentFrameCallback);    // 注释③
        _mouseTracker = _createMouseTracker();
      }
    }
    

    注释①

    构建一个PipelineOwner对象,并且传递一些回调函数作为入参。那么问题来了,PipelineOwner是什么东西?先看看源码的注释:

    /// The pipeline owner manages the rendering pipeline.
    

    源码注释很长,这里只截取了关键部分。从说明来看可以知道它是管理渲染通道的,那么怎么管理呢?可以看看调用PipelineOwner最核心的代码:
    [源码路径: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(); // 发送语义化给系统,用于辅助功能等
      }
    

    从函数名drawFrame大概就可以知道PipelineOwner的核心作用就是将Widget对应RenderObject的布局绘制结果发送给GPU。这个方法会在下一篇文章进行分析。

    一个Flutter App只有这里构建了PipelineOwner,全局只有一个PipelineOwner实例。

    注释②

    这里使用了window变量,这个变量其实是dart:ui库暴露出来的一个全局变量,其类型为Window。同样看看源码注释:

    [源码路径:flutter/bin/cache/pkg/sky_engine/lib/ui/window.dart]
    /// The most basic interface to the host operating system's user interface.
    

    可以看出,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;
      // 绘制回调  
      VoidCallback get onDrawFrame => _onDrawFrame;
      // 点击或指针事件回调
      PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
      // 调度Frame,该方法执行后,onBeginFrame和onDrawFrame将紧接着会在合适时机被调用,
      // 此方法会直接调用Flutter engine的Window_scheduleFrame方法
      void scheduleFrame() native 'Window_scheduleFrame';
      // 更新应用在GPU上的渲染,此方法会直接调用Flutter engine的Window_render方法
      void render(Scene scene) native 'Window_render';
    
      // 发送平台消息
      void sendPlatformMessage(String name,
                               ByteData data,
                               PlatformMessageResponseCallback callback) ;
      // 平台通道消息处理回调  
      PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
    
      ... //其它属性及回调
    
    }
    

    Window类包含了当前设备和系统的一些信息以及Flutter Engine的一些回调。

    注释③

    addPersistentFrameCallback方法估计大家都有用过,就是注册一个持久的监听,当Flutter接收到一个"Vsync"信号时,最终会触发调用注册的PersistentFrameCallback。当然这里也一样。
    [源码路径:flutter/lib/src/rendering/binding.dart]

    void _handlePersistentFrameCallback(Duration timeStamp) {
        drawFrame();
      }
    

    因此,注释③这行代码的意思就是,当接收到"Vsync"信号并可以执行下一帧的时候,调用RendererBinding.drawFrame()。该方法的执行内容将在下一篇文章就行分析。

    4.3.3 initRenderView

    继续回到源码分析;

    [源码路径:flutter/lib/src/rendering/binding.dart]
      void initRenderView() {
        assert(renderView == null);
        renderView = RenderView(configuration: createViewConfiguration(), window: window);   
         // 注释①
        renderView.scheduleInitialFrame();      // [见小节4.3.4]
      }
    
      set renderView(RenderView value) {
        assert(value != null);
        _pipelineOwner.rootNode = value;     // 注释②
      }
    
    [源码路径:flutter/lib/src/rendering/object.dart]
      AbstractNode get rootNode => _rootNode;
      AbstractNode _rootNode;
      set rootNode(AbstractNode value) {
        if (_rootNode == value)
          return;
        _rootNode?.detach();
        _rootNode = value;
        _rootNode?.attach(this);     // 注释③
      }
    

    注释①

    这里创建了一个RenderView实例。你没猜错,这个实例也是全局只有一个的。那么RenderView是啥玩意呢?

    [源码路径:/flutter/lib/src/rendering/view.dart]
    /// The root of the render tree.
    ///
    /// The view represents the total output surface of the render tree and handles
    /// bootstrapping the rendering pipeline. The view has a unique child
    /// [RenderBox], which is required to fill the entire output surface.
    class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
      RenderView({
        RenderBox child,
        @required ViewConfiguration configuration,
        @required ui.Window window,
      }) : assert(configuration != null),
           _configuration = configuration,
           _window = window {
        this.child = child;
      }
    }
    

    注释第一行已经表明身份,没错它就是我们RenderObject树的根节点,它也是集成于RenderObject。我们写的Widget对应的RenderObject就是挂在这个RenderView下,就是它的那个child属性(不是在这里挂上去的,挂上去的代码在下一节)

    一个Flutter App只有这里构建了RenderView,当然全局只有一个RenderView实例。

    注释②

    注释①赋值renderView会触发调用setter注释②,同样注释②也会触发调用setter注释③。

    注释③

    [源码路径:flutter/lib/src/foundation/node.dart]
      @mustCallSuper
      void attach(covariant Object owner) {
        assert(owner != null);
        assert(_owner == null);
        _owner = owner;
      }
    

    整个流程下来,可以整理为pipelineOwner的rootNode是renderView,而renderView也不客气地attach了pipelineOwner。简单来说就是互相持有对方的引用。

    4.3.4 scheduleInitialFrame

    [源码路径:/flutter/lib/src/rendering/view.dart]
      /// Bootstrap the rendering pipeline by scheduling the first frame.
      ///
      /// This should only be called once, and must be called before changing
      /// [configuration]. It is typically called immediately after calling the
      /// constructor.
      void scheduleInitialFrame() {
        assert(owner != null);
        assert(_rootTransform == null);
        scheduleInitialLayout();    // [见4.3.5]
        scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());    // [见4.3.6和4.3.7]
        assert(_rootTransform != null);
        owner.requestVisualUpdate();    // [见4.3.8]
      }
    

    4.3.5 scheduleInitialLayout

    [源码路径:flutter/lib/src/rendering/object.dart]
      /// Bootstrap the rendering pipeline by scheduling the very first layout.
      ///
      /// Requires this render object to be attached and that this render object
      /// is the root of the render tree.
      ///
      /// See [RenderView] for an example of how this function is used.
      void scheduleInitialLayout() {
        assert(attached);
        assert(parent is! RenderObject);
        assert(!owner._debugDoingLayout);
        assert(_relayoutBoundary == null);
        _relayoutBoundary = this;    // 注释①
        assert(() {
          _debugCanParentUseSize = false;
          return true;
        }());
        owner._nodesNeedingLayout.add(this);    // 注释②
      }
    

    注释①

    设置relayoutBoundary为自己。relayoutBoundary是在layout布局过程中用的,relayoutBoundary顾名思义就是布局的界限。如果一个 RenderObject 是 relayoutBoundary,就表示它的大小变化不会影响到 parent 的大小了,于是 执行重新布局时,就从该RenderObject开始重新layout,parent 不用重新布局了。【该具体逻辑分析将会在下一篇文章从源码侧说明。】

    注释②

    PipelineOwner有个List属性_nodesNeedingLayout,在这里的RenderObject会在PipelineOwner执行flushLayout时,拿出来去执行layout方法。【该具体逻辑分析将会在下一篇文章从源码侧说明。】

    4.3.6 _updateMatricesAndCreateNewRootLayer

    [源码路径:flutter/lib/src/rendering/view.dart]
        Layer _updateMatricesAndCreateNewRootLayer() {
        _rootTransform = configuration.toMatrix();
        final ContainerLayer rootLayer = TransformLayer(transform: _rootTransform);
        rootLayer.attach(this);
        assert(_rootTransform != null);
        return rootLayer;
      }
    

    这个函数主要创建了一个TransformLayer实例。TransformLayer继承与Layer,而Layer的源码注释如下:

    [源码路径:flutter/lib/src/rendering/layer.dart]
    /// A composited layer.
    ///
    /// During painting, the render tree generates a tree of composited layers that
    /// are uploaded into the engine and displayed by the compositor. This class is
    /// the base class for all composited layers.
    

    再看看它的关键调用代码:

    [源码路径:flutter/lib/src/rendering/view.dart]
      /// Uploads the composited layer tree to the engine.
      ///
      /// Actually causes the output of the rendering pipeline to appear on screen.
      void compositeFrame() {
        Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);
        try {
          final ui.SceneBuilder builder = ui.SceneBuilder();
          final ui.Scene scene = layer.buildScene(builder);    // 这个layer就是上面创建的TransformLayer
          if (automaticSystemUiAdjustment)
            _updateSystemChrome();
          _window.render(scene);
          scene.dispose();
          assert(() {
            if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)
              debugCurrentRepaintColor = debugCurrentRepaintColor.withHue((debugCurrentRepaintColor.hue + 2.0) % 360.0);
            return true;
          }());
        } finally {
          Timeline.finishSync();
        }
      }
    

    从这两处代码和注释可以大概理解到Layer其实就是RenderObject/Render树绘制出来的内容数据。当然Layer还有很多别的重要知识点,【同样将会在下一篇文章从源码侧说明。】

    4.3.7 scheduleInitialPaint

    [源码路径:flutter/lib/src/rendering/object.dart]
      /// Bootstrap the rendering pipeline by scheduling the very first paint.
      ///
      /// Requires that this render object is attached, is the root of the render
      /// tree, and has a composited layer.
      ///
      /// See [RenderView] for an example of how this function is used.
      void scheduleInitialPaint(ContainerLayer rootLayer) {
        assert(rootLayer.attached);
        assert(attached);
        assert(parent is! RenderObject);
        assert(!owner._debugDoingPaint);
        assert(isRepaintBoundary);
        assert(_layer == null);
        _layer = rootLayer;
        assert(_needsPaint);
        owner._nodesNeedingPaint.add(this);
      }
    

    这里就干了两件事

    1. 把刚才创建的TransformLayer赋值给_layer,而这个_layer就是最终执行compositeFrame方法用的layer,该方法会把layer的数据通过Window类渲染出来。
    2. PipelineOwner还有个List属性_nodesNeedingPaint,在这个List里的RenderObject会在PipelineOwner执行flushPaint时,拿出来去执行paint方法进行绘制。【该具体逻辑分析将会在下一篇文章从源码侧说明。】

    4.3.8 requestVisualUpdate

    [源码路径:flutter/lib/src/rendering/object.dart]
        /// Calls [onNeedVisualUpdate] if [onNeedVisualUpdate] is not null.
      ///
      /// Used to notify the pipeline owner that an associated render object wishes
      /// to update its visual appearance.
      void requestVisualUpdate() {
        if (onNeedVisualUpdate != null)
          onNeedVisualUpdate();    // 回调onNeedVisualUpdate方法
      }
    
    [源码路径:flutter/lib/src/rendering/binding.dart]
    mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
      @override
      void initInstances() {
        super.initInstances();
        _instance = this;
        _pipelineOwner = PipelineOwner(
          onNeedVisualUpdate: ensureVisualUpdate,    // onNeedVisualUpdate其实就是调用ensureVisualUpdate
          onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
          onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
        );
        ...
      }
    }
    
    [源码路径:flutter/lib/src/scheduler/binding.dart]
      void ensureVisualUpdate() {
        switch (schedulerPhase) {
          case SchedulerPhase.idle:
          case SchedulerPhase.postFrameCallbacks:
            scheduleFrame();      // 最终会调用scheduleFrame
            return;
          case SchedulerPhase.transientCallbacks:
          case SchedulerPhase.midFrameMicrotasks:
          case SchedulerPhase.persistentCallbacks:
            return;
        }
      }
    

    跟着注释说明,最终会调用scheduleFrame方法,而该方法从名字大概就可以知道它会触发调度下一帧渲染逻辑,最终就是触发drawFrame方法。

    4.3.9 ensureInitialized小结

    至此,WidgetsFlutterBinding.ensureInitialized()已经分析了七七八八,我们总结一下,这一步还是做了不少事情。

    • 这一步主要是初始化了整个RenderObject树到渲染通道的框架。
    • 首先创建了PipelineOwner实例,通过PipelineOwner去管理渲染通道的执行流程;然后创建了RenderView实例,并且绑定了PipelineOwner实例,而Window实例也在RenderView关联起来。最后,为RenderView构建了一个绘制的载体TransformLayer,然后触发调度下一帧scheduleFrame。
    • 这些串在一起就是,RenderView的内容(也就是我们写的Widget对应RenderObject内容)绘制在TransformLayer上,然后通过PipelineOwner管理整个渲染调度流程,把TransformLayer上绘制好的内容数据,通过Window类发送到更底层/GPU渲染出来。

    4.4 attachRootWidget

    接下来分析runApp的第二步,WidgetsFlutterBinding.attachRootWidget。它其实是调用了mixin的WidgetsBinding.attachRootWidget()。
    我已经整理了这一步的大致时序图。

    3.4.1 时序图

    attachRootWidget时序图

    我们跟着时序图一步一步分析。

    4.4.2 BuildOwner

    [源码路径:flutter/lib/src/widgets/binding.dart]
      /// The [BuildOwner] in charge of executing the build pipeline for the
      /// widget tree rooted at this binding.
      BuildOwner get buildOwner => _buildOwner;
      final BuildOwner _buildOwner = BuildOwner();
    

    又是一个新事物。在WidgetsBinding里有个字段构建了一个BuildOwner实例,从注释得知,它主要管理Widget树的构建build。描述依然非常抽象,从其他核心调用,我们大概可以知道Widget的build、mount和unmount等都跟它有关联。

    一个Flutter App全局只有一个BuildOwner实例。

    4.4.3 attachRootWidget

    [源码路径:flutter/lib/src/widgets/binding.dart]
      /// Takes a widget and attaches it to the [renderViewElement], creating it if
      /// necessary.
      ///
      /// This is called by [runApp] to configure the widget tree.
      ///
      /// See also [RenderObjectToWidgetAdapter.attachToRenderTree].
      void attachRootWidget(Widget rootWidget) {
        _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(    // 注释①
          container: renderView,
          debugShortDescription: '[root]',
          child: rootWidget,
        ).attachToRenderTree(buildOwner, renderViewElement);    // [见4.4.4]
      }
    

    注释①

    这里构造了一个RenderObjectToWidgetAdapter实例,它继承于Widget,所以它本质就是Widget。然后我们写的Widget(也就是rootWidget变量)作为child参数传进去,而Render树的根结点RenderView也作为container参数传进去。

    从这些可以看出来这个RenderObjectToWidgetAdapter有多厉害,没错,它就是我们Widget树的根结点,我们写的Widget就是挂在它下面,它对应的RenderObject就是RenderView。

    4.4.4 attachToRenderTree

    [源码路径:flutter/lib/src/widgets/binding.dart]
      /// Inflate this widget and actually set the resulting [RenderObject] as the
      /// child of [container].
      ///
      /// If `element` is null, this function will create a new element. Otherwise,
      /// the given element will have an update scheduled to switch to this widget.
      ///
      /// Used by [runApp] to bootstrap applications.
      RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
        if (element == null) {
          owner.lockState(() {    // lockState,在下面代码执行过程中,禁止调用setState方法
            element = createElement();    // 构建一个Element实例,见[4.4.5]
            assert(element != null);
            element.assignOwner(owner);    // 将上面介绍的BuildOwner赋值给Element实例
          });
          owner.buildScope(element, () {      // 见[4.4.6]
            element.mount(null, null);      // 作为一个回调函数传给buildScope,见[4.4.6]
          });
        } else {
          element._newWidget = this;
          element.markNeedsBuild();
        }
        return element;
      }
    

    这里值得一提的是,入参element,也就是renderViewElement这个时候还是为null,所以会走第一个判断逻辑。当走完这个函数之后,renderViewElement才会被赋值。

    4.4.5 createElement

    [源码路径:flutter/lib/src/widgets/binding.dart]
      @override
      RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
    

    构建了一个RenderObjectToWidgetElement实例,它继承于Element。这里的this是刚才的根Widget RenderObjectToWidgetAdapter。

    至此,三棵树的根结点都出来了:

    • Widget树的根结点是RenderObjectToWidgetAdapter;
    • Element树的根结点是RenderObjectToWidgetElement;
    • RenderObject树的根结点是RenderView;
      他们仨一一对应。

    4.4.6 buildScope

    [源码路径:flutter/lib/src/widgets/framework.dart]
    void buildScope(Element context, [VoidCallback callback]) {
      if (callback == null && _dirtyElements.isEmpty)
        return;
      Timeline.startSync('Build', arguments: timelineWhitelistArguments);
      try {
        if (callback != null) {
          _dirtyElementsNeedsResorting = false;
          callback();  //此处回调就是刚才说的mount() ,见4.4.7
        }
        ...
        _dirtyElements.sort(Element._sort);
        int dirtyCount = _dirtyElements.length;
        while (index < dirtyCount) {
            _dirtyElements[index].rebuild();     //对mark为dirty的Element执行rebuild操作,这块的核心会在下一篇文章说明
            ...
        }
      } finally {
        ...
        Timeline.finishSync();
      }
    }
    

    4.4.7 mount

    [源码路径:flutter/lib/src/widgets/binding.dart] RenderObjectToWidgetElement.mount
      @override
      void mount(Element parent, dynamic newSlot) {
        assert(parent == null);
        super.mount(parent, newSlot);    // 看下面
        _rebuild();    // 见4.4.8
      }
    
    [源码路径:flutter/lib/src/widgets/framework.dart] RenderObjectElement.mount
    @override
      void mount(Element parent, dynamic newSlot) {
        super.mount(parent, newSlot);    // 再看下面
        _renderObject = widget.createRenderObject(this);     //  注释①
        assert(() { _debugUpdateRenderObjectOwner(); return true; }());
        assert(_slot == newSlot);
        attachRenderObject(newSlot);    // 本次分析不涉及这里
        _dirty = false;
      }
    
    [源码路径:flutter/lib/src/widgets/framework.dart] Element.mount
    // 这里是每个Element必经之路
    @mustCallSuper
      void mount(Element parent, dynamic newSlot) {
        assert(_debugLifecycleState == _ElementLifecycle.initial);
        assert(widget != null);
        assert(_parent == null);
        assert(parent == null || parent._debugLifecycleState == _ElementLifecycle.active);
        assert(slot == null);
        assert(depth == null);
        assert(!_active);
        _parent = parent;    // 赋值爸妈
        _slot = newSlot;     // slot参数,可以理解为是一个存数据的地方
        _depth = _parent != null ? _parent.depth + 1 : 1;    // 深度
        _active = true;    // 这个不用说吧
        if (parent != null) // Only assign ownership if the parent is non-null
          _owner = parent.owner;    // 这个就是BuildOwner,所以刚才创建的BuildOwner会通过这种方式为整棵树每一个结点都赋值上;
        if (widget.key is GlobalKey) {
          final GlobalKey key = widget.key;
          key._register(this);    // 注册并建立globalKey和Element的关联
        }
        _updateInheritance();
        assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; }());
      }
    

    注释①

    widget.createRenderObject()这个方法很重要。
    这里就是我们写的Widget创建出RenderObject的地方,然后Element就可以存有RenderObject实例。createRenderObject是每一个RenderObjectWidget必须实现了,看看源码就可以知道每个Widget它对应的RenderObject是什么类别。

    那么Element是怎样跟Widget关联起来呢?后面会介绍。(见4.4.10注释②)

    那么我们的根Widget RenderObjectToWidgetAdapter是怎么实现createRenderObject方法:

      @override
      RenderObjectWithChildMixin<T> createRenderObject(BuildContext > context) => container;
    

    而这里返回的container就是刚才我们用RenderView去赋值的。所以RenderView就是这里与RenderObjectToWidgetElement关联在一起。

    4.4.8 _rebuild

    [源码路径:flutter/lib/src/widgets/binding.dart]   
    void _rebuild() {
      try {
        _child = updateChild(_child, widget.child, _rootChildSlot);    // 见4.4.9
      } catch (exception, stack) {
        ...
      }
    }
    

    这里的widget就是根结点RenderObjectToWidgetAdapter,而widget.child就是我们写的Widget。因此这里就是把我们的Widget生成对应的Element,然后挂在根Element的child属性里。

    4.4.9 updateChild

    [源码路径:flutter/lib/src/widgets/framework.dart]   
    @protected
     Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
       ...
       if (newWidget == null) {
         if (child != null)
           deactivateChild(child);
         return null;
       }
       if (child != null) {
         if (child.widget == newWidget) {
           if (child.slot != newSlot)
             updateSlotForChild(child, newSlot);
           return child;
         }
         if (Widget.canUpdate(child.widget, newWidget)) {
           if (child.slot != newSlot)
             updateSlotForChild(child, newSlot);
           child.update(newWidget);
           ...
           return child;
         }
         deactivateChild(child);
       }
       return inflateWidget(newWidget, newSlot);    // 见4.4.10
     }
    

    重要!!!
    这个方法是Flutter Rendering层非常重要的一个函数。它在Widget的增删改过程都会触发调用。什么会触发Widget树的增删改?最简单的栗子就是初次应用启动,或者setState的时候;

    下面是源码注释:

      /// Update the given child with the given new configuration.
      ///
      /// This method is the core of the widgets system. It is called each time we
      /// are to add, update, or remove a child based on an updated configuration.
      ///
      /// If the `child` is null, and the `newWidget` is not null, then we have a new
      /// child for which we need to create an [Element], configured with `newWidget`.
      ///
      /// If the `newWidget` is null, and the `child` is not null, then we need to
      /// remove it because it no longer has a configuration.
      ///
      /// If neither are null, then we need to update the `child`'s configuration to
      /// be the new configuration given by `newWidget`. If `newWidget` can be given
      /// to the existing child (as determined by [Widget.canUpdate]), then it is so
      /// given. Otherwise, the old child needs to be disposed and a new child
      /// created for the new configuration.
      ///
      /// If both are null, then we don't have a child and won't have a child, so we
      /// do nothing.
      ///
      /// The [updateChild] method returns the new child, if it had to create one,
      /// or the child that was passed in, if it just had to update the child, or
      /// null, if it removed the child and did not replace it.
      ///
      /// The following table summarizes the above:
      ///
      /// |                     | **newWidget == null**  | **newWidget != null**   |
      /// | :-----------------: | :--------------------- | :---------------------- |
      /// |  **child == null**  |  Returns null.         |  Returns new [Element]. |
      /// |  **child != null**  |  Old child is removed, returns null. | Old child updated if possible, returns child or new [Element]. |
    

    这里主要是处理Element能不能复用的逻辑,我们都知道Element创建很好资源,通过复用可以大大减少耗时。

    具体的处理逻辑是:

    1. 如果新的Widget为null,那么就不需要Element,所以就都返回null;
      唯一要注意是如果旧的Element还在,需要移除掉Element与WIdget的关联,同时这个Element会保存在BuildOwner一个缓存_inactiveElements中,如果下次能用的时候会直接拿来用,避免创建耗时。(可以看4.4.10注释①)
    2. 如果新的Widget不为null,而旧的Element也不为null,那么需要通过Widget.canUpdate方法判断这个旧的Element能不能复用。而判断逻辑是Element对应的Widget和新Widget的类型和key相同就可以复用。
    3. 其他情况,就需要通过新的Widget去创建新的Element。具体请看下一小节。

    毫无疑问,我们写的Widget肯定会走到情况3,也就是调用inflateWidget创建Element;

    4.4.10 inflateWidget

    [源码路径:flutter/lib/src/widgets/framework.dart]   
    @protected
     Element inflateWidget(Widget newWidget, dynamic newSlot) {
       final Key key = newWidget.key;
       if (key is GlobalKey) {
         final Element newChild = _retakeInactiveElement(key, newWidget);      // 注释①
         if (newChild != null) {
           newChild._activateWithParent(this, newSlot);
           final Element updatedChild = updateChild(newChild, newWidget, newSlot);
           return updatedChild;
         }
       }
       final Element newChild = newWidget.createElement();      // 注释②
       newChild.mount(this, newSlot);    // 注释③
       return newChild;
     }
    

    这个方法同样重要。
    如果你是Android开发的,那么对inflate肯定熟悉不过。Android里如果要把xml转成View,我们调用的接口就是LayoutInflater.inflate。而Flutter的这个接口意思也是差不多,Widget作为Flutter界面的蓝图,它更接近于xml,所以这个接口就是把Widget描述的样子inflate出来。

    注释①

    _retakeInactiveElement这个方法内部会去刚才我提到的_inactiveElements缓存里找有没有能复用Element,然后直接拿来用。

    注释②

    newWidget.createElement(),顾名思义,就是创建Widget对应的Element实例。其实Element的构造函数是必须要传Widget作为入参,在这里Element就与Widget进行了关联。
    至此,我们已经知道Element是如何关联Widget和RenderObject了。

    看看源码,可以了解每个Widget对应的Element和RenderObject类型。

    注释③

    mount方法可能每个Element类型都会不一样,但是做的事情的核心就是将自己挂在parent Element上,同时为自己的Widget.child生成Element。

    4.4.11 attachRootWidget小结

    runApp的第二步已经分析完成。到这一步很多事情就明朗起来。

    • 首先构造Widget树和Element树的根结点,分别是RenderObjectToWidgetAdapter和RenderObjectToWidgetElement,而他们对应的RenderObject就是上一步构造的Render树根结点RenderView;
    • 然后把我们写的Widget作为rootWidget通过mount方法挂载到了树的根结点上。到这里我们已经解答“我写的Widget是怎么渲染出来呢?”的一半了。(还有一半下一篇文章进行分析)

    同时,最初的这两个问题也已经有了答案:

    1. 问题:我写的Widget明明这么复杂,为啥可以被频繁build重新创建,性能还这么好?
      答:Element有高效的复用逻辑,即使是Widget频繁rebuild,但是通过上述的复用操作,可以尽可能减少耗时,提高性能;
    2. 问题:我们都知道有Widget树、Element树、RenderObject树,但是为什么要设计这么多层?Element树究竟有啥用?
      答:分析下来,我们已经很清楚Element在Widget和RenderObject之间的重要性。大家都知道Widget是immutable的,而RenderObject是可以改变而重绘的,这里面就需要Element去从中协调。Element就像一个倒贴的中间商,不仅不赚差价,还帮你做优化。
      -- Widget通过Element进行mount,当Widget有变化时也是通过Element做更新改变--updateChild;
      -- Element同时拥有Widget和RenderObject的引用,它们之间的沟通需要经过Element;

    4.5 scheduleWarmUpFrame

    终于来到最后一步了,WidgetsFlutterBinding.scheduleWarmUpFrame。

    [源码路径:flutter/lib/src/scheduler/binding.dart]   
    void scheduleWarmUpFrame() {
        if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
          return;
    
        _warmUpFrame = true;
        Timeline.startSync('Warm-up frame');
        final bool hadScheduledFrame = _hasScheduledFrame;
        // We use timers here to ensure that microtasks flush in between.
        Timer.run(() {
          assert(_warmUpFrame);
          handleBeginFrame(null);    // 帧准备绘制
        });
        Timer.run(() {
          assert(_warmUpFrame);
          handleDrawFrame();    // 帧绘制
          // We call resetEpoch after this frame so that, in the hot reload case,
          // the very next frame pretends to have occurred immediately after this
          // warm-up frame. The warm-up frame's timestamp will typically be far in
          // the past (the time of the last real frame), so if we didn't reset the
          // epoch we would see a sudden jump from the old time in the warm-up frame
          // to the new time in the "real" frame. The biggest problem with this is
          // that implicit animations end up being triggered at the old time and
          // then skipping every frame and finishing in the new time.
          resetEpoch();
          _warmUpFrame = false;
          if (hadScheduledFrame)
            scheduleFrame();
        });
    
        // Lock events so touch events etc don't insert themselves until the
        // scheduled frame has finished.
        lockEvents(() async {
          await endOfFrame;
          Timeline.finishSync();
        });
      }
    

    这一步主要就是触发立刻绘制首帧,而不用等待下一个“Vsync”信号。
    关于帧绘制handleDrawFrame相关分析,请看下一篇文章。

    5、参考资料

    相关文章

      网友评论

          本文标题:深入Flutter的Rendering层(一)--- 从runA

          本文链接:https://www.haomeiwen.com/subject/nrdethtx.html