Flutter setState流程分析

大師兄要瀟灑發表於2020-06-02

介紹

Flutter中最常用的Widget是StatelessWidgetStatefulWidget,分別對應於無狀態的元件和有狀態的元件。而StatefulWidget中更新狀態的方法就是setState(fn),呼叫該方法後,會重新呼叫StatefulWidgetbuild方法重新構建元件,達到重新整理介面的效果。那麼呼叫setState方法後,是通過什麼的樣流程走到build方法的呢?帶著這個疑惑通過閱讀原始碼來分析StatefulWidget的更新流程。

原始碼解析

setState方法有一個fn引數,一般會在該函式中執行更新狀態的操作,在方法體內會首先同步執行fn函式。這個函式的返回值不能是Future型別,即不能是async非同步函式。執行完fn函式後,呼叫_elementmarkNeedsBuild方法。

void setState(VoidCallback fn) {
    ...
    final dynamic 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().'
          ),
        ]);
      }
      return true;
    }());
    _element.markNeedsBuild();
  }
複製程式碼

StatefulWidget對應的Element是StatefulElement,在StatefulElement中的構造方法中會通過StatefulWidgetcreateState建立State,同時將element本身設定給State_element屬性。而State也被儲存在Element_state屬性中。

  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    ...
    _state._element = this;
    ...
    _state._widget = widget;
    assert(_state._debugLifecycleState == _StateLifecycle.created);
  }
複製程式碼

Element markNeedsBuild

void markNeedsBuild() {
    ...
    if (!_active)
      return;
    ...
    if (dirty)
      return;
    _dirty = true;
    owner.scheduleBuildFor(this);
  }
複製程式碼

markNeedsBuild方法會呼叫ownerscheduleBuildFor方法,將該element標記為dirty,並且將element加入到一個全域性的表示需要更新的Element列表中。ownerBuildOwner物件。

BuildOwner scheduleBuildFor

void scheduleBuildFor(Element element) {
    ...
    if (element._inDirtyList) {
      ...
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled();
    }
    _dirtyElements.add(element); 
    element._inDirtyList = true;
    ...
  }
複製程式碼

這個方法主要執行幾個任務

  1. 判斷element是否已經加入到_dirtyElements列表中,如果已經在列表中,就直接返回,不用再執行下面的操作。
  2. 判斷_scheduledFlushDirtyElements是否為false,這個變數表示當前是否正在rebuild_dirtyElements中的元素。如果沒有正在rebuild,並且onBuildScheduled回撥不為空,就呼叫onBuildScheduled函式。
  3. 將element加入到_dirtyElements中,並且標記element的_inDirtyListtrue,表示已經加入到髒元素列表。

通過搜尋可以查到,BuildOwner是在WdigetBindinginitInstances方法中建立的,並且建立完成後設定了onBuildScheduled回撥為WidgetsBinding的_handleBuildScheduled方法。所以scheduleBuildFor方法又會呼叫到WidgetsBinding_handleBuildScheduled方法。

WdigetBinding _handleBuildScheduled

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    ...
    // Initialization of [_buildOwner] has to be done after
    // [super.initInstances] is called, as it requires [ServicesBinding] to
    // properly setup the [defaultBinaryMessenger] instance.
    _buildOwner = BuildOwner();
    buildOwner.onBuildScheduled = _handleBuildScheduled;
    window.onLocaleChanged = handleLocaleChanged;
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
  }
複製程式碼
void _handleBuildScheduled() {
    // If we're in the process of building dirty elements, then changes
    // should not trigger a new frame.
    ...
    ensureVisualUpdate();
  }
複製程式碼

_handleBuildScheduled呼叫ensureVisualUpdate注意ensureVisualUpdate並不是WidgetsBinding中的方法,而是SchedulerBinding中的方法,WidgetsBindingSchedulerBinding都是mixin,被整合在WidgetsFlutterBinding類中,在應用啟動執行runApp函式時會進行初始化。在dart中,一個類同時引入多個mixin,根據with的順序,最右邊的優先順序更高。mixin有個線性化處理,如果右邊的mixin重寫了某一方法,並且在重寫方法中呼叫了super.overrideMethod(),就會呼叫其左邊的mixin的相應方法。

'Dart中的Mixins通過建立一個新類來實現,該類將mixin的實現層疊在一個超類之上以建立一個新類 ,它不是“在超類中”,而是在超類的“頂部”,因此如何解決查詢問題不會產生歧義。
— Lasse R. H. Nielsen on StackOverflow.'

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

SchedulerBinding scheduleFrame

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

ensureVisualUpdate方法會通過SchedulerPhase列舉類判斷當前的重新整理狀態。一共有五種狀態 狀態的轉變流程為 transientCallbacks -> midFrameMicrotasks -> persistentCallbacks -> postFrameCallbacks -> idle 通過後面的分析,可以知道真正的重新整理過程是在persistentCallbacks狀態完成的。 所以,如果上次重新整理已經完成(postFrameCallbacksidle狀態),就會呼叫scheduleFrame請求再次重新整理。

void scheduleFrame() {
    if (_hasScheduledFrame || !framesEnabled)
      return;
    ...
    ensureFrameCallbacksRegistered();
    window.scheduleFrame();
    _hasScheduledFrame = true;
  }
複製程式碼

WidgetBindingscheduleFrame會首先呼叫ensureFrameCallbacksRegistered方法確保window的回撥函式以被註冊。再呼叫windowscheduleFrame的方法。

void ensureFrameCallbacksRegistered() {
    window.onBeginFrame ??= _handleBeginFrame;
    window.onDrawFrame ??= _handleDrawFrame;
}
複製程式碼
/// Requests that, at the next appropriate opportunity, the [onBeginFrame]
/// and [onDrawFrame] callbacks be invoked.
void scheduleFrame() native 'Window_scheduleFrame';
複製程式碼

WindowscheduleFrame方法是個native方法,通過上面的註釋,可以知道呼叫該方法後,onBeginFrame回撥和onDrawFrame回被呼叫。這兩個回撥已經通過ensureFrameCallbacksRegistered設定為WidgetBinding_handleBeginFrame_handleDrawFrame方法。我們重點看下_handleDrawFrame方法。

void _handleDrawFrame() {
    if (_ignoreNextEngineDrawFrame) {
      _ignoreNextEngineDrawFrame = false;
      return;
    }
    handleDrawFrame();
  }
複製程式碼
/// Called by the engine to produce a new frame.
///
/// This method is called immediately after [handleBeginFrame]. It calls all
/// the callbacks registered by [addPersistentFrameCallback], which typically
/// drive the rendering pipeline, and then calls the callbacks registered by
/// [addPostFrameCallback].
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 {
      _schedulerPhase = SchedulerPhase.idle;
      ...
      _currentFrameTimeStamp = null;
    }
  }
複製程式碼

handleDrawFrame方法上面的註釋已經說了該方法的作用,被引擎呼叫建立一個新的幀。這個方法流程也比較清晰,首先會迴圈執行_persistentCallbacks中的callback,這裡的callback可以通過WidgetsBinding.instance.addPersistentFrameCallback(fn)註冊;然後,再複製一份_postFrameCallbacks的拷貝,並將原_postFrameCallbacks列表清空,_postFrameCallbacks中儲存重繪後執行的回撥函式,並且只執行一次,可以通過WidgetsBinding.instance.addPostFrameCallback(fn)新增回撥。執行完_persistentCallbacks_postFrameCallbacks後,便將狀態設定為SchedulerPhase.idle表示已經重新整理過。

通過註釋可以知道是通過addPersistentFrameCallback來驅動渲染的。通過搜尋,可以看到在RendererBindinginitInstances方法中註冊了persistentFrameCallback回撥。

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    ...
    initRenderView();
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    initMouseTracker();
  }
}
複製程式碼

_handlePersistentFrameCallback回撥函式中直接呼叫了drawFrame方法。

void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
    _mouseTracker.schedulePostFrameCheck();
  }
複製程式碼
@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;
    }
}
複製程式碼

需要注意的是,WidgetsBinding也實現了drawFrame,並且WidgetsBinding在被mixin到WidgetsFlutterBinding類時是在最右邊,所以它的方法優先順序最高。_handlePersistentFrameCallback中呼叫drawFrame方法時,會先呼叫WidgetsBinding中的drawFrame方法。

WidgetsBinding drawFrame

@override
  void drawFrame() {
    ...  
    try {
      if (renderViewElement != null)
        buildOwner.buildScope(renderViewElement);
      super.drawFrame();
      buildOwner.finalizeTree();
    } finally {
      assert(() {
        debugBuildingDirtyElements = false;
        return true;
      }());
    }
    ...
  }
複製程式碼

WidgetsBindingdrawFrame方法中,先呼叫了buildOwnerbuildScope方法,然後再呼叫了super.drawFrame(),通過super.drawFrame()可以呼叫到RendererBindingdrawFrame方法。先看buildOwnerbuildScope方法。

BuildOwner buildScope

void buildScope(Element context, [ VoidCallback callback ]) {
    if (callback == null && _dirtyElements.isEmpty)
      return;
    ...
    try {
      _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) {
            // 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;
          }
        }
      }
      ...
    } finally {
      for (final Element element in _dirtyElements) {
        assert(element._inDirtyList);
        element._inDirtyList = false;
      }
      _dirtyElements.clear();
      _scheduledFlushDirtyElements = false;
      _dirtyElementsNeedsResorting = null;
      ...
    }
  }
複製程式碼

buildScope的核心邏輯就是,首先對_dirtyElements按照深度進行排序,再遍歷_dirtyElements列表,呼叫其中元素的rebuild方法。rebuild方法定義在Element類中。

void rebuild() {
    ...
    performRebuild();
    ...
  }
複製程式碼
@protected
  void performRebuild();
複製程式碼

performRebuildElement類中的抽象方法,各個子類會實現該方法。StateElement的父類是ComponentElement,先看ComponentElementperformRebuild方法

@override
  void performRebuild() {
    ...
    Widget built;
    try {
      ..
      built = build();
      ..
    } catch (e, stack) {
      _debugDoingBuild = false;
      built = ErrorWidget.builder(
        _debugReportException(
          ErrorDescription('building $this'),
          e,
          stack,
          informationCollector: () sync* {
            yield DiagnosticsDebugCreator(DebugCreator(this));
          },
        ),
      );
    } finally {
      ...
    }
    try {
      _child = updateChild(_child, built, slot);
      assert(_child != null);
    } catch (e, stack) {
      ...
      _child = updateChild(null, built, slot);
    }
    ...
  }
複製程式碼

在這個方法中,直接呼叫build方法建立Widget,如果build方法產生異常,就會建立一個ErrorWidget,就是經常看到的紅色警告介面。呼叫完build方法後,會再呼叫updateChild(_child, built, slot)更新子Widget。 StatelessElementStatefulElement重寫了build方法,分別呼叫了WidgetStatebuild方法。

///StatelessElement
@override
Widget build() => widget.build(this);
複製程式碼
@override
Widget build() => _state.build(this);
複製程式碼

前面提到WidgetsBindingdrawFrame方法會通過super.drawFrame()呼叫到RendererBindingdrawFrame方法,再回頭看RendererBindingdrawFrame方法。

@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;
    }
  }
複製程式碼

RendererBindingdrawFrame方法,通過pipelineOwner物件重新layout和paint,已達到更新UI的效果。

總結

StatefulWidget通過setState方法將其對應的StatefulElement新增到BuildOwnerdirtyElements中,並觸發一次重新整理。在收到重新整理回撥後,遍歷dirtyElements中的元素,執行rebuild操作,以更新顯示狀態。

相關文章