zoukankan      html  css  js  c++  java
  • flutter_5_深入_1_深入widget树和构建流程

    Build流程

    BuildOwner.buildScope() 会有两种调用时机:

    • 树构建(应用启动时):runApp() 方法调用的 scheduleAttachRootWidget() 方法,它会构建Widgets Tree、Element Tree与RenderObject Tree三棵树。
    • 树更新(帧绘制与更新时):这里不会重新构建三棵树,而是只会更新dirty区域的Element。

    也即是说树的构建和更新都是由 BuildOwner.buildScope() 方法来完成的。它们的差别在于树构建的时候传入了一个 element.mount(null, null) 回调。在 buildScope() 过程中会触发这个回调。

    这个回调会构建三棵树,为什么会有三棵树呢,因为Widget只是对UI元素的一个抽象描述,我们需要先将其inflate成Element,然后生成对应的RenderObject来驱动渲染,如下所示:

    • Widget Tree:为Element描述需要的配置,调用createElement方法创建Element,决定Element是否需要更新。Flutter通过查分算法比对Widget树前后的变化,来决定Element的State是否改变。
    • Element Tree:表示Widget Tree特定位置的一个实例,调用createRenderObject创建RenderObject,同时持有Widget和RenderObject,负责管理Widget的配置和RenderObjec的渲染。Element的状态由Flutter维护,开发人员只需要维护Widget即可。
    • RenderObject Tree:RenderObject绘制,测量和绘制节点,布局子节点,处理输入事件。

    首次

    整个流程挺简单的,就从runApp方法一直往下跟就可以了。

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

    WidgetsBinding.attachRootWidget

    void attachRootWidget(Widget rootWidget) {
      _readyToProduceFrames = true;
      _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
        container: renderView,
        debugShortDescription: '[root]',
        child: rootWidget,
      ).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
    }

    RenderObjectToWidgetAdapter继承自RenderObjectWidget,从RenderObject到Element树的桥梁。由runApp用于引导应用程序。

    注意此时的renderViewElement 肯定是null。

    RenderObjectToWidgetAdapter.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);
        });
        // This is most likely the first time the framework is ready to produce
        // a frame. Ensure that we are asked for one.
        SchedulerBinding.instance.ensureVisualUpdate();
      } else {
        element._newWidget = this;
        element.markNeedsBuild();
      }
      return element;
    }

    createElement创建的element是RenderObjectToWidgetElement,他是一个RootRenderObjectElement,也就是根element。

    element.mount(null, null);会向下遍历并构建整个widget树。

    RenderObjectToWidgetElement.mount

    @override
    void mount(Element parent, dynamic newSlot) {
      assert(parent == null);
      super.mount(parent, newSlot);
      _rebuild();
    }

    RenderObjectToWidgetElement._rebuild

    void _rebuild() {
      try {
        _child = updateChild(_child, widget.child, _rootChildSlot);
      } catch (exception, stack) {
      }
    }

    此时的widget就是RenderObjectToWidgetAdapter,它的widget.child就是runApp传递进去的widget。

    Element.updateChild

    Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
      。。。
      Element newChild;
      if (child != null) {
        。。。
      } else {
        newChild = inflateWidget(newWidget, newSlot);
      }
    
    
      return newChild;
    }

    由于是第一次,Element child是null,执行else里的逻辑,inflateWidget使用子widget来创建一个子Element。

    此时的newWidget是runApp传递进去的widget。

    Element.inflateWidget

    Element inflateWidget(Widget newWidget, dynamic newSlot) {
      assert(newWidget != null);
      final Key key = newWidget.key;
      if (key is GlobalKey) {
        final Element newChild = _retakeInactiveElement(key, newWidget);
        if (newChild != null) {
          assert(newChild._parent == 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);
      return newChild;
    }

    有两步,

    1. 如果是GlobalKey的话,会先从GlobalKey中获取引用的Element,如果有 有效的element的话就复用,

    2. 如果 不是GlobalKey 或 没有从GlobalKey中获取到element 的话,就用widget调用其createElement()来创建了一个element,接着就把新建的子element挂载到了element树上。

    element.mount主要有两个流程,

    • 一个是ComponentElement的,
    • 一个是RenderObjectElement的,

    ComponentElement.mount

    void mount(Element parent, dynamic newSlot) {
      super.mount(parent, newSlot);
      assert(_child == null);
      assert(_active);
      _firstBuild();
      assert(_child != null);
    }

    super.mount会把parent记录在此element中。

    firstBuild会调用到performRebuild方法:

    void performRebuild() {
      Widget built;
      try {
        built = build();
      } catch (e, stack) {
      } finally {
      }
      try {
        _child = updateChild(_child, built, slot);
        assert(_child != null);
      } catch (e, stack) {
      }
    }

    此方法会首先调用build方法,来创建其子widget,ComponentElement主要有两类stateful和stateless,会有不同的实现。

    然后就又调用到了updateChild方法,这就回到了上边流程,所以其实就是一直往下遍历创建widget树。

    RenderObjectElement.mount

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

    此方法会创建一个RenderObject,

    之后就会把此RenderObject添加到RenderObject树上。

    attachRenderObject

    void attachRenderObject(dynamic newSlot) {
      assert(_ancestorRenderObjectElement == null);
      _slot = newSlot;
      _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
      _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
    
      final ParentDataElement<ParentData> parentDataElement = _findAncestorParentDataElement();
      if (parentDataElement != null)
        _updateParentData(parentDataElement.widget);
    }

    1. _findAncestorRenderObjectElement往上找离它最近的父RenderObjectElement,这个和_parent可能不一样;然后把当前的renderObject给插入到父RenderObjectElement中。

    2. 接着_findAncestorParentDataElement(),它的作用是找到离它最近的父ParentDataElement,并用ParentDataWidget上的数据更新此renderObject上的ParentData数据(具体作用可以看flutter2_widget_6_其他.docx 中的ParentDataWidget)。

    状态更新时

    标记element dirty

    一般状态更新是StatefulWidget才可以的,所以这里分析StatefulWidget。

    我们通过调用State.setState来标识当前需要更新子widget。

    void setState(VoidCallback fn) {
      final dynamic result = fn() as dynamic;
      _element.markNeedsBuild();
    }

    可以看到会先执行传递进来的函数类型参数,然后调用markNeedsBuild来标记此element需要更新其子widget。

    void markNeedsBuild() {
      if (!_active)
        return;
    
      if (dirty)
        return;
      _dirty = true;
      owner.scheduleBuildFor(this);
    }

    此方法首先会判断是否已经调用过了,_dirty会在之后的rebuild后重新设置为false。

    owner是BuildOwner,用来管理widget框架。此类用于追踪需要rebuilding的widget,并作为一个整体处理应用于widget树的其他任务,比如管理树的 inactive element列表,以及在调试时hot reload期间在必要时触发“reassemble”命令。

    一般BuildOwner是由WidgetsBinding持有,并且与 build/layout/paint管道的其余部分一起被操作系统驱动。额外的BuildOwner可以被构建来管理off-screen widget trees。

    要把一个BuildOwner分配给一个Element树,请使用RootRenderObjectElement.assignOwner 方法。

    BuildOwner.scheduleBuildFor

    void scheduleBuildFor(Element element) {
      if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
        _scheduledFlushDirtyElements = true;
        onBuildScheduled();
      }
      _dirtyElements.add(element);
      element._inDirtyList = true;
    }

    首先调用onBuildScheduled()方法,此方法是一个回调,在WidgetsBinding中赋值的,

    然后把此element加入到了_dirtyElements列表中了。

    WidgetsBinding.initInstances

    void initInstances() {
      super.initInstances();
      _instance = this;
    
      _buildOwner = BuildOwner();
      buildOwner.onBuildScheduled = _handleBuildScheduled;
    }

    回到上边onBuildScheduled()的调用,会调用到ensureVisualUpdate()

    void _handleBuildScheduled() {
      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;
    
      ensureFrameCallbacksRegistered();
      window.scheduleFrame();
      _hasScheduledFrame = true;
    }

    最后会去调用底层window.scheduleFrame()来注册一个下一帧时回调,就类似于Android中的ViewRootImpl.scheduleTraversals()。

    重建dirty Element的子widget

    下一帧来到后,会调用到的方法是在上边的ensureFrameCallbacksRegistered()中注册的回调,

    SchedulerBinding.ensureFrameCallbacksRegistered

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

    onBeginFrame :主要是用来执行动画,

    onDrawFrame :这个主要处理上边说的persistentCallbacks

    void handleDrawFrame() {
      try {
        // PERSISTENT FRAME CALLBACKS
        _schedulerPhase = SchedulerPhase.persistentCallbacks;
        for (final FrameCallback callback in _persistentCallbacks)
          _invokeFrameCallback(callback, _currentFrameTimeStamp!);
    
        // POST-FRAME CALLBACKS
        _schedulerPhase = SchedulerPhase.postFrameCallbacks;
        final List<FrameCallback> localPostFrameCallbacks =
            List<FrameCallback>.from(_postFrameCallbacks);
        _postFrameCallbacks.clear();
        for (final FrameCallback callback in localPostFrameCallbacks)
          _invokeFrameCallback(callback, _currentFrameTimeStamp!);
      } finally {
      }
    }

    看下persistentCallbacks列表在哪添加的callback

    最终找到是在RendererBinding.initInstances中添加的callback,

    void initInstances() {
      super.initInstances();
      _instance = this;
      _pipelineOwner = PipelineOwner(
        onNeedVisualUpdate: ensureVisualUpdate,
        onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
        onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
      );
    
      initRenderView();
    
      addPersistentFrameCallback(_handlePersistentFrameCallback);
      initMouseTracker();
    }

    WidgetsFlutterBinding继承了RendererBinding。

    接着上边handleDrawFrame的流程:

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

    WidgetBinding重载了drawFrame,把build流程加入进来了。

    @override
    void drawFrame() {
    
      try {
        if (renderViewElement != null)
          buildOwner.buildScope(renderViewElement);
        super.drawFrame();
        buildOwner.finalizeTree();
      } finally {
      }
    }

    buildOwner.finalizeTree();

    buildOwner.buildScope

    void buildScope(Element context, [ VoidCallback callback ]) {
      if (callback == null && _dirtyElements.isEmpty)
        return;
      try {
        _scheduledFlushDirtyElements = true;
        if (callback != null) {
          assert(_debugStateLocked);
          Element debugPreviousBuildTarget;
          _dirtyElementsNeedsResorting = false;
          try {
    // 可以添加一个回调在build之前执行。
            callback();
          } finally {
          }
        }
        _dirtyElements.sort(Element._sort);
        _dirtyElementsNeedsResorting = false;
        int dirtyCount = _dirtyElements.length;
        int index = 0;
        while (index < dirtyCount) {
          try {
            _dirtyElements[index].rebuild();
          } catch (e, stack) {
          }
          index += 1;
          if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
            _dirtyElements.sort(Element._sort);
            _dirtyElementsNeedsResorting = false;
            dirtyCount = _dirtyElements.length;
            while (index > 0 && _dirtyElements[index - 1].dirty) {
              index -= 1;
            }
          }
        }
      } finally {
        for (final Element element in _dirtyElements) {
          element._inDirtyList = false;
        }
        _dirtyElements.clear();
        _scheduledFlushDirtyElements = false;
        _dirtyElementsNeedsResorting = null;
      }
    }

    可以看到会遍历dirtyElements列表中的element.rebuild()。

    而element.rebuild()最终会调用到performRebuild()。

    Element.performRebuild

    接着会根据不同类型的element去 重建子widget 或 重建子element。

    ComponentElement.performRebuild

    void performRebuild() {
      Widget built;
      try {
        built = build();
      } catch (e, stack) {
    
      } finally {
        // We delay marking the element as clean until after calling build() so
        // that attempts to markNeedsBuild() during build() will be ignored.
        _dirty = false;
      }
      try {
        _child = updateChild(_child, built, slot);
      } catch (e, stack) {
      }
    }

    会先调用build创建一个子widget,然后调用Element.updateChild来更新。

    RenderObjectElement.performRebuild

    void performRebuild() {
      widget.updateRenderObject(this, renderObject);
      _dirty = false;
    }

    可以看到RenderObjectElement只是更新widget的配置。

    Element.updateChild

    Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
      if (newWidget == null) {
        if (child != null)
          deactivateChild(child);
        return null;
      }
      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);
          child.update(newWidget);
    
          newChild = child;
        } else {
          deactivateChild(child);
          newChild = inflateWidget(newWidget, newSlot);
        }
      } else {
        newChild = inflateWidget(newWidget, newSlot);
      }
    
      return newChild;
    }

    由于不是第一次调用,所以Element child不是null,进入if逻辑,会进行三个判断:

    • 如果子widget不变,子element和子widget匹配(就是是否都是stateless,或都是stateful),那么更新slot。
    • 如果子element和子widget匹配,但子widget发生了变化,就调用子element.update(newWidget)来更新widget配置。
    • 最后一个判断是子element和子widget不匹配,那么就把老的child element加入到一个_inactiveElements列表中,然后进行重建element。

    element.update

    @mustCallSuper
    void update(covariant Widget newWidget) {
      _widget = newWidget;
    }

    会把newWidget记录下来,

    ComponentElement.update

    • StatelessElement.update
    @override
    void update(StatelessWidget newWidget) {
      super.update(newWidget);
      assert(widget == newWidget);
      _dirty = true;
      rebuild();
    }

    最后又调用rebuild,rebuild中会调用performRebuild()去重建其子widget。

    • StatefulElement.update

    @override

    @override
    void update(StatefulWidget newWidget) {
      super.update(newWidget);
      assert(widget == newWidget);
      final StatefulWidget oldWidget = _state._widget;
      // Notice that we mark ourselves as dirty before calling didUpdateWidget to
      // let authors call setState from within didUpdateWidget without triggering
      // asserts.
      _dirty = true;
      _state._widget = widget as StatefulWidget;
      try {
        final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
      } finally {
      }
      rebuild();
    }

    回调state.didUpdateWidget,

    最后又调用rebuild,rebuild中会调用performRebuild()去重建其子widget。

    RenderObjectElement.update

    RenderObjectElement.update

    @override
    void update(covariant RenderObjectWidget newWidget) {
      super.update(newWidget);
      assert(widget == newWidget);
      widget.updateRenderObject(this, renderObject);
      _dirty = false;
    }

    可以看下RenderObjectElement的几个重要的子类:

    • SingleChildRenderObjectElement.update
    void update(SingleChildRenderObjectWidget newWidget) {
      super.update(newWidget);
      assert(widget == newWidget);
      _child = updateChild(_child, widget.child, null);
    }
    • MultiChildRenderObjectElement.update
    void update(MultiChildRenderObjectWidget newWidget) {
      super.update(newWidget);
      assert(widget == newWidget);
      _children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
      _forgottenChildren.clear();
    }

    unmount流程

    在上边重建流程中,如果子element和子widget不匹配,那么就把老的child element加入到一个_inactiveElements列表中。

    为什么要先加入到这里边,而不是直接unmount呢?

    这么做的原因一般是element的parent发生了改变,需要先从原来的树上移出(也就是加入到_inactiveElements中),然后在此帧后续的操作中如果真正需要复用时从列表中再取出,如果后续不需要那么就会在整个构建流程执行完时,框架会把这个列表中的element都给unmount了。

    经过搜索发现只有GlobalKey使用了这一逻辑。

    具体逻辑在,BuildOwner.finalizeTree()

    void finalizeTree() {
      Timeline.startSync('Finalize tree', arguments: timelineArgumentsIndicatingLandmarkEvent);
      try {
        lockState(() {
          _inactiveElements._unmountAll(); // this unregisters the GlobalKeys
        });
      } catch (e, stack) {
        // Catching the exception directly to avoid activating the ErrorWidget.
        // Since the tree is in a broken state, adding the ErrorWidget would
        // cause more exceptions.
        _debugReportException(ErrorSummary('while finalizing the widget tree'), e, stack);
      } finally {
        Timeline.finishSync();
      }
    }

    这个逻辑就是每帧的处理流程说的The finalization phase。

    对于StatefulElement的unmount,里边会调用state.dispose():

    void unmount() {
      super.unmount();
      _state.dispose();
      _state._element = null;
      _state = null;
    }
  • 相关阅读:
    fork()和vfork()的区别(转载)
    Linux中fork()函数详解(转载)
    ERROR:Simulator861-Failed to link the design解决办法
    ISE 14.7安装教程最新版(Win10安装)
    实验2用户及文件权限管理
    检验
    实验1基本概念及操作
    日常学习笔记(2)
    日常笔记1
    拷贝初始化的几种情况
  • 原文地址:https://www.cnblogs.com/muouren/p/14245605.html
Copyright © 2011-2022 走看看