Flutter setState流程分析

介紹

Flutter中最經常使用的Widget是StatelessWidgetStatefulWidget,分別對應於無狀態的組件和有狀態的組件。而StatefulWidget中更新狀態的方法就是setState(fn),調用該方法後,會從新調用StatefulWidgetbuild方法從新構建組件,達到刷新界面的效果。那麼調用setState方法後,是經過什麼的樣流程走到build方法的呢?帶着這個疑惑經過閱讀源碼來分析StatefulWidget的更新流程。node

源碼解析

setState方法有一個fn參數,通常會在該函數中執行更新狀態的操做,在方法體內會首先同步執行fn函數。這個函數的返回值不能是Future類型,即不能是async異步函數。執行完fn函數後,調用_elementmarkNeedsBuild方法。markdown

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屬性中。app

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對象。less

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方法。async

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的相應方法。ide

'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請求再次刷新。post

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

WidgetBindingscheduleFrame會首先調用ensureFrameCallbacksRegistered方法確保window的回調函數以被註冊。再調用windowscheduleFrame的方法。ui

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操做,以更新顯示狀態。

相關文章
相關標籤/搜索