Flutter - setState更新機制

基于Flutter 1.22.3的源碼刨析柿隙,分析Flutter的StatefulWidget的UI更新機制实撒,相關(guān)源碼:

  widgets/framework.dart
  widgets/binding.dart
  scheduler/binding.dart
  lib/ui/window.dart
  flutter/runtime/runtime_controller.cc

一、概述

對于Flutter來說扒腕,萬物皆為Widget擂达,常見的Widget子類為StatelessWidget(無狀態(tài))和StatefulWidget(有狀態(tài));

  • StatelessWidget:內(nèi)部沒有保存狀態(tài)胶滋,界面創(chuàng)建后不會發(fā)生改變板鬓;
  • StatefulWidget:內(nèi)部有保存狀態(tài),當狀態(tài)發(fā)生改變究恤,調(diào)用setState()方法會觸發(fā)StatefulWidget的UI發(fā)生更新俭令,對于自定義繼承自StatefulWidget的子類,必須要重寫createState()方法部宿。

接下來看看setState()究竟干了哪些操作抄腔。

二、Widget更新流程

2.1 setState

[-> framework.dart::state]

abstract class State<T extends StatefulWidget> with Diagnosticable {
StatefulElement _element;
  /// It is an error to call this method after the framework calls [dispose].
  /// You can determine whether it is legal to call this method by checking
  /// whether the [mounted] property is true.
  @protected
  void setState(VoidCallback fn) {
   ...
    _element.markNeedsBuild();
  }
}

這里需要注意setState()方法要在dispose()方法調(diào)用前調(diào)用,可以通過mounted屬性值來判斷父Widget是否還包含該Widget.

2.2 markNeedsBuild

[->framework.dart::Element]

abstract class Element extends DiagnosticableTree implements BuildContext {
  /// Marks the element as dirty and adds it to the global list of widgets to
  /// rebuild in the next frame.
  ///標記元素為臟元素理张,然后添加到list集合里 等下一幀刷新
    void markNeedsBuild() {
      if (!_active)
        return;
      if (dirty)
        return;
      _dirty = true;
      owner.scheduleBuildFor(this);
    }
}

設(shè)置 Element的 _dirty 為 true

2.3 scheduleBuildFor

[->framework.dart::BuildOwner]

abstract class Element extends DiagnosticableTree implements BuildContext {
  /// Adds an element to the dirty elements list so that it will be rebuilt
  /// when [WidgetsBinding.drawFrame] calls [buildScope].
 void scheduleBuildFor(Element element) {  
  ...
   if (element._inDirtyList) { //是否在集合里面
        _dirtyElementsNeedsResorting = true;
        return;
      }
      if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
        _scheduledFlushDirtyElements = true;
        onBuildScheduled();
      }
      _dirtyElements.add(element);//記錄所有臟元素
      element._inDirtyList = true;
  }
}

將element添加到臟element集合赫蛇,之后會被重建

2.4 _handleBuildScheduled

[-> binding.dart:: WidgetsBinding]


/// The glue between the widgets layer and the Flutter engine.
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() {
    ensureVisualUpdate();
 }

在Flutter應(yīng)用啟動過程初始化WidgetsBinding時,賦值onBuildScheduled等于_handleBuildScheduled()雾叭。

2.5 ensureVisualUpdate

[ -> binding.dart::SchedulerBinding]

mixin SchedulerBinding on BindingBase {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
  }

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

schedulerPhase的初始值為SchedulerPhase.idle悟耘。SchedulerPhase是一個enum枚舉類型,有以下5個可取值:

狀態(tài) 含義
idle 沒有正在處理的幀织狐,可能正在執(zhí)行的是WidgetsBinding.scheduleTask暂幼,scheduleMicrotask,Timer移迫,事件handlers旺嬉,或者其他回調(diào)等
transientCallbacks SchedulerBinding.handleBeginFrame過程, 處理動畫狀態(tài)更新
midFrameMicrotasks 處理transientCallbacks階段觸發(fā)的微任務(wù)(Microtasks)
persistentCallbacks WidgetsBinding.drawFrame和SchedulerBinding.handleDrawFrame過程厨埋,build/layout/paint流水線工作
postFrameCallbacks 主要是清理和計劃執(zhí)行下一幀的工作

2.6 scheduleFrame

   void scheduleFrame() {
    //只有當APP處于用戶可見狀態(tài)才會準備調(diào)度下一幀方法
    if (_hasScheduledFrame || !framesEnabled)
      return;
    ensureFrameCallbacksRegistered();
    window.scheduleFrame();
    _hasScheduledFrame = true;
  }

2.7 scheduleFrame

[ -> lib/ui/window.dart::Window]

  void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';

window是Flutter引擎中跟圖形相關(guān)接口打交道的核心類邪媳,這里是一個native方法

2.7.1 ScheduleFrame(C++)

[->引擎庫 lib/ui/window/platform_configuration.cc]

  void ScheduleFrame(Dart_NativeArguments args) {
  UIDartState::ThrowIfUIOperationsProhibited();
  UIDartState::Current()->platform_configuration()->client()->ScheduleFrame();
}

通過RegisterNatives()完成native方法的注冊,“PlatformConfiguration_scheduleFrame”所對應(yīng)的native方法如上所示荡陷。

2.7.2 RuntimeController::ScheduleFrame

[->runtime/runtime_controller.cc]

  // |PlatformConfigurationClient|
void RuntimeController::ScheduleFrame() {
  client_.ScheduleFrame();
}

2.7.3 Engine::ScheduleFrame

[->flutter/shell/common/engine.cc]

  void Engine::ScheduleFrame(bool regenerate_layer_tree) {
  animator_->RequestFrame(regenerate_layer_tree);
}

Engine::ScheduleFrame()經(jīng)過層層調(diào)用悲酷,最終會注冊Vsync回調(diào)。 等待下一次vsync信號的到來亲善,然后再經(jīng)過層層調(diào)用最終會調(diào)用到Window::BeginFrame()设易。這里不展開解釋了。

2.8 Window::BeginFrame

[-> flutter/lib/ui/window/window.cc]

  void Window::BeginFrame(fml::TimePoint frameTime) {
  std::shared_ptr<tonic::DartState> dart_state = library_.dart_state().lock();
  if (!dart_state)
    return;
  tonic::DartState::Scope scope(dart_state);

  int64_t microseconds = (frameTime - fml::TimePoint()).ToMicroseconds();

  DartInvokeField(library_.value(), "_beginFrame",
                  {
                      Dart_NewInteger(microseconds),
                  });

  //執(zhí)行MicroTask
  UIDartState::Current()->FlushMicrotasksNow();

  DartInvokeField(library_.value(), "_drawFrame", {});
}

Window::BeginFrame()過程主要工作:

  • 執(zhí)行_beginFrame
  • 執(zhí)行FlushMicrotasksNow
  • 執(zhí)行_drawFrame

可見蛹头,Microtask位于beginFrame和drawFrame之間顿肺,那么Microtask的耗時會影響ui繪制過程戏溺。

2.9handleBeginFrame

[-> lib/src/scheduler/binding.dart:: SchedulerBinding]

  void handleBeginFrame(Duration rawTimeStamp) {
  Timeline.startSync('Frame', arguments: timelineWhitelistArguments);
  _firstRawTimeStampInEpoch ??= rawTimeStamp;
  _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
  if (rawTimeStamp != null)
    _lastRawTimeStamp = rawTimeStamp;

  profile(() {
    _profileFrameNumber += 1;
    _profileFrameStopwatch.reset();
    _profileFrameStopwatch.start();
  });

  //此時階段等于SchedulerPhase.idle;
  _hasScheduledFrame = false;
  try {
    Timeline.startSync('Animate', arguments: timelineWhitelistArguments);
    _schedulerPhase = SchedulerPhase.transientCallbacks;
    //執(zhí)行動畫的回調(diào)方法
    final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
    _transientCallbacks = <int, _FrameCallbackEntry>{};
    callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
      if (!_removedIds.contains(id))
        _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
    });
    _removedIds.clear();
  } finally {
    _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
  }
}

該方法主要功能是遍歷_transientCallbacks,執(zhí)行相應(yīng)的Animate操作屠尊,可通過scheduleFrameCallback()/cancelFrameCallbackWithId()來完成添加和刪除成員旷祸,再來簡單看看這兩個方法。

2.10 handleDrawFrame

[-> lib/src/scheduler/binding.dart:: SchedulerBinding]

  void handleDrawFrame() {
  assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
  Timeline.finishSync(); // 標識結(jié)束"Animate"階段
  try {
    _schedulerPhase = SchedulerPhase.persistentCallbacks;
    //執(zhí)行PERSISTENT FRAME回調(diào)
    for (FrameCallback callback in _persistentCallbacks)
      _invokeFrameCallback(callback, _currentFrameTimeStamp);

    _schedulerPhase = SchedulerPhase.postFrameCallbacks;
    // 執(zhí)行POST-FRAME回調(diào)
    final List<FrameCallback> localPostFrameCallbacks = List<FrameCallback>.from(_postFrameCallbacks);
    _postFrameCallbacks.clear();
    for (FrameCallback callback in localPostFrameCallbacks)
      _invokeFrameCallback(callback, _currentFrameTimeStamp);
  } finally {
    _schedulerPhase = SchedulerPhase.idle;
    Timeline.finishSync(); //標識結(jié)束”Frame“階段
    profile(() {
      _profileFrameStopwatch.stop();
      _profileFramePostEvent();
    });
    _currentFrameTimeStamp = null;
  }
}

該方法主要功能:

  • 遍歷_persistentCallbacks讼昆,執(zhí)行相應(yīng)的回調(diào)方法托享,可通過addPersistentFrameCallback()注冊,一旦注冊后不可移除浸赫,后續(xù)每一次frame回調(diào)都會執(zhí)行闰围;
  • 遍歷_postFrameCallbacks,執(zhí)行相應(yīng)的回調(diào)方法既峡,可通過addPostFrameCallback()注冊羡榴,handleDrawFrame()執(zhí)行完成后會清空_postFrameCallbacks內(nèi)容。

三运敢、小結(jié)

可見校仑,setState()過程主要工作是記錄所有的臟元素,添加到BuildOwner對象的_dirtyElements成員變量传惠,然后調(diào)用scheduleFrame來注冊Vsync回調(diào)迄沫。 當下一次vsync信號的到來時會執(zhí)行handleBeginFrame()和handleDrawFrame()來更新UI。

本文參考了gityan大佬的博客卦方,感謝大佬分享邢滑!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市愿汰,隨后出現(xiàn)的幾起案子惋鹅,更是在濱河造成了極大的恐慌啤咽,老刑警劉巖赤套,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搀擂,死亡現(xiàn)場離奇詭異,居然都是意外死亡吗跋,警方通過查閱死者的電腦和手機侧戴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跌宛,“玉大人酗宋,你說我怎么就攤上這事〗校” “怎么了蜕猫?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哎迄。 經(jīng)常有香客問我回右,道長隆圆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任翔烁,我火速辦了婚禮渺氧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蹬屹。我一直安慰自己侣背,他們只是感情好,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布慨默。 她就那樣靜靜地躺著贩耐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪业筏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天鸟赫,我揣著相機與錄音蒜胖,去河邊找鬼。 笑死抛蚤,一個胖子當著我的面吹牛台谢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播岁经,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼朋沮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了缀壤?” 一聲冷哼從身側(cè)響起樊拓,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎塘慕,沒想到半個月后筋夏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡图呢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年条篷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛤织。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡赴叹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出指蚜,到底是詐尸還是另有隱情乞巧,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布摊鸡,位于F島的核電站摊欠,受9級特大地震影響丢烘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜些椒,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一播瞳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧免糕,春花似錦赢乓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至松逊,卻和暖如春躺屁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背经宏。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工犀暑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人烁兰。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓耐亏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親沪斟。 傳聞我的和親對象是個殘疾皇子广辰,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361