Flutter setState之后做了什么?

标签: 编程学习 Flutter学习

先看源码

  • setState接收一个回调,可以看到这个函数里面大部分都是断言判断,只有最后的 _element.markNeedsBuild(); 才是核心

    void setState(VoidCallback fn) {
    //fn不为空
    assert(fn != null);
    assert(() {
       //当前State的状态不能为_StateLifecycle.defunct,因为该状态dispose函数已经调用
      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().'
          ),
        ]);
      }
      //State创建也需要调用mounted函数添加到widget树上
      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;
    }());
    //将回调fn转为dynamic动态类型
    final dynamic result = fn() as dynamic;
    assert(() {
      //因为setState函数在下一帧就会重绘,而 Future 是异步的,并不能确定具体的重绘时间
      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;
    }());
    //调用 markNeedsBuild 函数
    _element.markNeedsBuild();
    }
  • markNeedsBuild 这里面大部分也是断言,主要是根据一些条件判断,然后把当前 Element 标记为 _dirty,再去调用 scheduleBuildFor 函数

    void markNeedsBuild() {
    //与setState一样先判断 State 状态
    assert(_debugLifecycleState != _ElementLifecycle.defunct);
    //当前页处于未激活状态
    if (!_active)
      return;
    //所有者为空
    assert(owner != null);
    //当前状态为激活状态
    assert(_debugLifecycleState == _ElementLifecycle.active);
    assert(() {
      //此Widget树是否出于构建阶段
      if (owner._debugBuilding) {
        //当前构建的目标不能等于null
        assert(owner._debugCurrentBuildTarget != null);
        //调用 BuildOwner.lockState 函数 _debugStateLockLevel 会增加,当前 BuildOwner 会锁定 State
        assert(owner._debugStateLocked);
       // 调用 _debugIsInScope 函数判断当前构建的目标是否在构建域中
        if (_debugIsInScope(owner._debugCurrentBuildTarget))
          return true;
        //_debugAllowIgnoredCallsToMarkNeedsBuild 该标志位为false时,也就是在 initState、didUpdateWidget 和 build 函数中调用 setState 函数都会报错。
        if (!_debugAllowIgnoredCallsToMarkNeedsBuild) {
          final List<DiagnosticsNode> information = <DiagnosticsNode>[
            ErrorSummary('setState() or markNeedsBuild() called during build.'),
            ErrorDescription(
              'This ${widget.runtimeType} widget cannot be marked as needing to build because the framework '
              'is already in the process of building widgets.  A widget can be marked as '
              'needing to be built during the build phase only if one of its ancestors '
              'is currently building. This exception is allowed because the framework '
              'builds parent widgets before children, which means a dirty descendant '
              'will always be built. Otherwise, the framework might not visit this '
              'widget during this build phase.'
            ),
            describeElement(
              'The widget on which setState() or markNeedsBuild() was called was',
            ),
          ];
          if (owner._debugCurrentBuildTarget != null)
            information.add(owner._debugCurrentBuildTarget.describeWidget('The widget which was currently being built when the offending call was made was'));
          throw FlutterError.fromParts(information);
        }
        assert(dirty); // can only get here if we're not in scope, but ignored calls are allowed, and our call would somehow be ignored (since we're already dirty)
      } else if (owner._debugStateLocked) {
        //状态已经锁定 会报错
        assert(!_debugAllowIgnoredCallsToMarkNeedsBuild);
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() or markNeedsBuild() called when widget tree was locked.'),
          ErrorDescription(
            'This ${widget.runtimeType} widget cannot be marked as needing to build '
            'because the framework is locked.'
          ),
          describeElement('The widget on which setState() or markNeedsBuild() was called was'),
        ]);
      }
      return true;
    }());
    //如果该 Element 已经被标志位 dirty
    if (dirty)
      return;
    //把当前 Element 的 _dirty 设置为 true
    _dirty = true;
    //调用 owner.scheduleBuildFor(Element) 函数
    owner.scheduleBuildFor(this);
    }
  • scheduleBuildFor 函数
    void scheduleBuildFor(Element element) {
    // 断言 Element 不能为空
    assert(element != null);
    //element 的 BuildOwner 对象必须是当前对象
    assert(element.owner == this);
    assert(() {
      if (debugPrintScheduleBuildForStacks)
        debugPrintStack(label: 'scheduleBuildFor() called for $element${_dirtyElements.contains(element) ? " (ALREADY IN LIST)" : ""}');
      //当前 Element 的 dirty 标记状态
      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=BUG.md'
          ),
        ]);
      }
      return true;
    }());
    //element 已经处于 dirty 列表中
    if (element._inDirtyList) {
      assert(() {
        if (debugPrintScheduleBuildForStacks)
          debugPrintStack(label: 'BuildOwner.scheduleBuildFor() called; _dirtyElementsNeedsResorting was $_dirtyElementsNeedsResorting (now true); dirty list is: $_dirtyElements');
        //_debugIsInBuildScope 等于 true 时,才可以调用 scheduleBuildFor 函数
        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;
      }());
      // 需要排序 Element 树
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled();
    }
    // 将 element 添加到 dirty 元素列表中
    _dirtyElements.add(element);
    // 将 element 的_ inDirtyList 标记为true
    element._inDirtyList = true;
    assert(() {
      if (debugPrintScheduleBuildForStacks)
        debugPrint('...dirty list is now: $_dirtyElements');
      return true;
    }());
    }

总结

大概过程如下

  • setState 传递函数不为null; 当前的状态判断,

    enum _StateLifecycle {
    /// The [State] object has been created. [State.initState] is called at this
    /// time.
    created,
    
    /// The [State.initState] method has been called but the [State] object is
    /// not yet ready to build. [State.didChangeDependencies] is called at this time.
    initialized,
    
    /// The [State] object is ready to build and [State.dispose] has not yet been
    /// called.
    ready,
    
    /// The [State.dispose] method has been called and the [State] object is
    /// no longer able to build.
    defunct,
    }

    传递的函数不能是future; 最后调用markNeedsBuild(),下一帧时当前 State 中 Element 需要重新build。

  • markNeedsBuild 判断_debugLifecycleState的状态

    enum _ElementLifecycle {
    initial,
    active,
    inactive,
    defunct,
    }

    当前Element的owner和active断言,active在deactivate函数被置为false,在mounted和active被置为true,所以调用setState的时机需要注意; debug模式下的断言; 如果当前Element._dirty已经为true的话返回; 调用owner.scheduleBuildFor(this)函数。

  • scheduleBuildFor 将element加入到BuildOwner的dirty列表中,WidgetBinding在下一帧时会通过drawFrame绘制该element。 element的_dirty标志位置为true,加入到dirty列表中,并且已经mount、active 且未 deactivate、dispose。 下一帧该Element会通过RenderObject在其区域重绘。

注意

setState((){}) 会让整个Widget重新绘制,所以如果一个Widget需要reBuild,那么它的子节点、兄弟节点、兄弟节点的子节点应该尽可能少,最好让setState所处的区块尽量小,否则会导致不必要的重绘。所以要尽量少用 StatefulWidget, 优先使用 StatelessWidget,含有大量子 Widget(如根布局、次根布局)慎用 StatefulWidget,尽量在叶子节点使用 StatefulWidget。