本文來自整理和簡化
調用 setState()必須是沒有調用過 dispose()方法,不然出錯,可通過mounted
屬性來判斷調用此方法是否合法。
if (mounted) {
setState(() {});
}
清晰的看到在framework.dart內setstate方法除了一些條件判斷就是:
_element.markNeedsBuild();
那我們看看markNeedsBuild。
Element 類 markNeedsBuild方法
void markNeedsBuild() {
assert(_debugLifecycleState != _ElementLifecycle.defunct);
if (!_active)
return;//返回
...
if (dirty)
return;
_dirty = true;
//調用scheduleBuildFor方法
owner.scheduleBuildFor(this);
}
將 element 元素標記為“臟”,並把它添加到全局的“臟”鏈表里,以便在下一幀更新信號時更新.
- 這里的“
臟
”鏈表是待更新的鏈表,更新過后就不“臟”了。 - 由於一幀做兩次更新有點低效,所以在
_active=false
的時候直接返回。
那我們看看本方法最后調用的scheduleBuildFor方法。
BuildOwner 類 scheduleBuildFor方法
BuildOwner
類是widget framework
的管理類,它跟蹤那些需要重新構建的 widget。
void scheduleBuildFor(Element element) {
...
if (element._inDirtyList) {
...
_dirtyElementsNeedsResorting = true;
return;
}
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled();//回調
}
_dirtyElements.add(element);//把element加入臟元素鏈表
element._inDirtyList = true;
assert(() {
if (debugPrintScheduleBuildForStacks)
debugPrint('...dirty list is now: $_dirtyElements');
return true;
}());
}
把一個 element 添加到 _dirtyElements 鏈表,以便當WidgetsBinding.drawFrame
中調用 buildScope 的時候能夠重構 element。onBuildScheduled()是一個 BuildOwner 的回調。
onBuildScheduled回調在WidgetsBinding的initInstances里初始化。
mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
// 這里
buildOwner.onBuildScheduled = _handleBuildScheduled;
window.onLocaleChanged = handleLocaleChanged;window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
SystemChannels.system.setMessageHandler(_handleSystemMessage); FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
}
}
我們可以看到buildOwner.onBuildScheduled回調等於了_handleBuildScheduled,那現在來看看這個_handleBuildScheduled方法:
void _handleBuildScheduled() {
//調用ensureVisualUpdate
ensureVisualUpdate();
}
可以看到調用ensureVisualUpdate方法,那我們繼續走下去。
SchedulerBinding類ensureVisualUpdate方法
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
//當schedulerPhase為SchedulerPhase.idle,
//SchedulerPhase.postFrameCallbacks時調用
//scheduleFrame()
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
分別case了SchedulerPhase 的 5 個枚舉值:
狀態 | 含義 |
---|---|
idle | 沒有正在處理的幀,可能正在執行的是 WidgetsBinding.scheduleTask,scheduleMicrotask,Timer,事件 handlers,或者其他回調等 |
transientCallbacks | SchedulerBinding.handleBeginFrame 過程, 處理動畫狀態更新 |
midFrameMicrotasks | 處理 transientCallbacks 階段觸發的微任務(Microtasks) |
persistentCallbacks | WidgetsBinding.drawFrame 和 SchedulerBinding.handleDrawFrame 過程,build/layout/paint 流水線工作 |
postFrameCallbacks | 主要是清理和計划執行下一幀的工作 |
第二個case調用scheduleFrame()方法
那我們看看scheduleFrame()方法
void scheduleFrame() {
if (_hasScheduledFrame || !_framesEnabled) return;
assert(() {
if (debugPrintScheduleFrameStacks)
debugPrintStack(
label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
return true;
}());
//調用Window 的scheduleFrame方法是一個 native 方法
window.scheduleFrame();
_hasScheduledFrame = true;
}
WidgetsFlutterBinding 混入的這些 Binding 中基本都是監聽並處理 Window 對象的一些事件,然后將這些事件按照 Framework 的模型包裝、抽象然后分發。可以看到 WidgetsFlutterBinding 正是粘連 Flutter engine 與上層 Framework 的“膠水”。
名 | 解釋 |
---|---|
GestureBinding | 提供了 window.onPointerDataPacket 回調,綁定 Framework 手勢子系統,是 Framework 事件模型與底層事件的綁定入口 |
ServicesBinding | 提供了 window.onPlatformMessage 回調, 用於綁定平台消息通道(message channel),主要處理原生和 Flutter 通信 |
SchedulerBinding | 提供了 window.onBeginFrame 和 window.onDrawFrame 回調,監聽刷新事件,綁定 Framework 繪制調度子系統 |
PaintingBinding | 綁定繪制庫,主要用於處理圖片緩存 |
SemanticsBinding | 語義化層與 Flutter engine 的橋梁,主要是輔助功能的底層支持 |
RendererBinding | 提供了 window.onMetricsChanged 、window.onTextScaleFactorChanged 等回調。它是渲染樹與 Flutter engine 的橋梁 |
WidgetsBinding | 提供了 window.onLocaleChanged、onBuildScheduled 等回調。它是 Flutter widget 層與 engine 的橋梁 |
之前的文中有說過,UI 的繪制邏輯是在 Render 樹中實現的,所以這里還來細看 RendererBinding 的邏輯。
RendererBinding
void initInstances() {
...
//監聽Window對象的事件
ui.window
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;
//添加PersistentFrameCallback
addPersistentFrameCallback(_handlePersistentFrameCallback);
}
addPersistentFrameCallback 中添加 _handlePersistentFrameCallback 最終調用了 drawFrame 而 WidgetsBinding 重寫了 RendererBinding 中的 drawFrame() 方法。最終發現我們又回到了 WidgetsBinding 這個類中,在 WidgetsBinding 中 drawFrame 的實現如下:
@override
void drawFrame() {
...
try {
if (renderViewElement != null)
// 重構需要更新的element
buildOwner.buildScope(renderViewElement);
super.drawFrame(); //調用RendererBinding的drawFrame()方法
buildOwner.finalizeTree();
}
}
在上面 scheduleBuildFor 方法介紹中有提到:"scheduleBuildFor 是把一個 element 添加到 _dirtyElements 鏈表,以便當[WidgetsBinding.drawFrame]中調用 buildScope 的時候能夠重構 element。onBuildScheduled()是一個 BuildOwner 的回調"。在 drawFrame 中調用 buildOwner.buildScope(renderViewElement)更新 elements。
void buildScope(Element context, [ VoidCallback callback ]) {
...
while (index < dirtyCount) {
assert(_dirtyElements[index] != null);
assert(_dirtyElements[index]._inDirtyList);
assert(!_dirtyElements[index]._active || _dirtyElements[index]._debugIsInScope(context));
try {
//while 循環進行元素重構
_dirtyElements[index].rebuild();
} catch (e, stack) {
...
}
}
}
得出
條件判斷
- 1.生命周期判斷
- 2.是否安裝mounted
管理類
- 1.告訴管理類方法自己需要被重新構建
- 也就是BuildOwner類scheduleBuildFor方法
添加臟鏈表
-
- “臟”鏈表是待更新的鏈表
- 2.更新過后就不“臟”了
- 3._active=false 的時候直接返回
調用 window.scheduleFrame()
- native 方法
- 按照 Framework 的模型包裝、抽象然后分發
- WidgetsFlutterBinding 正是粘連 Flutter engine 和上層 Framework 的“膠水”
- UI 的繪制邏輯是在 Render 樹中實現的
更新幀信號來臨從而刷新需要重構的界面
- "scheduleBuildFor 是把一個 element 添加到 _dirtyElements 鏈表
- 以便當[WidgetsBinding.drawFrame]中調用 buildScope 的時候能夠重構 element
- onBuildScheduled()是一個 BuildOwner 的回調"
- 在 drawFrame 中調用 buildOwner.buildScope(renderViewElement)更新 elements
圖:
Flutter微信群
Flutter教程網:www.flutterj.com
Flutter交流QQ群:874592746
公眾號
關注公眾號“Flutter前線
”,各種Flutter項目實戰經驗技巧,干活知識,Flutter面試題答案,等你來領取。