Flutter的布局和绘制详解 ---三棵树

1.背景说明

Flutter开发也有段时间了,在实践中我们清晰的得知Flutter中一切皆是Widget,在绝大多数时候,我们就是跟各种StatefulWidgetStatelessWidget打交道。Widget对于我们而言,既是UI布局文件的配置信息,也是一些功能性的实现,如GestureDetector等。
当然通过简单的学习,我们也会了解到随处可见的Flutter的三棵树:Widget树,Element树,RenderObject树。抄一张官网的截图如下:

截屏2022-12-27 11.13.48.png

引用一下官网的说明:

既然Widget只是描述一个UI元素的配置信息,那么真正的布局、绘制是由谁来完成的呢?Flutter框架的的处理流程是这样的:
1.根据Widget树生成一个Element树,Element树中的节点都继承自 Element类。
2.根据Element树生成Render树(渲染树),渲染树中的节点都继承自RenderObject类。
3.根据渲染树生成Layer树,然后上屏显示,Layer树中的节点都继承自 Layer类。

以上是Flutter布局绘制的基本架构,那么问题来了,这几棵树是如何被关联起来的?关联起来后又是如何进行布局或绘制的呢?为了讲清楚这个流程,就必须现弄清楚三棵树的事情。本文将为大家先梳理三棵树,之后再写一篇说明布局或绘制。

2.Flutter的三棵树

我们从三棵树开始说起,从一个App的运行开始看看这三棵树是如何建立的。
我们建立一个新的Flutter工程,得到的入口如下:

void main() => runApp(MyApp());  

MyApp()是我们App的根节点Widget,整个运行的开始都在runApp()中。我们从分析runApp()的实现开始。

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

三个步骤,从命名中可得知:对应的是初始化,挂载根节点Widget,绘制首帧。我们来看每一步的具体实现。

2.1.WidgetsFlutterBinding.ensureInitialized()

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

WidgetsFlutterBinding继承BindingBase,同时混合了GestureBindingSchedulerBindingServicesBindingPaintingBindingSemanticsBindingRendererBindingWidgetsBinding。我们看一下其注释:

/// * [GestureBinding], which implements the basics of hit testing.
/// * [SchedulerBinding], which introduces the concepts of frames.
/// * [ServicesBinding], which provides access to the plugin subsystem.
/// * [PaintingBinding], which enables decoding images.
/// * [SemanticsBinding], which supports accessibility.
/// * [RendererBinding], which handles the render tree.
/// * [WidgetsBinding], which handles the widget tree.

这些都是将FrameworkFlutter引擎进行绑定的桥梁,其中RendererBindingWidgetsBinding与我们的三棵树有关,接下来将对它们进行分析。在此之前,我们先看WidgetsFlutterBinding的父类BindingBase的初始化:

BindingBase() {
//....
    initInstances();
//....
    initServiceExtensions();
//....
  }

其中initInstances()initServiceExtensions()都由其子类或mixin进行实现,其中initServiceExtensions()是在绑定初始化完成之后去初始化一些服务拓展,这个方法我们先不做展开,只看initInstances()的实现。
由于我们将要分析的是Flutter的布局和绘制,所以我们看RendererBindingWidgetsBinding对于initInstances()的实现。

2.1.1.RendererBinding

@override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    platformDispatcher
      ..onMetricsChanged = handleMetricsChanged
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
      ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
      ..onSemanticsAction = _handleSemanticsAction;
    initRenderView();
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    initMouseTracker();
    if (kIsWeb) {
      addPostFrameCallback(_handleWebFirstFrame);
    }
  }

2.1.1.1. PipelineOwner和PlatformDispatcher

首先创建了一个PipelineOwner对象,PipelineOwner是用来管理渲染管道的,ensureVisualUpdate()的实现如下:

  void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
  }

  void scheduleFrame() {
    if (_hasScheduledFrame || !framesEnabled) {
      return;
    }
    assert(() {
      if (debugPrintScheduleFrameStacks) {
        debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
      }
      return true;
    }());
    ensureFrameCallbacksRegistered();
    platformDispatcher.scheduleFrame();
    _hasScheduledFrame = true;
  }

看起来是用来触发渲染逻辑的。其中platformDispatcher是个PlatformDispatcher单例,它是最基础的Flutter Framework和宿主操作系统之间的接口,管理加载在屏幕硬件上的views,screens列表和显示窗口的配置信息。它的scheduleFrame()会执行native的方法,其描述如下:

/// Requests that, at the next appropriate opportunity, the [onBeginFrame] and
/// [onDrawFrame] callbacks be invoked.

总而言之就是真正的绘制操作的执行。我们继续看initRenderView()的实现。

2.1.1.2. RenderView

  void initRenderView() {
    assert(!_debugIsRenderViewInitialized);
    assert(() {
      _debugIsRenderViewInitialized = true;
      return true;
    }());
    renderView = RenderView(configuration: createViewConfiguration(), window: window);
    renderView.prepareInitialFrame();
  }

其中RenderView继承RenderObject。这个方法的作用就是为RenderObject树创建一个作为根节点。

  RenderView({
    RenderBox? child,
    required ViewConfiguration configuration,
    required ui.FlutterView window,
  }) : assert(configuration != null),
       _configuration = configuration,
       _window = window {
    this.child = child;
  }

  set renderView(RenderView value) {
    assert(value != null);
    _pipelineOwner.rootNode = value;
  }

  set rootNode(AbstractNode? value) {
    if (_rootNode == value) {
      return;
    }
    _rootNode?.detach();
    _rootNode = value;
    _rootNode?.attach(this);
  }
  @mustCallSuper
  void attach(covariant Object owner) {
    assert(owner != null);
    assert(_owner == null);
    _owner = owner;
  }

window是个继承ui.FlutterViewui.SingletonFlutterWindow对象,它是个单例,是Flutter scene真正被绘制的view。它包含自己的Layer树,在Flutter scene渲染时会将这些Layer渲染到FlutterView所在的区域。其中scene是个native对象,它存储了Layer结构,是最终真正被渲染的对象。
在创建RenderView时候,也将自己赋值给_pipelineOwnerrootNode。而rootNode会调用attach()方法,将_pipelineOwner赋值给rootNode_owner,总而言之就是相互持有了。我们继续看renderView.prepareInitialFrame()的实现:

  void prepareInitialFrame() {
    assert(owner != null);
    assert(_rootTransform == null);
    scheduleInitialLayout();
    scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
    assert(_rootTransform != null);
  }

先看scheduleInitialLayout()的实现:

void scheduleInitialLayout() {
    assert(!_debugDisposed);
    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,那么它将不会影响其父布局的大小,在之后的章节会进行详细的说明。因为renderView是根节点的view,它的布局边界就是它自己。_nodesNeedingLayout是个List,存储了需要被layoutview,这个在之后的章节也会进行说明。我们再看 scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer())的实现。

2.1.1.3. TransformLayer

  TransformLayer _updateMatricesAndCreateNewRootLayer() {
    _rootTransform = configuration.toMatrix();
    final TransformLayer rootLayer = TransformLayer(transform: _rootTransform);
    rootLayer.attach(this);
    assert(_rootTransform != null);
    return rootLayer;
  }

  void scheduleInitialPaint(ContainerLayer rootLayer) {
    assert(rootLayer.attached);
    assert(attached);
    assert(parent is! RenderObject);
    assert(!owner!._debugDoingPaint);
    assert(isRepaintBoundary);
    assert(_layerHandle.layer == null);
    _layerHandle.layer = rootLayer;
    assert(_needsPaint);
    owner!._nodesNeedingPaint.add(this);
  }

先创建了一个TransformLayer对象,Layer就是真正储存到scene里的绘制内容,先将rootLayerrenderView进行了绑定,再赋值给_layerHandlelayer对象。_layerHandle用来阻止Layer的平台图像资源被回收的handle。_nodesNeedingPaint也是个List,储存了需要被重新绘制的view,在之后的章节也将进行说明。
我们再次回到initInstances()方法,看addPersistentFrameCallback(_handlePersistentFrameCallback)的实现:

  void addPersistentFrameCallback(FrameCallback callback) {
    _persistentCallbacks.add(callback);
  }

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

  @protected
  void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    if (sendFramesToEngine) {
      renderView.compositeFrame(); // this sends the bits to the GPU
      pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
      _firstFrameSent = true;
    }
  }

_handlePersistentFrameCallback存储在_persistentCallbacks中。而_handlePersistentFrameCallback的核心在drawFrame()上。从drawFrame()的实现我们可得知,实际上是通过pipelineOwner去执行layoutpaint等一些列操作。相关具体实现我们在之后的章节进行分析。_handlePersistentFrameCallback将在handleDrawFrame()方法中进行调用,而handleDrawFrame()其中一个调用时机是在scheduleWarmUpFrame()中。这个方法看上去非常熟悉,可以回到最初的runApp()

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

我们找到了这条链路,之后再做分析。到此为止,WidgetsFlutterBinding.ensureInitialized()的过程就分析完了。我们总结一下:

  1. 创建了几个重要的对象:管理渲染管道的PipelineOwner,将Flutter Framework和宿主操作系统连接起来的PlatformDispatcher,根节点RenderView和真正储存到scene里的绘制内容TransformLayer。其中RenderView继承RenderObject,是我们三棵树之一的RenderObject树的根节点,是真正处理布局和绘制的对象。
  2. 再将它们关联起来:将App的根节点RenderView的内容存储在TransformLayer上,交由PipelineOwner进行调度。PipelineOwner最终会调用PlatformDispatcher的scheduleFrame()通知native层进行渲染。

总结成如下示意图:

截屏2022-12-30 11.44.51.png

之前我们提到过,RendererBindingWidgetsBinding与我们的三棵树有关,我们分析了刚刚分析了RendererBindinginitInstances()方法,得到了第一棵RenderObject树,接下来看一下WidgetsBinding的initInstances()方法的实现。

2.1.2. WidgetsBinding

  @override
  void initInstances() {
    super.initInstances();
//...
    _buildOwner = BuildOwner();
    buildOwner!.onBuildScheduled = _handleBuildScheduled;
//...
  void _handleBuildScheduled() {
//...
    ensureVisualUpdate();
  }

WidgetsBinding最重要的就是创建了一个BuildOwner对象,BuildOwner是用来管理Widget framework的,它有个onBuildScheduledcallBack,它的实现是我们上个章节分析过的ensureVisualUpdate()。而onBuildScheduled的调用在其scheduleBuildFor(Element element)中,被Elementactivate()markNeedsBuild()调用。关于BuildOwner我们将在下一章节scheduleAttachRootWidget(app)进行说明。

2.2.scheduleAttachRootWidget(app)

2.2.1.Widget树和Element树

先看它的实现:

  @protected
  void scheduleAttachRootWidget(Widget rootWidget) {
    Timer.run(() {
      attachRootWidget(rootWidget);
    });
  }

  void attachRootWidget(Widget rootWidget) {
    final bool isBootstrapFrame = renderViewElement == null;
    _readyToProduceFrames = true;
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
    if (isBootstrapFrame) {
      SchedulerBinding.instance.ensureVisualUpdate();
    }
  }

先创建了一个RenderObjectToWidgetAdapter对象。RenderObjectToWidgetAdapter继承RenderObjectWidget,继承Widget

  RenderObjectToWidgetAdapter({
    this.child,
    required this.container,
    this.debugShortDescription,
  }) : super(key: GlobalObjectKey(container));

而它的child对应的rootWidget就是我们App的根widget:MyApp()container是我们上一章节分析过的RenderView
由此可见这个RenderObjectToWidgetAdapter其实就是我们三棵树之一:Widget树的根节点。到此为止,三棵树中的两棵已经明了了,最后一棵Element树在哪儿呢?_renderViewElement看上去像是Element树。那我们继续看attachToRenderTree()的实现:

  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
    if (element == null) {
      owner.lockState(() {
        element = createElement();
        assert(element != null);
        element!.assignOwner(owner);
      });
      owner.buildScope(element!, () {
        element!.mount(null, null);
      });
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element!;
  }

RenderObjectToWidgetElement继承Element,我们来看createElement()的实现:

  @override
  RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);

实际上就是创建了一个Element对象,到此为止三棵树都出现了:

  • Widget树:RenderObjectToWidgetAdapter对象。
  • Element树:RenderObjectToWidgetElement对象。
  • RenderObject树:RenderView对象。

我们也可以很清晰的看见RenderObjectToWidgetElementRenderObjectToWidgetAdapterRenderObject连接了起来,将这三棵树一一对应。
我们再看element!.assignOwner(owner)

  void assignOwner(BuildOwner owner) {
    _owner = owner;
  }

Element持有了BuildOwner对象,用来管理dirty element列表。接着分析owner.buildScope()的实现,代码很长,有两个重要节点:
1.回调callback,实现element!.mount(null, null)mount()的作用是将当前element添加到树中parent节点上。

  1. element.rebuild():让widget更新自己。

2.2.2.mount()

我们先看看mount()方法是怎么将自己挂载到parent上的:

//RenderObjectToWidgetElement
  @override
  void mount(Element? parent, Object? newSlot) {
    assert(parent == null);
    super.mount(parent, newSlot);
    _rebuild();
    assert(_child != null);
  }

父类RootRenderObjectElement的实现:

//RootRenderObjectElement
  @override
  void mount(Element? parent, Object? newSlot) {
    // Root elements should never have parents.
    assert(parent == null);
    assert(newSlot == null);
    super.mount(parent, newSlot);
  }
}

父类RenderObjectElement的实现:

//RenderObjectElement
  @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);
    _dirty = false;
  }

父类Element的实现:

//Element
void mount(Element? parent, Object? newSlot) {
    assert(_lifecycleState == _ElementLifecycle.initial);
    assert(widget != null);
    assert(_parent == null);
    assert(parent == null || parent._lifecycleState == _ElementLifecycle.active);
    assert(slot == null);
    _parent = parent;
    _slot = newSlot;
    _lifecycleState = _ElementLifecycle.active;
    _depth = _parent != null ? _parent!.depth + 1 : 1;
    if (parent != null) {
      // Only assign ownership if the parent is non-null. If parent is null
      // (the root node), the owner should have already been assigned.
      // See RootRenderObjectElement.assignOwner().
      _owner = parent.owner;
    }
    assert(owner != null);
    final Key? key = widget.key;
    if (key is GlobalKey) {
      owner!._registerGlobalKey(key, this);
    }
    _updateInheritance();
    attachNotificationTree();
  }

由于每个子类都是先调用super方法,所以我们从父类Element的实现倒着往回看:
首先将_lifecycleState设置为_ElementLifecycle.active。将parentBuildOwner对象赋值给每个Element节点,注册GlobalKey。然后调用_updateInheritance()_updateInheritance()的实现如下:

  void _updateInheritance() {
    assert(_lifecycleState == _ElementLifecycle.active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }

将父的_inheritedWidgets赋值给当前Element节点的_inheritedWidgetsInheritedWidgets是通过将组件和InheritedWidgets的依赖关系层层向下传递,来实现父与子的数据共享的。详见:https://www.jianshu.com/p/926fe12e4437
最后调用attachNotificationTree()

  @protected
  void attachNotificationTree() {
    _notificationTree = _parent?._notificationTree;
  }

将父的_notificationTree赋值给当前Element节点的_notificationTree_notificationTree的作用是让Widget树的所有节点收到notification,比如说ListView滚动相关的notification,会通过_notificationTreescroll状态层层传递下去。
不过当前我们mount()方法的传参parentnewSlot都是null,所以只会执行注册GlobalKey的逻辑。
我们再看子类RenderObjectElement的重要实现:

 _renderObject = (widget as RenderObjectWidget).createRenderObject(this);

创建并持有了一个RenderObject对象:

  @protected
  @factory
  RenderObject createRenderObject(BuildContext context);

其真正的实现在子类里:

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

这个container是不是很熟悉,它就是我们在创建RenderObjectToWidgetAdapter时传入的container,即上一个章节提到的RenderView。到此为止,Element是如何持有的RenderObject就明晰了。
createRenderObject()完成后,我们再回到RenderObjectElement,看到最后还有一行重要的实现attachRenderObject(newSlot),这个方法的作用是将当前RenderObject添加到RenderObject树中相应的位置。在分析完父类mount()方法的实现后,我们再回到子类RenderObjectToWidgetElement中继续看mount()方法的实现。还剩最后一行代码_rebuild()

  @pragma('vm:notify-debugger-on-exception')
  void _rebuild() {
    try {
      _child = updateChild(_child, (widget as RenderObjectToWidgetAdapter<T>).child, _rootChildSlot);
    } catch (exception, stack) {
      final FlutterErrorDetails details = FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'widgets library',
        context: ErrorDescription('attaching to the render tree'),
      );
      FlutterError.reportError(details);
      final Widget error = ErrorWidget.builder(details);
      _child = updateChild(null, error, _rootChildSlot);
    }
  }

追踪updateChild()的实现,传参(widget as RenderObjectToWidgetAdapter<T>).child是我们App的根WidgetMyApp()

  @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;
//....
      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);
        }
//...
        child.update(newWidget);
//...
        newChild = child;
      } else {
        deactivateChild(child);
//..
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }
//...
    return newChild;
  }

这个方法可以说是Element最核心的部分,作为连接WidgetRenderObject的桥梁,Widget作为布局的配置无论如何创建,更新,删除,都会通过ElementupdateChild()方法去做一遍处理,根据不同条件判断是否需要创建/更新/删除Element,看看是否有可以复用Element,从而达到节约资源的目的。它的实现逻辑如下:

  1. newWidgetnullchild不为null,则执行deactivateChild()deactivateChild()的实现如下:
  @protected
  void deactivateChild(Element child) {
    assert(child != null);
    assert(child._parent == this);
    child._parent = null;
    child.detachRenderObject();
    owner!._inactiveElements.add(child); // this eventually calls child.deactivate()
    assert(() {
      if (debugPrintGlobalKeyedWidgetLifecycle) {
        if (child.widget.key is GlobalKey) {
          debugPrint('Deactivated $child (keyed child of $this)');
        }
      }
      return true;
    }());
  }

将当前Element放到_inactiveElements列表中去,并将child ElementRenderObject解绑。

  1. 如果newWidgetnullchildnull,直接返回null
  2. 如果newWidget不为nullchild不为null,如果可以的话将会更新旧的child,返回child或创建一个新的Element

更新旧child的判断条件是:

hasSameSuperclass && child.widget == newWidget

或者

Widget.canUpdate(child.widget, newWidget)

Widget.canUpdate()实现如下:

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

判断runtimeTypekey是否相同。除此之外将会执行inflateWidget(newWidget, newSlot)创建新Element

  1. 如果newWidget不为nullchildnull,执行inflateWidget(newWidget, newSlot)创建新Element

如此看来inflateWidget()是个很重要的方法,它的注释如下:

/// Create an element for the given widget and add it as a child of this
/// element in the given slot.

Widget创建一个Element,并将它作为child添加在当前Element节点中。看上去这个步骤会将WidgetElement进行绑定,我们看看它的实现:

  @protected
  @pragma('vm:prefer-inline')
  Element inflateWidget(Widget newWidget, Object? newSlot) {
    //...
    final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(newWidget);
    if (isTimelineTracked) {
      Map<String, String>? debugTimelineArguments;
//...

    try {
      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);
          assert(newChild == updatedChild);
          return updatedChild!;
        }
      }
      final Element newChild = newWidget.createElement();
//...
      newChild.mount(this, newSlot);
      assert(newChild._lifecycleState == _ElementLifecycle.active);

      return newChild;
    } finally {
//...
    }
  }

先尝试通过_retakeInactiveElement(key, newWidget)去创建一个newChild

 Element? _retakeInactiveElement(GlobalKey key, Widget newWidget) {
    final Element? element = key._currentElement;
    if (element == null) {
      return null;
    }
    if (!Widget.canUpdate(element.widget, newWidget)) {
      return null;
    }
//...
    final Element? parent = element._parent;
    if (parent != null) {
//...
      parent.forgetChild(element);
      parent.deactivateChild(element);
    }
    assert(element._parent == null);
    owner!._inactiveElements.remove(element);
    return element;
  }

这个步骤其实就是在通过key来找是否有可以复用的Element,它存在上面我们提到的_inactiveElements列表中。如果有且Widget.canUpdate()的话就不要创建了,直接从缓存中拿来用即可。返回的newChild将会作为参数再次执行updateChild()方法, 这时Widget.canUpdate()true,将不会执行inflateWidget()方法,而是会更新旧的child
如果_retakeInactiveElement()没有找到可复用的Element返回为null,那么将会执行以下代码:

final Element newChild = newWidget.createElement();

就是为Widget创建相对应的Element对象。到这里我们终于看到了将WidgetElement关联起来的关键代码。最后执行newChild.mount(this, newSlot)。这个mount方法在上一章节我们分析过,将child Element挂载到当前Element节点上。总结一下:

  1. 在1.1章节分析WidgetsFlutterBinding.ensureInitialized(),我们得到了RenderObject树的根节点。本章节我们通过scheduleAttachRootWidget(app)得到了Widget树和Element树的根节点,分别是RenderObjectToWidgetAdapterRenderObjectToWidgetElement
  2. 通过 (widget as RenderObjectWidget).createRenderObject(this)将上一个章节提到的RenderView和当前Element进行绑定。
  3. 通过newWidget.createElement()WidgetElement关联起来。
  4. 最后把我们的根WidgetMyApp()通过mount()方法挂载到树的根节点上。

总结成如下示意图:

截屏2022-12-30 14.45.40.png

scheduleAttachRootWidget(app)分析完成了,我们最后再看看runApp()的最后一个执行方法:scheduleWarmUpFrame()

2.3.scheduleWarmUpFrame()

  void scheduleWarmUpFrame() {
//...
    Timer.run(() {
      assert(_warmUpFrame);
      handleBeginFrame(null);
    });
    Timer.run(() {
      assert(_warmUpFrame);
      handleDrawFrame();
//...
      resetEpoch();
      _warmUpFrame = false;
      if (hadScheduledFrame) {
        scheduleFrame();
      }
    });
//...
  }

重点在handleDrawFrame()方法中,这个我们在之前的章节分析过,核心在drawFrame()即真正的每一帧绘制过程。这个过程我们将在之后的章节进行梳理。

3.总结

通过分析runApp()方法,我们可得知在执行真正的绘制操作之前会创建好三棵树:Widget树,Element树,RenderObject树,并将三棵树进行关联。同时初始化好渲染管道,发送渲染信号等待真正的渲染过程。有了这基础的三棵树,在下一篇文章将详细分析Flutter的布局和绘制的流程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容