美文网首页
2023-03-22 flutter widget更新2

2023-03-22 flutter widget更新2

作者: 我是小胡胡123 | 来源:发表于2023-03-21 11:25 被阅读0次

在Flutter中,当调用setState方法时,Flutter会重新构建当前组件及其子组件的widget tree(也就是widget树),并将其与上一次构建的widget tree进行比较,从而确定哪些widget需要更新。

在widget tree比较时,Flutter会递归遍历新旧widget tree的节点,对比它们的类型和key属性是否相同。如果两个节点的类型相同且key相同,则Flutter认为它们是同一个widget,不需要更新,否则需要更新。

当一个widget需要更新时,Flutter会创建一个新的实例,并将它与旧的widget实例进行比较,以确定需要更新哪些部分。这个过程称为diff算法,也就是计算出新widget树和旧widget树的不同之处。

一旦确定需要更新哪些部分,Flutter会将这些更新打包成一个更新操作(也称为element),并添加到一个更新队列中。最后,Flutter会异步地执行这个更新队列,从而更新widget tree。

总之,当我们通过setState方法修改一个widget时,Flutter会递归地比较新旧widget tree的节点,并根据差异进行更新,从而实现界面的刷新。

setState -> markNeedsBuild -> scheduleBuildFor ->onBuildScheduled -> SchedulerBinding.ensureVisualUpdate -> SchedulerBinding.scheduleFrame(); -> platformDispatcher.scheduleFrame();

1、setState

setState 
  void setState(VoidCallback fn) {
    assert(fn != null);
    assert(() {
      if (_debugLifecycleState == _StateLifecycle.defunct) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() called after dispose(): $this'),
          ErrorDescription(
            'This error happens if you call setState() on a State object for a widget that '
            'no longer appears in the widget tree (e.g., whose parent widget no longer '
            'includes the widget in its build). This error can occur when code calls '
            'setState() from a timer or an animation callback.',
          ),
          ErrorHint(
            'The preferred solution is '
            'to cancel the timer or stop listening to the animation in the dispose() '
            'callback. Another solution is to check the "mounted" property of this '
            'object before calling setState() to ensure the object is still in the '
            'tree.',
          ),
          ErrorHint(
            'This error might indicate a memory leak if setState() is being called '
            'because another object is retaining a reference to this State object '
            'after it has been removed from the tree. To avoid memory leaks, '
            'consider breaking the reference to this object during dispose().',
          ),
        ]);
      }
      if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() called in constructor: $this'),
          ErrorHint(
            'This happens when you call setState() on a State object for a widget that '
            "hasn't been inserted into the widget tree yet. It is not necessary to call "
            'setState() in the constructor, since the state is already assumed to be dirty '
            'when it is initially created.',
          ),
        ]);
      }
      return true;
    }());
    final Object? result = fn() as dynamic;
    assert(() {
      if (result is Future) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() callback argument returned a Future.'),
          ErrorDescription(
            'The setState() method on $this was called with a closure or method that '
            'returned a Future. Maybe it is marked as "async".',
          ),
          ErrorHint(
            'Instead of performing asynchronous work inside a call to setState(), first '
            'execute the work (without updating the widget state), and then synchronously '
            'update the state inside a call to setState().',
          ),
        ]);
      }
      // We ignore other types of return values so that you can do things like:
      //   setState(() => x = 3);
      return true;
    }());
    _element!.markNeedsBuild();
  }

2、markNeedsBuild

3、scheduleBuildFor

scheduleBuildFor 方法实现:

  void scheduleBuildFor(Element element) {
    assert(element != null);
    assert(element.owner == this);
    assert(() {
      if (debugPrintScheduleBuildForStacks)
        debugPrintStack(label: 'scheduleBuildFor() called for $element${_dirtyElements.contains(element) ? " (ALREADY IN LIST)" : ""}');
      if (!element.dirty) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('scheduleBuildFor() called for a widget that is not marked as dirty.'),
          element.describeElement('The method was called for the following element'),
          ErrorDescription(
            'This element is not current marked as dirty. Make sure to set the dirty flag before '
            'calling scheduleBuildFor().',
          ),
          ErrorHint(
            'If you did not attempt to call scheduleBuildFor() yourself, then this probably '
            'indicates a bug in the widgets framework. Please report it:\n'
            '  https://github.com/flutter/flutter/issues/new?template=2_bug.md',
          ),
        ]);
      }
      return true;
    }());
    if (element._inDirtyList) {
      assert(() {
        if (debugPrintScheduleBuildForStacks)
          debugPrintStack(label: 'BuildOwner.scheduleBuildFor() called; _dirtyElementsNeedsResorting was $_dirtyElementsNeedsResorting (now true); dirty list is: $_dirtyElements');
        if (!_debugIsInBuildScope) {
          throw FlutterError.fromParts(<DiagnosticsNode>[
            ErrorSummary('BuildOwner.scheduleBuildFor() called inappropriately.'),
            ErrorHint(
              'The BuildOwner.scheduleBuildFor() method should only be called while the '
              'buildScope() method is actively rebuilding the widget tree.',
            ),
          ]);
        }
        return true;
      }());
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled!();
    }
    _dirtyElements.add(element);
    element._inDirtyList = true;
    assert(() {
      if (debugPrintScheduleBuildForStacks)
        debugPrint('...dirty list is now: $_dirtyElements');
      return true;
    }());
  }

主要是是

  • onBuildScheduled
  • _dirtyElements.add(element);

onBuildScheduled -> WidgetsBinding._handleBuildScheduled

  void _handleBuildScheduled() {
    // If we're in the process of building dirty elements, then changes
    // should not trigger a new frame.
    assert(() {
      if (debugBuildingDirtyElements) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('Build scheduled during frame.'),
          ErrorDescription(
            'While the widget tree was being built, laid out, and painted, '
            'a new frame was scheduled to rebuild the widget tree.',
          ),
          ErrorHint(
            'This might be because setState() was called from a layout or '
            'paint callback. '
            'If a change is needed to the widget tree, it should be applied '
            'as the tree is being built. Scheduling a change for the subsequent '
            'frame instead results in an interface that lags behind by one frame. '
            'If this was done to make your build dependent on a size measured at '
            'layout time, consider using a LayoutBuilder, CustomSingleChildLayout, '
            'or CustomMultiChildLayout. If, on the other hand, the one frame delay '
            'is the desired effect, for example because this is an '
            'animation, consider scheduling the frame in a post-frame callback '
            'using SchedulerBinding.addPostFrameCallback or '
            'using an AnimationController to trigger the animation.',
          ),
        ]);
      }
      return true;
    }());
    ensureVisualUpdate();
  }

4、 ensureVisualUpdate

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

5、scheduleFrame

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

主要是:

  • ensureFrameCallbacksRegistered();
  • platformDispatcher.scheduleFrame();

6、ensureFrameCallbacksRegistered

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

7、platformDispatcher.scheduleFrame();

  void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';

最后调用native:

  ui.PlatformDispatcher get platformDispatcher => ui.PlatformDispatcher.instance;

platformDispatcher.scheduleFrame();->
platformDispatcher.onDrawFrame ??= _handleDrawFrame;
-> binding.dart void _handleDrawFrame()
-> binding.dart handleDrawFrame();
->

      for (final FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp!);

->

 @pragma('vm:notify-debugger-on-exception')
  void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace? callbackStack ])

->RenderBinding _handlePersistentFrameCallback

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

-> WidgetBinding void drawFrame()

  void drawFrame() {
    assert(!debugBuildingDirtyElements);
    assert(() {
      debugBuildingDirtyElements = true;
      return true;
    }());

    TimingsCallback? firstFrameCallback;
    if (_needToReportFirstFrame) {
      assert(!_firstFrameCompleter.isCompleted);

      firstFrameCallback = (List<FrameTiming> timings) {
        assert(sendFramesToEngine);
        if (!kReleaseMode) {
          // Change the current user tag back to the default tag. At this point,
          // the user tag should be set to "AppStartUp" (originally set in the
          // engine), so we need to change it back to the default tag to mark
          // the end of app start up for CPU profiles.
          developer.UserTag.defaultTag.makeCurrent();
          developer.Timeline.instantSync('Rasterized first useful frame');
          developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
        }
        SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback!);
        firstFrameCallback = null;
        _firstFrameCompleter.complete();
      };
      // Callback is only invoked when FlutterView.render is called. When
      // sendFramesToEngine is set to false during the frame, it will not be
      // called and we need to remove the callback (see below).
      SchedulerBinding.instance.addTimingsCallback(firstFrameCallback!);
    }

    try {
      if (renderViewElement != null)
        buildOwner!.buildScope(renderViewElement!);
      super.drawFrame();
      buildOwner!.finalizeTree();
    } finally {
      assert(() {
        debugBuildingDirtyElements = false;
        return true;
      }());
    }
    if (!kReleaseMode) {
      if (_needToReportFirstFrame && sendFramesToEngine) {
        developer.Timeline.instantSync('Widgets built first useful frame');
      }
    }
    _needToReportFirstFrame = false;
    if (firstFrameCallback != null && !sendFramesToEngine) {
      // This frame is deferred and not the first frame sent to the engine that
      // should be reported.
      _needToReportFirstFrame = true;
      SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback!);
    }
  }

-> buildOwner!.buildScope(renderViewElement!);

  • 最后调用到buildOwner!.buildScope(renderViewElement!);

8、buildScope

 @pragma('vm:notify-debugger-on-exception')
  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);
  }

核心方法是调用 element.rebuild();
循环遍历 dirty 脏标记的 element 调用rebuild方法

   while (index < dirtyCount) {
        final Element element = _dirtyElements[index];
      element.rebuild();
}
  • setState的那个StatefullWidget
  • rebuild
  • performRebuild
  • build widget
  • updateChild 比较2个widget
  • update -》rebuild
  • rebuild 到 child widget 递归遍历子widget
  • inflateWidget 创建新newWidget.createElement();
  • newChild.mount(this, newSlot)
  • _firstBuild-》rebuild
  • parentDataWidget.applyParentData(renderObject);
  • markNeedsLayout
  • owner!.requestVisualUpdate();
void requestVisualUpdate() {
    onNeedVisualUpdate?.call();
  }
  • ensureVisualUpdate
  void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
  }

SchedulerBinding.scheduleFrame();
最后又回到了scheduleFrame

看看别人的分析吧:

image.png

Flutter 核心渲染流程分析 [完结篇]: https://juejin.cn/post/6973818961724964901
Flutter渲染机制—UI线程: http://gityuan.com/2019/06/15/flutter_ui_draw/

相关文章

  • Flutter控件之间联动方式整理

    这里说的Flutter中Widget之间的联动方式是指一个Widget更新后,另一个Widget得到响应并更新,我...

  • flutter学习资源

    一、我的github的flutter中的demo项目,持续更新中。。。 flutter_widget组件生命周期的...

  • Flutter widget更新原理

    一、背景 我们在写StatefulWidget的时候有时候会遇到数据改变了,也刷新(setState)了,但是数据...

  • StatefulWidget 和 StatelessWidget

    如何更新 Widget 在 Android 中可以直接通过 View 来更新它们的状态。但是在 Flutter 中...

  • flutter的渲染原理

    flutter的4颗树1.Widget树,Widget相互关联形成的书2.Element树,根据Widget树生成...

  • flutter笔记汇总

    Widget 基础Widget flutter笔记(一)-----官方示例&代码解读 flutter笔记(二)-...

  • flutter-基础组件

    1.在Flutter中几乎所有的对象都是widget。2.在Flutter中widget的作用是描述一个UI元素的...

  • Flutter之Widget

    Widget说明 在Flutter中几乎所有的对象都是一个 widget。Flutter 中是通过 Widget ...

  • Flutter Widget

    Flutter Widget 1、显示小部件2、更新小部件的状态信息3、摆放小部件4、添加、删除小部件5、小部件设...

  • Flutter中的widget和其生命周期

    1.Widget 在Flutter中所有的组成都是widget,可以成为组件或者控件Widget可以分为2类Sta...

网友评论

      本文标题:2023-03-22 flutter widget更新2

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