本文我們來分析下Flutter的啟動流程,首先我們從main.dart檔案的main
函式開始:
void main() => runApp(MyApp());
複製程式碼
main
函式則呼叫的是runApp
函式:
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
複製程式碼
函式中有用到Dart語法中的級聯運算子(..),代表的含義是WidgetsFlutterBinding.ensureInitialized()
生成的物件分別呼叫了scheduleAttachRootWidget
和scheduleWarmUpFrame
這兩個方法。
先概括一下這三行程式碼的重要作用:
- 生成一個Flutter Engine(C++程式碼)和Flutter Framework(Dart程式碼)的中間橋接物件,官方定義為膠水物件;
- 根據app生成一個渲染樹;
- 繪製熱身幀, 將渲染樹生成的Layer圖層通過Flutter Engine渲染到Flutter View上。
概括起來很簡單,但是其中包含的內容是相當複雜的。我們接下來就從這三行程式碼入手分析一下其中具體的流程。
WidgetsFlutterBinding
WidgetsFlutterBinding類中的所有程式碼如下:
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
// 類初始化方法
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
// 構造方法呼叫
WidgetsFlutterBinding();
// 返回物件WidgetsBinding
return WidgetsBinding.instance!;
}
}
複製程式碼
WidgetsFlutterBinding繼承自BindingBase,混入了GestureBinding,SchedulerBinding,ServicesBinding,PaintingBinding,SemanticsBinding,RendererBinding和WidgetsBinding7個mixin。這7個mixin的功能後面詳解介紹。
ensureInitialized
方法就是獲取WidgetsBinding.instance
單例的過程。由於mixin沒有構造方法,所以WidgetsFlutterBinding()
實際呼叫的是父類BindingBase的構造方法。
BindingBase() {
// 呼叫initInstances
initInstances();
}
複製程式碼
WidgetsFlutterBinding混入的7個mixin都重寫了initInstances()
方法,所以他們的initInstances()
都會被呼叫。最後的呼叫邏輯如下圖所示:
通過精妙的mixin程式碼設計,實現了高內聚低耦合和模組職責單一,並且通過mixin依賴,實現了initInstances()
方法呼叫的序列按執行順序。
FlutterView
問題:為什麼突兀的來介紹FlutterView物件呢?
FlutterView是Flutter Engine給Flutter Framework開放的使用者介面和事件的介面,可以把Flutter Framework理解為圍繞FlutterView的一個處理框架。所以其重要性不言而喻。
上面WidgetsFlutterBinding混入的多個mixin主要就是處理window物件(即FlutterView物件的)的回撥事件和提交渲染內容,所以我們先來介紹一下FlutterView是非常有必要的。
window物件是BindingBase的一個變數, 名字上推測他就是個單例物件:
<!-- BindingBase -->
ui.SingletonFlutterWindow get window => ui.window;
複製程式碼
ui.window
是PlatformDispatcher.instance中windowId為0的主window:
<!-- window.dart -->
final SingletonFlutterWindow window = SingletonFlutterWindow._(0, PlatformDispatcher.instance);
複製程式碼
SingletonFlutterWindow的繼承圖譜如下:
<!-- window.dart -->
abstract class FlutterView {}
class FlutterWindow extends FlutterView {}
class SingletonFlutterWindow extends FlutterWindow {}
複製程式碼
FlutterView
abstract class FlutterView {
//
PlatformDispatcher get platformDispatcher;
//
ViewConfiguration get viewConfiguration;
//
double get devicePixelRatio => viewConfiguration.devicePixelRatio;
//
Rect get physicalGeometry => viewConfiguration.geometry;
//
Size get physicalSize => viewConfiguration.geometry.size;
//
WindowPadding get viewInsets => viewConfiguration.viewInsets;
//
WindowPadding get viewPadding => viewConfiguration.viewPadding;
//
WindowPadding get systemGestureInsets => viewConfiguration.systemGestureInsets;
//
WindowPadding get padding => viewConfiguration.padding;
//
void render(Scene scene) => _render(scene, this);
void _render(Scene scene, FlutterView view) native 'PlatformConfiguration_render';
}
複製程式碼
FlutterView有幾個重要的屬性和方法:
- PlatformDispatcher是FlutterView的核心,FlutterView是對它的一層封裝,是真正向Flutter Engine傳送訊息和得到回撥的類;
- ViewConfiguration是Platform View的一些資訊的描述,其中主要包括幾個資訊:
devicePixelRatio
:物理畫素和虛擬畫素的比值。這個和手機有關,譬如iPhone手機可能是2或者3,Android手機就有可能是個小數,譬如3.5等。geometry
:Flutter渲染的View在Native platform中的位置和大小。viewInsets
: 各個邊顯示的內容和能顯示內容的邊距大小;譬如:沒有鍵盤的時候viewInsets.bottom為0,當有鍵盤的時候鍵盤擋住了一些區域,鍵盤底下無法顯示內容,所以viewInsets.bottom就變成了鍵盤的高度。padding
: 系統UI的顯示區域如狀態列,這部分割槽域最好不要顯示內容,否則有可能被覆蓋了。譬如,很多iPhone頂部的劉海區域,padding.top就是其高度。viewPadding
:viewInsets
和padding
的和。參考地址
- 下面的屬性都是對ViewConfiguration內部屬性的暴露,便於外部獲取。
render
方法是將Flutter程式碼生成的渲染內容(Layer Tree生成的Scene)傳遞給Flutter Engine, 讓GPU去渲染。
ViewConfiguration其實也是從PlatformDispatcher獲取的。
FlutterWindow
FlutterWindow沒有什麼功能,只是封裝了一個構造方法,我們不做分析,接下來我們來看看SingletonFlutterWindow的一些重要程式碼:
- devicePixelRatio, physicalSize, padding和viewInsets等的變化會觸發的回撥
onMetricsChanged
;
本質是轉發了platformDispatcher的回撥,後面的回撥方法都類似。
VoidCallback? get onMetricsChanged => platformDispatcher.onMetricsChanged;
set onMetricsChanged(VoidCallback? callback) {
platformDispatcher.onMetricsChanged = callback;
}
複製程式碼
- 手機設定的地區(如中國大陸),以及設定的地區更改後收到的回撥
onLocaleChanged
;
Locale get locale => platformDispatcher.locale;
VoidCallback? get onLocaleChanged => platformDispatcher.onLocaleChanged;
set onLocaleChanged(VoidCallback? callback) {
platformDispatcher.onLocaleChanged = callback;
}
複製程式碼
- 文字縮放倍率變化後的回撥
onTextScaleFactorChanged
;
VoidCallback? get onTextScaleFactorChanged => platformDispatcher.onTextScaleFactorChanged;
set onTextScaleFactorChanged(VoidCallback? callback) {
platformDispatcher.onTextScaleFactorChanged = callback;
}
複製程式碼
- platformBrightness變化後的回撥
onPlatformBrightnessChanged
;
VoidCallback? get onPlatformBrightnessChanged => platformDispatcher.onPlatformBrightnessChanged;
set onPlatformBrightnessChanged(VoidCallback? callback) {
platformDispatcher.onPlatformBrightnessChanged = callback;
}
複製程式碼
- Flutter Engine根據VSync傳送的準備開始下一幀的回撥
onBeginFrame
;
FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame;
set onBeginFrame(FrameCallback? callback) {
platformDispatcher.onBeginFrame = callback;
}
複製程式碼
- onBeginFrame完成後,開始繪製幀的回撥
onDrawFrame
;
VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame;
set onDrawFrame(VoidCallback? callback) {
platformDispatcher.onDrawFrame = callback;
}
複製程式碼
- 使用者的手勢操作(點選,滑動等)的回撥
onPointerDataPacket
;
PointerDataPacketCallback? get onPointerDataPacket => platformDispatcher.onPointerDataPacket;
set onPointerDataPacket(PointerDataPacketCallback? callback) {
platformDispatcher.onPointerDataPacket = callback;
}
複製程式碼
- 收到外掛傳送的訊息的回撥
onPlatformMessage
;
PlatformMessageCallback? get onPlatformMessage => platformDispatcher.onPlatformMessage;
set onPlatformMessage(PlatformMessageCallback? callback) {
platformDispatcher.onPlatformMessage = callback;
}
複製程式碼
- 語義的設定和修改後的回撥;
void updateSemantics(SemanticsUpdate update) => platformDispatcher.updateSemantics(update);
VoidCallback? get onAccessibilityFeaturesChanged => platformDispatcher.onAccessibilityFeaturesChanged;
set onAccessibilityFeaturesChanged(VoidCallback? callback) {
platformDispatcher.onAccessibilityFeaturesChanged = callback;
}
複製程式碼
總結:
FlutterView物件window
本質上是對PlatformDispatcher的封裝,從PlatformDispatcher獲取一些介面相關資訊,獲取從Flutter Engine 傳送來的事件,然後觸發和轉發相應的回撥方法。
如果有想法,可以基於
window
實現自己的Flutter Framework。
BindingBase
BindingBase的主要功能前面都已經說明,這裡總結一下:
- 建構函式呼叫
initInstances
方法,其實是為了依次呼叫7個mixin的initInstances
方法。 - 提供了一個
window
單例。
abstract class BindingBase {
BindingBase() {
// 初始化
initInstances();
}
// 單例window
ui.SingletonFlutterWindow get window => ui.window;
}
複製程式碼
RendererBinding
RendererBinding的功能主要和渲染樹相關。我們來看看它的重要程式碼:
initInstances
初始化方法:
void initInstances() {
super.initInstances();
_instance = this;
// 1
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
// 2
window
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;
// 3
initRenderView();
_handleSemanticsEnabledChanged();
// 4
addPersistentFrameCallback(_handlePersistentFrameCallback);
// 5
initMouseTracker();
}
複製程式碼
- 生成了一個PipelineOwner物件。它的主要作用是收集需要更新的RenderObjects,然後藉助RendererBinding進行UI重新整理。
- 處理window物件的
onMetricsChanged
,onTextScaleFactorChanged
等回撥方法。 initRenderView
生成了一個RenderView物件renderView
, 然後將renderView
設定為_pipelineOwner
的根節點rootNode。
這個
renderView
是渲染樹的根節點,我們的MyApp將作為它的子節點插入渲染樹。先劇透一下,後面會介紹。
addPersistentFrameCallback
呼叫的是SchedulerBinding的方法, PersistentFrameCallback主要執行的是Widget的build / layout / paint等一系列操作。
<!-- SchedulerBinding.dart -->
void addPersistentFrameCallback(FrameCallback callback) {
_persistentCallbacks.add(callback);
}
複製程式碼
- 生成一個MouseTracker物件,處理
hitTestResult
或者PointerAddedEvent
和PointerRemovedEvent
事件。
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
if (hitTestResult != null ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {
assert(event.position != null);
_mouseTracker!.updateWithEvent(event,
() => hitTestResult ?? renderView.hitTestMouseTrackers(event.position));
}
super.dispatchEvent(event, hitTestResult);
}
複製程式碼
這裡是事件傳遞的重要方法,後面介紹GestureBinding事件傳遞的時候會再次見到它。
drawFrame
繪製方法
void drawFrame() {
// 1
pipelineOwner.flushLayout();
// 2
pipelineOwner.flushCompositingBits();
// 3
pipelineOwner.flushPaint();
// 4
if (sendFramesToEngine) {
// 5
renderView.compositeFrame(); // this sends the bits to the GPU
// 6
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}
}
複製程式碼
pipelineOwner.flushLayout
是對Dirty RenderObject進行佈局定位;pipelineOwner.flushCompositingBits
是更新RenderObject的needsCompositing屬性,這個屬性在很多情況下需要用到,譬如裁剪(Clip),旋轉(Transform)等。pipelineOwner.flushPaint
是在PaintingContext對RenderObject進行繪製。renderView.compositeFrame
方法是用SceneBuilder將前幾步的繪製結果轉換成一個Scene(可以理解為一幀畫面)物件,然後呼叫window的render
方法提交給GUP去顯示,程式碼如下:
void compositeFrame() {
...
final ui.SceneBuilder builder = ui.SceneBuilder();
final ui.Scene scene = layer!.buildScene(builder);
_window.render(scene);
...
}
複製程式碼
5.pipelineOwner.flushSemantics
更新語義輔助資訊。
SemanticsBinding
Semantics譯來就是語義,主要就是描述應用程式中的UI資訊。在iOS和Android主要是用於讀屏使用,幫助有視力障礙的人使用。在網頁開發中可以方便搜尋等。
在Flutter Framework中Semantics非常常見,但是其實在移動端開發中,這個功能其實很少使用到。我們就一筆帶過,簡單看下它的初始化方法:
mixin SemanticsBinding on BindingBase {
void initInstances() {
super.initInstances();
_instance = this;
_accessibilityFeatures = window.accessibilityFeatures;
}
}
複製程式碼
PaintingBinding
不要被它的名字誤導了,其實它是處理圖片快取的mixin。和RenderObject的Paint沒啥關係。
接下來我們看看PaintingBinding的主要程式碼:
initInstances
初始化方法
mixin PaintingBinding on BindingBase, ServicesBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
_imageCache = createImageCache();
shaderWarmUp?.execute();
}
複製程式碼
_imageCache
是圖片快取的類,最大能存1000張圖片,最大記憶體是100MB;shaderWarmUp?.execute()
是一個非同步方法,初始化了一個預設的著色器,避免需要著色器的時候再初始化出現掉幀現象。
Reduce shader compilation jank on mobile
handleMemoryPressure
處理記憶體警告
void handleMemoryPressure() {
super.handleMemoryPressure();
imageCache?.clear();
}
複製程式碼
圖片儲存非常耗記憶體,所以當App記憶體警告時需要清除掉快取。
ServicesBinding
ServicesBinding的主要功能是接收MethodChannel和SystemChannels傳遞過來的訊息。我們來看看ServicesBinding的主要程式碼:
initInstances
初始化方法
void initInstances() {
super.initInstances();
_instance = this;
// 1
_defaultBinaryMessenger = createBinaryMessenger();
// 2
_restorationManager = createRestorationManager();
// 3
window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
// 4
initLicenses();
// 5
SystemChannels.system.setMessageHandler((dynamic message) => handleSystemMessage(message as Object));
SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
// 6
readInitialLifecycleStateFromNativeWindow();
}
複製程式碼
createBinaryMessenger()
建立了一個MethodChannel;createRestorationManager()
建立了一個RestorationManager用於恢復介面資料的功能;
這個場景主要是手機App進入後臺後可能已經被殺死(釋放資源給其他App在前臺流程執行),可以通過恢復資料在App切換回來的時候,讓使用者感覺手機App一直在後臺執行的假象;
- 通過第一步建立的
_defaultBinaryMessenger
實現和Plugin外掛的通訊 initLicenses
是給一些檔案加上Licenses說明;- 接收SystemChannels傳遞過來的記憶體警告和過來的生命週期回撥;
Future<void> handleSystemMessage(Object systemMessage) async {
final Map<String, dynamic> message = systemMessage as Map<String, dynamic>;
final String type = message['type'] as String;
switch (type) {
case 'memoryPressure':
handleMemoryPressure();
break;
}
return;
}
複製程式碼
- 讀取當前的生命週期狀態,處理則是在父類SchedulerBinding這個mixin中去實現的。
SchedulerBinding
SchedulerBinding主要處理任務排程。在Flutter中有幾個排程階段:
- idle
這個階段沒有繪製幀任務處理,主要處理Task,Microtask,Timer回撥,使用者輸入和手勢,以及其他一些任務。
- transientCallbacks
這個階段主要處理動畫狀態的計算和更新
- midFrameMicrotasks
這個階段處理transientCallbacks階段觸發的Microtasks
- persistentCallbacks
這個階段主要處理build/layout/paint,在RendererBinding那部分有提到
- postFrameCallbacks
這個階段主要在下一幀之前,做一些清理工作或者準備工作
接下來我們看看SchedulerBinding的重要程式碼:
handleAppLifecycleStateChanged
AppLifecycleState? get lifecycleState => _lifecycleState;
void handleAppLifecycleStateChanged(AppLifecycleState state) {
assert(state != null);
_lifecycleState = state;
switch (state) {
case AppLifecycleState.resumed:
case AppLifecycleState.inactive:
_setFramesEnabledState(true);
break;
case AppLifecycleState.paused:
case AppLifecycleState.detached:
_setFramesEnabledState(false);
break;
}
}
複製程式碼
void _setFramesEnabledState(bool enabled) {
if (_framesEnabled == enabled)
return;
_framesEnabled = enabled;
if (enabled)
scheduleFrame();
}
複製程式碼
監聽生命週期變化,生命週期的狀態改變設定_framesEnabled
的值,如果_framesEnabled
為false停止重新整理介面;如果_framesEnabled
為true呼叫scheduleFrame
向Native Platform請求重新整理檢視的請求。
scheduleFrame
void scheduleFrame() {
if (_hasScheduledFrame || !framesEnabled)
return;
// 1
ensureFrameCallbacksRegistered();
// 2
window.scheduleFrame();
_hasScheduledFrame = true;
}
複製程式碼
ensureFrameCallbacksRegistered()
是先確保向window註冊了onBeginFrame
和onDrawFrame
兩個重要回撥函式;
void ensureFrameCallbacksRegistered() {
window.onBeginFrame ??= _handleBeginFrame;
window.onDrawFrame ??= _handleDrawFrame;
}
複製程式碼
window.scheduleFrame()
是向Native platform發起一個重新整理檢視的請求;傳送這個請求後,Native platform會在合適的時間呼叫onBegineFrame
和onDrawFrame
這兩個函式, 這兩個回撥會完成重新整理檢視所需的操作,比如更新widgets、動畫、和完成渲染等。這些都完成後再呼叫window.scheduleFrame()
,一直迴圈下去,直到程式退出前臺或者程式退出。
handleBeginFrame
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;
}
}
複製程式碼
Map<int, _FrameCallbackEntry> _transientCallbacks = <int, _FrameCallbackEntry>{};
複製程式碼
handleBeginFrame
的功能是執行_transientCallbacks
中的所有函式。向transientCallbacks中新增回撥主要是Ticker.scheduleTick
方法,是動畫框架的一部分。
<!-- ticker.dart -->
void scheduleTick({ bool rescheduling = false }) {
_animationId = SchedulerBinding.instance!.scheduleFrameCallback(_tick, rescheduling: rescheduling);
}
<!-- schedulerBinding.dart -->
int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
scheduleFrame();
_nextFrameCallbackId += 1;
_transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
return _nextFrameCallbackId;
}
複製程式碼
handleDrawFrame
void handleDrawFrame() {
try {
// 1
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (final FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
// 2
_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;
}
}
複製程式碼
final List<FrameCallback> _persistentCallbacks = <FrameCallback>[];
final List<FrameCallback> _postFrameCallbacks = <FrameCallback>[];
複製程式碼
handleDrawFrame
中執行了兩種回撥函式,persistentCallbacks和 postFrameCallbacks中所有的回撥函式。
- Tasks相關的程式碼
SchedulingStrategy schedulingStrategy = defaultSchedulingStrategy;
static int _taskSorter (_TaskEntry<dynamic> e1, _TaskEntry<dynamic> e2) {
return -e1.priority.compareTo(e2.priority);
}
final PriorityQueue<_TaskEntry<dynamic>> _taskQueue = HeapPriorityQueue<_TaskEntry<dynamic>>(_taskSorter);
Future<T> scheduleTask<T>(
TaskCallback<T> task,
Priority priority, {
String? debugLabel,
Flow? flow,
}) {
final bool isFirstTask = _taskQueue.isEmpty;
final _TaskEntry<T> entry = _TaskEntry<T>(
task,
priority.value,
debugLabel,
flow,
);
_taskQueue.add(entry);
if (isFirstTask && !locked)
_ensureEventLoopCallback();
return entry.completer.future;
}
void unlocked() {
super.unlocked();
if (_taskQueue.isNotEmpty)
_ensureEventLoopCallback();
}
void _ensureEventLoopCallback() {
assert(!locked);
assert(_taskQueue.isNotEmpty);
if (_hasRequestedAnEventLoopCallback)
return;
_hasRequestedAnEventLoopCallback = true;
Timer.run(_runTasks);
}
void _runTasks() {
_hasRequestedAnEventLoopCallback = false;
if (handleEventLoopCallback())
_ensureEventLoopCallback();
}
bool handleEventLoopCallback() {
if (_taskQueue.isEmpty || locked)
return false;
final _TaskEntry<dynamic> entry = _taskQueue.first;
if (schedulingStrategy(priority: entry.priority, scheduler: this)) {
try {
_taskQueue.removeFirst();
entry.run();
} catch (exception, exceptionStack) {
}
return _taskQueue.isNotEmpty;
}
return false;
}
複製程式碼
task就是自定義的一些任務。task相關的有好幾個方法,其實邏輯也很清晰,總結如下:
- 所有的task放在HeapPriorityQueue中,這個執行的優先順序比動畫的優先順序低,保證瞭如果有動畫就不會執行這些task, 確保動畫的流程。
- 在非渲染階段,Task按照優先順序從高到低一個個執行,直到都執行完畢。
如果需要較快執行,可以使用Future和Isolate等。
runapp
函式中的scheduleWarmUpFrame
就是呼叫的SchedulerBinding的方法,後面單獨列出來說明。
GestureBinding
GestureBinding主要處理使用者的各種操作:
initInstances
初始化方法
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
void initInstances() {
super.initInstances();
_instance = this;
window.onPointerDataPacket = _handlePointerDataPacket;
}
}
複製程式碼
GestureBinding用_handlePointerDataPacket
來處理window的onPointerDataPacket
方法,這個是事件的入口。
_handlePointerDataPacket
的事件處理方法流程
void _handlePointerDataPacket(ui.PointerDataPacket packet) {
_pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
if (!locked)
_flushPointerEventQueue();
}
void _flushPointerEventQueue() {
while (_pendingPointerEvents.isNotEmpty)
handlePointerEvent(_pendingPointerEvents.removeFirst());
}
void handlePointerEvent(PointerEvent event) {
_handlePointerEventImmediately(event);
}
void _handlePointerEventImmediately(PointerEvent event) {
HitTestResult? hitTestResult;
if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent) {
// 1
hitTestResult = HitTestResult();
// 2
hitTest(hitTestResult, event.position);
// 3
if (event is PointerDownEvent) {
_hitTests[event.pointer] = hitTestResult;
}
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
// 4
hitTestResult = _hitTests.remove(event.pointer);
} else if (event.down) {
hitTestResult = _hitTests[event.pointer];
}
if (hitTestResult != null ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {
// 5
dispatchEvent(event, hitTestResult);
}
}
複製程式碼
_handlePointerDataPacket
通過一系列的方法呼叫,最後呼叫_handlePointerEventImmediately
方法。
- 當event是PointerDownEvent或者PointerHoverEvent時,新建一個HitTestResult物件,它有一個path屬性,用來記錄事件傳遞所經過的的節點。
- HitTestResult把GestureBinding也加在了path中。
void hitTest(HitTestResult result, Offset position) {
result.add(HitTestEntry(this));
}
複製程式碼
- 如果event是PointerDownEvent,將這個event加入到
_hitTests
中, 為了在event.down
-即移動的時候也能獲取到它。
final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};
複製程式碼
- 當event是PointerUpEvent或者PointerCancelEvent時,將這個event從
_hitTests
中移除。 - 最後呼叫
dispatchEvent(event, hitTestResult)
方法。
dispatchEvent
方法
如果您有印象,RendererBinding中我們提到過dispatchEvent
方法。
<!-- rendererBinding.dart -->
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
_mouseTracker!.updateWithEvent(event,
() => hitTestResult ?? renderView.hitTestMouseTrackers(event.position));
super.dispatchEvent(event, hitTestResult);
}
複製程式碼
其中重要的呼叫邏輯renderView.hitTestMouseTrackers(event.position))
,會從renderview一直遍歷它的child,將沿途的Widget加入到path中。
程式碼如下:
<!-- view.dart -->
HitTestResult hitTestMouseTrackers(Offset position) {
final BoxHitTestResult result = BoxHitTestResult();
hitTest(result, position: position);
return result;
}
bool hitTest(HitTestResult result, { required Offset position }) {
if (child != null)
child!.hitTest(BoxHitTestResult.wrap(result), position: position);
result.add(HitTestEntry(this));
return true;
}
<!-- box.dart -->
bool hitTest(BoxHitTestResult result, { required Offset position }) {
if (_size!.contains(position)) {
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
result.add(BoxHitTestEntry(this, position));
return true;
}
}
return false;
}
複製程式碼
當遍歷完renderView的所有widget後,將hitTestResult返回給GestureBinding的dispatchEvent
方法,然後遍歷path陣列,逐個呼叫handleEvent
方法。
<!-- gestureBinding.dart -->
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
for (final HitTestEntry entry in hitTestResult.path) {
entry.target.handleEvent(event.transformed(entry.transform), entry);
}
}
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event);
if (event is PointerDownEvent) {
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent) {
gestureArena.sweep(event.pointer);
} else if (event is PointerSignalEvent) {
pointerSignalResolver.resolve(event);
}
}
複製程式碼
handleEvent
方法最後就做了一些路由和手勢的處理等。
事件處理的鏈路介紹完畢。
WidgetsBinding
WidgetsBinding主要處理widget tree的一些邏輯:
initInstances
初始化方法
void initInstances() {
super.initInstances();
_instance = this;
// 1
_buildOwner = BuildOwner();
buildOwner!.onBuildScheduled = _handleBuildScheduled;
// 2
window.onLocaleChanged = handleLocaleChanged;
window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
}
複製程式碼
- 初始化了一個BuildOwner物件,它主要是執行widget tree的build任務;
- 執行了一些window的回撥。
至此,第一步WidgetsFlutterBinding.ensureInitialized()
所涉及的知識點已經詳細的介紹完畢了。接下來我們來進入第二個階段。
scheduleAttachRootWidget
ensureInitialized
的介紹做了很多擴充套件,讓大家對框架有了一個整體的認識。scheduleAttachRootWidget
這一步我們只沿著程式碼流程一步步介紹。
void scheduleAttachRootWidget(Widget rootWidget) {
Timer.run(() {
attachRootWidget(rootWidget);
});
}
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
}
複製程式碼
scheduleAttachRootWidget
非同步呼叫了attachRootWidget
方法。
attachRootWidget
中初始化了一個RenderObjectToWidgetAdapter物件,建構函式傳入了renderView
和rootWidget
。renderView
就是RendererBinding的initInstances
方法中初始化的那個物件,rootWidget
則是我們寫的介面MyApp()。
從建構函式的引數名我們可以看到,renderView
是容器,rootWidget
是這個容器的child。也就是說renderView
是所有的Widget的根。
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
RenderObjectToWidgetAdapter({
this.child,
required this.container,
this.debugShortDescription,
}) : super(key: GlobalObjectKey(container));
複製程式碼
吐槽:RenderObjectToWidgetAdapter其實就是一個RenderObjectWidget子類,加個Adapter有點讓人誤解。
- RenderObjectToWidgetAdapter物件呼叫
attachToRenderTree
方法,把構造的工具**_buildOwner**傳進去。
attachToRenderTree
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
if (element == null) {
owner.lockState(() {
// 1
element = createElement();
element!.assignOwner(owner);
});
owner.buildScope(element!, () {
// 2
element!.mount(null, null);
});
// 3
SchedulerBinding.instance!.ensureVisualUpdate();
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
複製程式碼
- 建立了一個RenderObjectElement的子類RenderObjectToWidgetElement,並將構造工具buildOwner引用給了它;
RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
複製程式碼
- element呼叫
mount
方法。 - 先提前告訴Native platform想要重新整理介面。
- RenderObjectToWidgetElement
mount
// RenderObjectToWidgetElement
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
_rebuild();
}
// RenderObjectElement
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
// Element
void mount(Element? parent, dynamic newSlot) {
_parent = parent;
_slot = newSlot;
_lifecycleState = _ElementLifecycle.active;
_depth = _parent != null ? _parent!.depth + 1 : 1;
if (parent != null)
_owner = parent.owner;
final Key? key = widget.key;
if (key is GlobalKey) {
key._register(this);
}
_updateInheritance();
}
複製程式碼
- RenderObjectToWidgetElement的
mount
方法先呼叫Element的mount
方法。主要的作用就是設定_parent
,_slot
,_owner
,_depth
等的值;
簡單介紹下:
_parent
就是Element樹上的父節點,_slot
是插槽,可以簡單理解為子Element在父節點的位置,_depth
是在Element tree上的深度。
- 然後呼叫RenderObjectElement的
mount
方法。建立了一個renderObject
,其實就是renderView。然後把這個renderObject
掛載到RenderObject Tree上,之前的RenderObject Tree沒有內容,所以renderView就是根節點;
Flutter有三棵樹,Widget tree, Element Tree和RenderObject Tree。RenderObject Tree是真正渲染出來的內容。
- RenderObjectToWidgetElement
_rebuild
void _rebuild() {
try {
_child = updateChild(_child, widget.child, _rootChildSlot);
} catch (exception, stack) {
}
}
複製程式碼
_rebuild
的功能就是Build子Widget,這裡就是Build MyApp。
Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
final Element newChild;
if (child != null) {
if (hasSameSuperclass && child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
newChild = child;
} else {
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
// 建立Element
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
複製程式碼
updateChild
中如果child為null,newWidget不為null, 則會呼叫newChild = inflateWidget(newWidget, newSlot);
。
Element inflateWidget(Widget newWidget, dynamic newSlot) {
final Key? key = newWidget.key;
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
}
複製程式碼
inflateWidget
先建立一個Element,然後這個Element呼叫mount
方法。
又一個用mount
方法,你猜對了, 使用buildOwner對 Widget 樹---renderview->MyApp->MaterialApp... 一直Build下去,直到遍歷完成。
scheduleWarmUpFrame
scheduleWarmUpFrame
是SchedulerBinding的方法:
void scheduleWarmUpFrame() {
Timer.run(() {
handleBeginFrame(null);
});
Timer.run(() {
handleDrawFrame();
if (hadScheduledFrame)
scheduleFrame();
});
lockEvents(() async {
await endOfFrame;
});
}
複製程式碼
scheduleWarmUpFrame
就是呼叫handleBeginFrame
和handleDrawFrame
方法繪製一幀呈遞給GPU去顯示。
這裡需要說明的是scheduleWarmUpFrame
是立即去繪製的,沒有等待Vsyn的通知,因為啟動的顯示要越快越好。
後面的lockEvents
也是為了等待預約幀繪製完成後再去執行其他的任務。
繪製的是什麼?繪製的是RenderObject Tree對應的Layer Tree,最後以Scene的形式呈遞給GPU顯示。