介紹
Flutter中最常用的Widget是StatelessWidget
和StatefulWidget
,分別對應於無狀態的元件和有狀態的元件。而StatefulWidget
中更新狀態的方法就是setState(fn)
,呼叫該方法後,會重新呼叫StatefulWidget
的build
方法重新構建元件,達到重新整理介面的效果。那麼呼叫setState
方法後,是通過什麼的樣流程走到build
方法的呢?帶著這個疑惑通過閱讀原始碼來分析StatefulWidget
的更新流程。
原始碼解析
setState
方法有一個fn
引數,一般會在該函式中執行更新狀態的操作,在方法體內會首先同步執行fn
函式。這個函式的返回值不能是Future
型別,即不能是async
非同步函式。執行完fn
函式後,呼叫_element
的markNeedsBuild
方法。
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
中的構造方法中會通過StatefulWidget
的createState
建立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
方法會呼叫owner
的scheduleBuildFor
方法,將該element
標記為dirty,並且將element
加入到一個全域性的表示需要更新的Element列表中。owner
是BuildOwner
物件。
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;
...
}
複製程式碼
這個方法主要執行幾個任務
- 判斷
element
是否已經加入到_dirtyElements
列表中,如果已經在列表中,就直接返回,不用再執行下面的操作。 - 判斷
_scheduledFlushDirtyElements
是否為false
,這個變數表示當前是否正在rebuild_dirtyElements
中的元素。如果沒有正在rebuild,並且onBuildScheduled
回撥不為空,就呼叫onBuildScheduled
函式。 - 將element加入到
_dirtyElements
中,並且標記element的_inDirtyList
為true
,表示已經加入到髒元素列表。
通過搜尋可以查到,BuildOwner
是在WdigetBinding
的initInstances
方法中建立的,並且建立完成後設定了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
中的方法,WidgetsBinding
和SchedulerBinding
都是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
狀態完成的。
所以,如果上次重新整理已經完成(postFrameCallbacks
或idle
狀態),就會呼叫scheduleFrame
請求再次重新整理。
void scheduleFrame() {
if (_hasScheduledFrame || !framesEnabled)
return;
...
ensureFrameCallbacksRegistered();
window.scheduleFrame();
_hasScheduledFrame = true;
}
複製程式碼
WidgetBinding
的scheduleFrame
會首先呼叫ensureFrameCallbacksRegistered
方法確保window
的回撥函式以被註冊。再呼叫window
的scheduleFrame
的方法。
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';
複製程式碼
Window
的scheduleFrame
方法是個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
來驅動渲染的。通過搜尋,可以看到在RendererBinding
的initInstances
方法中註冊了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;
}());
}
...
}
複製程式碼
在WidgetsBinding
的drawFrame
方法中,先呼叫了buildOwner
的buildScope
方法,然後再呼叫了super.drawFrame()
,通過super.drawFrame()
可以呼叫到RendererBinding
的drawFrame
方法。先看buildOwner
的buildScope
方法。
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();
複製程式碼
performRebuild
是Element
類中的抽象方法,各個子類會實現該方法。StateElement
的父類是ComponentElement
,先看ComponentElement
的performRebuild
方法
@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。
StatelessElement
和StatefulElement
重寫了build
方法,分別呼叫了Widget
和State
的build
方法。
///StatelessElement
@override
Widget build() => widget.build(this);
複製程式碼
@override
Widget build() => _state.build(this);
複製程式碼
前面提到WidgetsBinding
的drawFrame
方法會通過super.drawFrame()
呼叫到RendererBinding
的drawFrame
方法,再回頭看RendererBinding
的drawFrame
方法。
@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;
}
}
複製程式碼
RendererBinding
的drawFrame
方法,通過pipelineOwner
物件重新layout和paint,已達到更新UI的效果。
總結
StatefulWidget
通過setState
方法將其對應的StatefulElement
新增到BuildOwner
的dirtyElements
中,並觸發一次重新整理。在收到重新整理回撥後,遍歷dirtyElements
中的元素,執行rebuild
操作,以更新顯示狀態。