Flutter 必知必會系列 —— 從 SchedulerBinding 中看 Flutter 幀排程
持續創作,加速成長!這是我參與「掘金日新計劃 · 6 月更文挑戰」的第2天,點選檢視活動詳情
前面我們介紹了 GestureBinding
,知道了 Flutter
的手勢處理流程,這一篇我們就從 SchedulerBinding
的程式碼中看幀排程過程。
往期精彩:
Flutter 必知必會系列 —— mixin 和 BindingBase 的巧妙配合
Flutter 必知必會系列 —— 從 GestureBinding 中看 Flutter 手勢處理過程
Flutter 幀的階段
Flutter 把一幀分為了 6 個階段,每個階段做不同的事情,並且把幀階段包裝在列舉類 SchedulerPhase
中,作為前期準備,我們認識一下這個類和這幾個階段是啥。
idle —— 空閒
這個階段沒有幀任務執行,在這個階段執行的程式碼是:SchedulerBinding.scheduleTask
釋出的 TaskCallback
、scheduleMicrotask
註冊的微任務、Timer
宣告的任務、使用者的手勢處理器、Future
和 Stream
的任務。
這裡大家需要先看一下 Dart 的任務執行順序,這個連結需要翻牆哦~,瞭解一下 Dart 是如何處理事件佇列、微任務佇列的,以便我們寫出更好的非同步程式碼。
transientCallbacks —— 瞬時任務階段
這個階段執行 SchedulerBinding.scheduleFrameCallback
新增的回撥,一般情況下,這些回撥執行動畫有關的計算,我們在前面的動畫介紹中,講過這個地方,👉
Flutter 動畫是這麼動起來的
midFrameMicrotasks —— 幀中微任務處理階段
transientCallbacks 也會產生一些微任務,這些微任務會在這個階段執行。
persistentCallbacks —— 持續任務處理階段
這個階段主要處理 SchedulerBinding.addPersistentFrameCallback
新增的回撥,與 transientCallbacks
階段相對應,這個階段處理 build/layout/paint
。
postFrameCallbacks —— 幀結束階段
這個階段執行 SchedulerBinding.addPostFrameCallback
新增的回撥,一般情況下會做一些幀清理工作和發起下一幀。
比如我們舉個例子:
void update(TextEditingValue newValue) {
if (_value == newValue)
return;
_value = newValue;
if (SchedulerBinding.instance!.schedulerPhase == SchedulerPhase.persistentCallbacks) { //第一處
SchedulerBinding.instance!.addPostFrameCallback(_markNeedsBuild);
} else {
_markNeedsBuild();
}
}
這一段程式碼是 EditText
的程式碼,在更新顯示欄位的時候,看第一處的顯示邏輯。如果當前是繪製階段,那就在本幀的結尾增加一個發起重新 build 的任務,否則就直接發起重新 build 任務。
小結
幀的排程分為六個階段,程式碼體現在 SchedulerPhase 中,幀和佇列的執行和關聯如下:
Flutter 發起幀排程
幀排程的方式
Flutter 中發起幀排程的方法就下面幾個:
| 方法名 | 作用 |
| --- | --- |
| scheduleWarmUpFrame | 排程一幀,並且該幀儘可能快的執行,不需要等待引擎的 "Vsync" 訊號 |
| scheduleFrame | 排程一幀 |
| scheduleForcedFrame | 強行排程一幀,即使是在熄屏的情況下也會執行|
| scheduleFrameCallback | 排程一幀,並且給這一幀設定一個執行回撥,回撥會在 transient
階段執行
這幾個方法大差不差,我們以核心的 scheduleFrame 的為例,看看幹了啥事。
```dart void scheduleFrame() { if (_hasScheduledFrame || !framesEnabled) return; ensureFrameCallbacksRegistered();//第一處 window.scheduleFrame(); _hasScheduledFrame = true; }
void ensureFrameCallbacksRegistered() { window.onBeginFrame ??= _handleBeginFrame; window.onDrawFrame ??= _handleDrawFrame; }
``
就幹了兩件事:第一:確保
window的
onBeginFrame和
onDrawFrame回撥已經設定了。
第二:呼叫
window` 的發起幀流程。
之前我們提到過,Flutter 和 Native 的互動都是通過回撥的方式,window 的發起流程最終會呼叫到
void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';
這是一個 native 方法,相當於 Flutter 告訴原生,原生請開始繪製吧,我已經準備好了。然後與原生在收到 "Vsync" 訊號之後,會呼叫到第一處註冊的兩個回撥 onBeginFrame 和 onDrawFrame。
我們看:
```dart @pragma('vm:entry-point') void _beginFrame(int microseconds, int frameNumber) { PlatformDispatcher.instance._beginFrame(microseconds); PlatformDispatcher.instance._updateFrameData(frameNumber); }
@pragma('vm:entry-point') void _drawFrame() { PlatformDispatcher.instance._drawFrame(); }
void scheduleFrame() native 'PlatformConfiguration_scheduleFrame'; ```
那麼 window
的 onBeginFrame
和 onDrawFrame
便是幀排程執行的內容了。
onBeginFrame 開始響應
我們來看 Flutter 是怎麼響應的。
dart
/// ...程式碼省略
void handleBeginFrame(Duration? rawTimeStamp) {
_hasScheduledFrame = false;
try {
_schedulerPhase = SchedulerPhase.transientCallbacks; //第一處
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 階段,並且在這個階段,執行了 _transientCallbacks 中的回撥。
_transientCallbacks 中的回撥是啥呢? 就是 scheduleFrameCallback 方法引數中新增的。
dart
int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
scheduleFrame();
_nextFrameCallbackId += 1;
_transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
return _nextFrameCallbackId;
}
\_transientCallbacks
是一個 Map
,key
是幀的 id
,value
是這一幀在 transient
階段應該執行的回撥陣列。
在使用 scheduleFrameCallback
方法發起幀任務的時候,需要傳遞一個 callback
回撥,這個回撥就是發起幀的下一幀的 transient
階段執行。
那麼誰呼叫了 scheduleFrameCallback
這個方法呢? 就是動畫!所以動畫的計算先與佈局繪製等。具體可以看這裡👉 Flutter 動畫是這麼動起來的
不管回撥會不會異常,都會執行到第三處,第三處就是幀排程進入了 midFrameMicrotasks
階段。
上面就是 \_handleBeginFrame
,會執行本幀的 \_transientCallbacks
回撥,因為動畫發起的時候會設定這個回撥,所以基本就是動畫的計算。
因為動畫會是 Future 等的計算,所以在 midFrameMicrotasks 階段,這些非同步的計算依然會執行。
下面我們看 _handleDrawFrame
的執行。
onDrawFrame 繪製任務
```dart /// 程式碼省略 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;
}
}
``
上面的程式碼還是比較清晰的,就是 **
修改狀態、執行回撥`**。我們仔細看。
首先看第一處的程式碼,就是從 midFrameMicrotasks
階段進入到 persistentCallbacks
。
然後執行 _persistentCallbacks
回撥,_persistentCallbacks
回撥是誰呢?
_persistentCallbacks
是通過 addPersistentFrameCallback
新增的。
void addPersistentFrameCallback(FrameCallback callback) {
_persistentCallbacks.add(callback);
}
那麼誰呼叫了這個方法呢?就是 RendererBinding 的初始化中。
```dart /// 程式碼省略 mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable { @override void initInstances() { super.initInstances(); addPersistentFrameCallback(_handlePersistentFrameCallback);//第一處 }
void _handlePersistentFrameCallback(Duration timeStamp) { drawFrame(); }
void drawFrame() { pipelineOwner.flushLayout(); pipelineOwner.flushCompositingBits(); pipelineOwner.flushPaint(); }
} ``` 就是第一處的程式碼, RendererBinding 初始化時,添加了全域性的幀 persistent 回撥。 回撥的任務就是佈局(Layout)、合成(CompositingBit)、繪製(Paint)。
所以說,在 persistentCallbacks
階段,執行的任務就是佈局、合成、繪製。
我們繼續看,persistentCallbacks
的回撥執行完了之後,就會到 postFrameCallbacks
階段,執行 _postFrameCallbacks
回撥。postFrameCallbacks
階段是幀的末尾階段,大家可以使用這個方法來做一些收尾的工作。比如獲取尺寸等等。和 persistentCallbacks
不同,_postFrameCallbacks
是一次性的。
```dart void handleDrawFrame() {
try { // PERSISTENT FRAME CALLBACKS _schedulerPhase = SchedulerPhase.persistentCallbacks; for (final FrameCallback callback in _persistentCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp!); //第一處
_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;
}
}
``
第一處並沒清空
_persistentCallbacks回撥,第二處在執行完了之後,會清空
_postFrameCallbacks` 回撥。
所以大家通過 addPostFrameCallback
新增的回撥只會執行一次。
小結
現在我們知道了發起幀排程就是通知 Native:請排程我吧,我的回撥已經準備好了!分別是 onBeginFrame
和 onDrawFrame
。
總結
幀排程就說完啦,和我們平時寫的程式碼一樣,把一個大任務分成幾個階段,每個階段對應一個回撥陣列,從開始到結束依次是:動畫、佈局、合成、繪製、收尾。