美文网首页
2023-03-21 flutter widget更新1

2023-03-21 flutter widget更新1

作者: 我是小胡胡分胡 | 来源:发表于2023-03-20 15:19 被阅读0次
    • Element ->rebuild
    • StatefulElement,ComponentElement ->performRebuild
    • Element ->updateChild
    • StatefulElement ->update

    在Flutter中,Widget树是通过递归遍历来构建和绘制的。当你构建一个Widget树时,Flutter会自动遍历每个Widget并调用它的build方法,这个方法返回一个新的Widget或者一组Widget。Flutter然后将这些Widget添加到树中,并重复此过程,直到所有Widget都被构建完成。

    在Flutter中,Widget树是一个有向无环图(DAG),因为每个Widget只能在Widget树中出现一次。因此,Flutter会跟踪每个Widget的父级和子级,并使用这些信息来构建整个Widget树。

    当你更新Widget树时,Flutter会比较新旧Widget树的差异,并将这些差异应用到底层的渲染树中。这样可以最小化绘制的成本,并提高应用程序的性能。

    总之,Flutter中的Widget树是通过递归遍历构建和绘制的,这个过程可以自动处理。通过比较新旧Widget树的差异,Flutter可以最小化绘制的成本,并提高应用程序的性能。

    在 Flutter 中,当使用 setState 函数时,Flutter 框架会重新调用 build 方法来重构 widget 树。在重构 widget 树时,Flutter 框架会比较新旧 widget 树的差异,然后只会更新有变化的部分。

    在比较 widget 树时,Flutter 框架会逐层比较新旧 widget 树的节点。当比较到两个节点不相同时,Flutter 框架会认为它们代表不同的 widget,并对其进行更新。Flutter 框架比较 widget 节点是否相同的规则如下:

    如果两个节点的类型不同,则认为它们代表不同的 widget。
    如果两个节点的 key 不同,则认为它们代表不同的 widget。
    如果两个节点的类型和 key 都相同,但它们的 props(包括属性值和回调函数)不相同,则认为它们代表不同的 widget。
    当比较到两个节点相同时,Flutter 框架会递归比较它们的子节点,直到比较完整个 widget 树。在比较的过程中,如果某个节点及其子节点不需要更新,则会被复用,从而提高应用的性能。

    主要流程为:


    未命名文件 (1).jpg

    1、buildScope

      void buildScope(Element context, [ VoidCallback? callback ]) {
        if (callback == null && _dirtyElements.isEmpty)
          return;
        assert(context != null);
        assert(_debugStateLockLevel >= 0);
        assert(!_debugBuilding);
        assert(() {
          if (debugPrintBuildScope)
            debugPrint('buildScope called with context $context; dirty list is: $_dirtyElements');
          _debugStateLockLevel += 1;
          _debugBuilding = true;
          return true;
        }());
        if (!kReleaseMode) {
          Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
          assert(() {
            if (debugProfileBuildsEnabled) {
              debugTimelineArguments = <String, String>{
                ...debugTimelineArguments,
                'dirty count': '${_dirtyElements.length}',
                'dirty list': '$_dirtyElements',
                'lock level': '$_debugStateLockLevel',
                'scope context': '$context',
              };
            }
            return true;
          }());
          Timeline.startSync(
            'BUILD',
            arguments: debugTimelineArguments
          );
        }
        try {
          _scheduledFlushDirtyElements = true;
          if (callback != null) {
            assert(_debugStateLocked);
            Element? debugPreviousBuildTarget;
            assert(() {
              context._debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
              debugPreviousBuildTarget = _debugCurrentBuildTarget;
              _debugCurrentBuildTarget = context;
              return true;
            }());
            _dirtyElementsNeedsResorting = false;
            try {
              callback();
            } finally {
              assert(() {
                context._debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
                assert(_debugCurrentBuildTarget == context);
                _debugCurrentBuildTarget = debugPreviousBuildTarget;
                _debugElementWasRebuilt(context);
                return true;
              }());
            }
          }
          _dirtyElements.sort(Element._sort);
          _dirtyElementsNeedsResorting = false;
          int dirtyCount = _dirtyElements.length;
          int index = 0;
          while (index < dirtyCount) {
            final Element element = _dirtyElements[index];
            assert(element != null);
            assert(element._inDirtyList);
            assert(() {
              if (element._lifecycleState == _ElementLifecycle.active && !element._debugIsInScope(context)) {
                throw FlutterError.fromParts(<DiagnosticsNode>[
                  ErrorSummary('Tried to build dirty widget in the wrong build scope.'),
                  ErrorDescription(
                    'A widget which was marked as dirty and is still active was scheduled to be built, '
                    'but the current build scope unexpectedly does not contain that widget.',
                  ),
                  ErrorHint(
                    'Sometimes this is detected when an element is removed from the widget tree, but the '
                    'element somehow did not get marked as inactive. In that case, it might be caused by '
                    'an ancestor element failing to implement visitChildren correctly, thus preventing '
                    'some or all of its descendants from being correctly deactivated.',
                  ),
                  DiagnosticsProperty<Element>(
                    'The root of the build scope was',
                    context,
                    style: DiagnosticsTreeStyle.errorProperty,
                  ),
                  DiagnosticsProperty<Element>(
                    'The offending element (which does not appear to be a descendant of the root of the build scope) was',
                    element,
                    style: DiagnosticsTreeStyle.errorProperty,
                  ),
                ]);
              }
              return true;
            }());
            final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(element.widget);
            if (isTimelineTracked) {
              Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
              assert(() {
                if (kDebugMode) {
                  debugTimelineArguments = element.widget.toDiagnosticsNode().toTimelineArguments();
                }
                return true;
              }());
              Timeline.startSync(
                '${element.widget.runtimeType}',
                arguments: debugTimelineArguments,
              );
            }
            try {
              element.rebuild();
            } catch (e, stack) {
              _debugReportException(
                ErrorDescription('while rebuilding dirty elements'),
                e,
                stack,
                informationCollector: () => <DiagnosticsNode>[
                  if (kDebugMode && index < _dirtyElements.length)
                    DiagnosticsDebugCreator(DebugCreator(element)),
                  if (index < _dirtyElements.length)
                    element.describeElement('The element being rebuilt at the time was index $index of $dirtyCount')
                  else
                    ErrorHint('The element being rebuilt at the time was index $index of $dirtyCount, but _dirtyElements only had ${_dirtyElements.length} entries. This suggests some confusion in the framework internals.'),
                ],
              );
            }
            if (isTimelineTracked)
              Timeline.finishSync();
            index += 1;
            if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
              _dirtyElements.sort(Element._sort);
              _dirtyElementsNeedsResorting = false;
              dirtyCount = _dirtyElements.length;
              while (index > 0 && _dirtyElements[index - 1].dirty) {
                // It is possible for previously dirty but inactive widgets to move right in the list.
                // We therefore have to move the index left in the list to account for this.
                // We don't know how many could have moved. However, we do know that the only possible
                // change to the list is that nodes that were previously to the left of the index have
                // now moved to be to the right of the right-most cleaned node, and we do know that
                // all the clean nodes were to the left of the index. So we move the index left
                // until just after the right-most clean node.
                index -= 1;
              }
            }
          }
          assert(() {
            if (_dirtyElements.any((Element element) => element._lifecycleState == _ElementLifecycle.active && element.dirty)) {
              throw FlutterError.fromParts(<DiagnosticsNode>[
                ErrorSummary('buildScope missed some dirty elements.'),
                ErrorHint('This probably indicates that the dirty list should have been resorted but was not.'),
                Element.describeElements('The list of dirty elements at the end of the buildScope call was', _dirtyElements),
              ]);
            }
            return true;
          }());
        } finally {
          for (final Element element in _dirtyElements) {
            assert(element._inDirtyList);
            element._inDirtyList = false;
          }
          _dirtyElements.clear();
          _scheduledFlushDirtyElements = false;
          _dirtyElementsNeedsResorting = null;
          if (!kReleaseMode) {
            Timeline.finishSync();
          }
          assert(_debugBuilding);
          assert(() {
            _debugBuilding = false;
            _debugStateLockLevel -= 1;
            if (debugPrintBuildScope)
              debugPrint('buildScope finished');
            return true;
          }());
        }
        assert(_debugStateLockLevel >= 0);
      }
    
    

    这段代码是 Flutter 框架中的一部分,主要用于构建元素树,该元素树用于描述应用程序 UI 中的所有小部件。它接受一个元素作为上下文,并且构建该元素及其子元素。它还接受一个可选的回调函数,可以在构建期间调用。该函数是在构建上下文的锁定状态下执行的,并且它会重建在_dirtyElements列表中的所有元素,然后将其排序并逐个重建。该函数主要用于在调试模式下记录时间线事件,并在遇到错误时输出详细的错误信息。

    2、Element ->rebuild

     void rebuild() {
        assert(_lifecycleState != _ElementLifecycle.initial);
        if (_lifecycleState != _ElementLifecycle.active || !_dirty)
          return;
        assert(() {
          debugOnRebuildDirtyWidget?.call(this, _debugBuiltOnce);
          if (debugPrintRebuildDirtyWidgets) {
            if (!_debugBuiltOnce) {
              debugPrint('Building $this');
              _debugBuiltOnce = true;
            } else {
              debugPrint('Rebuilding $this');
            }
          }
          return true;
        }());
        assert(_lifecycleState == _ElementLifecycle.active);
        assert(owner!._debugStateLocked);
        Element? debugPreviousBuildTarget;
        assert(() {
          debugPreviousBuildTarget = owner!._debugCurrentBuildTarget;
          owner!._debugCurrentBuildTarget = this;
          return true;
        }());
        performRebuild();
        assert(() {
          assert(owner!._debugCurrentBuildTarget == this);
          owner!._debugCurrentBuildTarget = debugPreviousBuildTarget;
          return true;
        }());
        assert(!_dirty);
      }
    

    这段代码实现了 Flutter 中 Element 类的 rebuild 方法,用于重新构建当前元素和其子树的 Widget。该方法的执行流程和原理如下:

    • 首先,通过断言判断当前元素的生命周期状态是否为 active,并且当前元素是否标记为 dirty。如果不是,则直接返回。

    • 然后,如果开启了调试模式,会调用 debugOnRebuildDirtyWidget 回调函数和输出打印信息,以便开发者进行调试。

    • 接下来,通过断言判断当前元素的生命周期状态是否为 active,并且当前元素是否处于被所属 StatefulElement 对象锁定的状态。

    • 再通过断言记录当前元素之前被锁定的目标元素,将当前元素设置为锁定的目标元素。

    • 调用 performRebuild 方法进行重构。

    • 最后,通过断言判断当前元素是否还标记为 dirty,如果不是,则说明重构成功。

    在 performRebuild 方法中,会进行实际的重构操作。首先,将 _dirty 标记为 false,以防止重复重构。然后调用 build 方法生成新的 Widget,并通过 updateChild 方法将新生成的 Widget 更新到子树中。如果在生成 Widget 或更新子树的过程中出现了异常,会通过 ErrorWidget.builder 方法生成一个错误的 Widget。在每次重构前后,都会通过断言记录一些调试信息,方便开发者进行调试。

    3、performRebuild

    void performRebuild() {
        assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
        Widget? built;
        try {
          assert(() {
            _debugDoingBuild = true;
            return true;
          }());
          built = build();
          assert(() {
            _debugDoingBuild = false;
            return true;
          }());
          debugWidgetBuilderValue(widget, built);
        } catch (e, stack) {
          _debugDoingBuild = false;
          built = ErrorWidget.builder(
            _debugReportException(
              ErrorDescription('building $this'),
              e,
              stack,
              informationCollector: () => <DiagnosticsNode>[
                if (kDebugMode)
                  DiagnosticsDebugCreator(DebugCreator(this)),
              ],
            ),
          );
        } finally {
          // We delay marking the element as clean until after calling build() so
          // that attempts to markNeedsBuild() during build() will be ignored.
          _dirty = false;
          assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
        }
        try {
          _child = updateChild(_child, built, slot);
          assert(_child != null);
        } catch (e, stack) {
          built = ErrorWidget.builder(
            _debugReportException(
              ErrorDescription('building $this'),
              e,
              stack,
              informationCollector: () => <DiagnosticsNode>[
                if (kDebugMode)
                  DiagnosticsDebugCreator(DebugCreator(this)),
              ],
            ),
          );
          _child = updateChild(null, built, slot);
        }
      }
    

    这段代码是Flutter中Element类的performRebuild方法的实现。Element表示一个可重建的widget的渲染元素,而performRebuild方法则是在需要进行重建时被调用,它的主要作用是执行widget的build方法,并根据其返回值更新子元素。下面是代码的执行流程和原理:

    • 首先设置调试标志_debugSetAllowIgnoredCallsToMarkNeedsBuild为true,这允许在build方法执行期间忽略markNeedsBuild调用。
    • 然后执行build方法,如果有异常则使用ErrorWidget.builder构建一个ErrorWidget代替原有的widget,并在控制台输出异常信息。如果没有异常,则将返回值存储在built变量中,并输出调试信息。
    • build方法执行完成后,将_dirty标志设置为false,表示该Element的状态已经是最新的。
    • 调用updateChild方法,将其子元素_child和新的built作为参数传入。updateChild方法会根据built和_child的关系,更新、替换或新增子元素。如果updateChild方法发生异常,则使用ErrorWidget.builder构建一个ErrorWidget代替原有的widget,并在控制台输出异常信息。
    • 最后,将调试标志_debugSetAllowIgnoredCallsToMarkNeedsBuild重新设置为false,以确保不会再忽略markNeedsBuild调用。

    在整个执行过程中,performRebuild方法主要涉及两个重要的方法:build和updateChild。build方法执行widget的构建逻辑,而updateChild方法则根据widget的变化更新Element的子元素。如果有异常发生,则使用ErrorWidget.builder构建一个ErrorWidget代替原有的widget,并在控制台输出异常信息,以帮助开发者调试。此外,由于build方法执行过程中会忽略markNeedsBuild调用,因此在执行完build方法后需要重新设置_debugSetAllowIgnoredCallsToMarkNeedsBuild标志,以确保能够正常响应后续markNeedsBuild调用。

    4、updateChild

       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;
          // When the type of a widget is changed between Stateful and Stateless via
          // hot reload, the element tree will end up in a partially invalid state.
          // That is, if the widget was a StatefulWidget and is now a StatelessWidget,
          // then the element tree currently contains a StatefulElement that is incorrectly
          // referencing a StatelessWidget (and likewise with StatelessElement).
          //
          // To avoid crashing due to type errors, we need to gently guide the invalid
          // element out of the tree. To do so, we ensure that the `hasSameSuperclass` condition
          // returns false which prevents us from trying to update the existing element
          // incorrectly.
          //
          // For the case where the widget becomes Stateful, we also need to avoid
          // accessing `StatelessElement.widget` as the cast on the getter will
          // cause a type error to be thrown. Here we avoid that by short-circuiting
          // the `Widget.canUpdate` check once `hasSameSuperclass` is false.
          assert(() {
            final int oldElementClass = Element._debugConcreteSubtype(child);
            final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
            hasSameSuperclass = oldElementClass == newWidgetClass;
            return true;
          }());
          if (hasSameSuperclass && child.widget == newWidget) {
            // We don't insert a timeline event here, because otherwise it's
            // confusing that widgets that "don't update" (because they didn't
            // change) get "charged" on the timeline.
            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 = timelineArgumentsIndicatingLandmarkEvent;
              assert(() {
                if (kDebugMode) {
                  debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
                }
                return true;
              }());
              Timeline.startSync(
                '${newWidget.runtimeType}',
                arguments: debugTimelineArguments,
              );
            }
            child.update(newWidget);
            if (isTimelineTracked)
              Timeline.finishSync();
            assert(child.widget == newWidget);
            assert(() {
              child.owner!._debugElementWasRebuilt(child);
              return true;
            }());
            newChild = child;
          } else {
            deactivateChild(child);
            assert(child._parent == null);
            // The [debugProfileBuildsEnabled] code for this branch is inside
            // [inflateWidget], since some [Element]s call [inflateWidget] directly
            // instead of going through [updateChild].
            newChild = inflateWidget(newWidget, newSlot);
          }
        } else {
          // The [debugProfileBuildsEnabled] code for this branch is inside
          // [inflateWidget], since some [Element]s call [inflateWidget] directly
          // instead of going through [updateChild].
          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;
      }
    
    

    这段代码是 Flutter 框架中的一部分,用于更新 Widget 树中的某个子节点 Element。以下是代码执行的大致流程和原理:

    • 检查 newWidget 是否为空,如果为空则调用 deactivateChild 方法来移除 child,并返回 null。
    • 如果 child 不为空,首先检查 child 和 newWidget 是否有相同的父类。如果不是,则不能更新 child,需要调用 deactivateChild 方法将其从树中移除。如果有相同的父类,则继续执行下一步。
    • 如果 child 和 newWidget 类型相同且能够进行更新,则调用 child.update(newWidget) 来更新 child 的状态,并将 newSlot 作为其新的插槽。
    • 如果类型不同或者无法更新,则调用 deactivateChild 方法将其从树中移除,并调用 inflateWidget 方法来创建一个新的子节点 Element。
    • 如果 child 为空,则调用 inflateWidget 方法来创建一个新的子节点 Element。
      在更新完成后,检查 newWidget 是否有 GlobalKey,如果有,则将其保留到全局 key 表中,以便在后续的渲染中使用。

    总的来说,这段代码的作用是更新 Widget 树中的某个子节点 Element,并保证更新的正确性和性能。它会根据传入的 newWidget 和 child 的类型以及更新策略,决定是否更新 child,创建一个新的子节点 Element,或者将 child 从树中移除。同时,它也会保留全局 GlobalKey,以便在后续的渲染中使用。

    这段代码比较重要,用于判断widget移除和重新调用inflateWidget创建新节点,进而依次通过inflateWidget-》mount-〉_firstBuild-> state.initState() 重新调用新的widget state的 initState方法

    5、update

    image.png

    这么多种Eelement子类的不同的update的实现方法,挑一个StatefulElement做分析:

    void update(StatefulWidget newWidget) {
        super.update(newWidget);
        assert(widget == newWidget);
        final StatefulWidget oldWidget = state._widget!;
        // 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 {
          _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
          final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
          assert(() {
            if (debugCheckForReturnedFuture is Future) {
              throw FlutterError.fromParts(<DiagnosticsNode>[
                ErrorSummary('${state.runtimeType}.didUpdateWidget() returned a Future.'),
                ErrorDescription( 'State.didUpdateWidget() must be a void method without an `async` keyword.'),
                ErrorHint(
                  'Rather than awaiting on asynchronous work directly inside of didUpdateWidget, '
                  'call a separate method to do this work without awaiting it.',
                ),
              ]);
            }
            return true;
          }());
        } finally {
          _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
        }
        rebuild();
      }
    

    这段代码是 Flutter 框架中 StatefulWidget 类的一个方法 update 的实现。下面是代码的执行流程和原理的解释:

    • 首先,该方法会调用父类 State 的 update 方法来更新 State 对象的关联的 widget 对象,以便重新构建该 State 对象的对应的 Element 对象。

    • 接下来,该方法会通过断言来验证传入的 newWidget 是否和当前对象的 widget 属性相等,以确保 widget 对象没有被意外更改。

    • 然后,该方法会保存当前 State 对象关联的旧 widget 对象。

    • 接下来,该方法会将 _dirty 标志设置为 true,表示该 State 对象需要重新构建。

    • 接着,该方法会将 state._widget 属性更新为当前对象的 widget 属性,以确保该 State 对象的 widget 属性是最新的。

    • 接下来,该方法会调用当前 State 对象的 didUpdateWidget 方法,以通知该 State 对象关联的 widget 对象已经更新。

    • 如果 didUpdateWidget 方法返回了一个 Future 对象,则会抛出一个异常,因为 didUpdateWidget 方法必须是一个无返回值的方法。

    • 最后,该方法会调用 rebuild 方法,以请求重新构建该 State 对象对应的 Element 对象,从而更新该 State 对象的子树。

    相关文章

      网友评论

          本文标题:2023-03-21 flutter widget更新1

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