# Flutter運行機制-從啟動到顯示
Flutter的入口在"lib/main.dart"的`main()`函數中,它是Dart應用程序的起點。在Flutter應用中,`main()`函數如下:
```
void main() {
runApp(MyApp());
}
```
可以看`main()`函數只調用了一個`runApp()`方法,我們看看`runApp()`方法中都做了什么:
```
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
```
參數app是一個Widget,它是Flutter應用啟動后要展示的第一個Widget。而WidgetsFlutterBinding正是綁定Widget 框架和Flutter engine的橋梁,定義如下:
```
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}
```
可以看到WidgetsFlutterBinding繼承自 BindingBase 并混入了很多Binding,在介紹這些Binding之前我們先介紹一下Window對象。我們看看Window的官方解釋:
> The most basic interface to the host operating system's user interface.
很明顯,Window正是Flutter Framework連接宿主操作系統的接口。我們看一下Window類的部分定義:
```
class Window {
// 當前設備的DPI,即一個物理相許顯示多少邏輯像素,數字越大,顯示效果就越精細保真。
// DPI是設備屏幕的固件屬性,如Nexus 6的屏幕DPI為3.5
double get devicePixelRatio => _devicePixelRatio;
// Flutter UI繪制區域的大小
Size get physicalSize => _physicalSize;
// 當前系統默認的語言Locale
Locale get locale;
// 當前系統字體縮放比例。
double get textScaleFactor => _textScaleFactor;
// 當繪制區域大小改變回調
VoidCallback get onMetricsChanged => _onMetricsChanged;
// Locale發生變化回調
VoidCallback get onLocaleChanged => _onLocaleChanged;
// 系統字體縮放變化回調
VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
// 繪制前回調,一般會受顯示器的垂直同步信號VSync驅動,當屏幕刷新時就會被調用
FrameCallback get onBeginFrame => _onBeginFrame;
// 繪制回調
VoidCallback get onDrawFrame => _onDrawFrame;
// 點擊或指針事件回調
PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
// 調度Frame,該方法執行后,onBeginFrame和onDrawFrame將緊接著會在合適時機被調用,
// 此方法會直接調用Flutter engine的Window_scheduleFrame方法
void scheduleFrame() native 'Window_scheduleFrame';
// 更新應用在GPU上的渲染,此方法會直接調用Flutter engine的Window_render方法
void render(Scene scene) native 'Window_render';
// 發送平臺消息
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) ;
// 平臺通道消息處理回調
PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
... //其它屬性及回調
}
```
可以看到Window類包含了當前設備和系統的一些信息以及Flutter Engine的一些回調。現在我們再回來看看WidgetsFlutterBinding混入的各種Binding。通過查看這些 Binding的源碼,我們可以發現這些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的橋梁。
`WidgetsFlutterBinding.ensureInitialized()`負責初始化一個WidgetsBinding的全局單例,緊接著會調用WidgetsBinding的attachRootWidget方法,該方法負責將根Widget添加到RenderView上,代碼如下:
```
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget
).attachToRenderTree(buildOwner, renderViewElement);
}
```
注意,代碼中的有`renderView`和`renderViewElement`兩個變量,`renderView`是一個RenderObject,它是渲染樹的根,而`renderViewElement`是`renderView`對應的Element對象,可見該方法主要完成了 根Widget 到根 RenderObject再到更Element的整個關聯過程。我們看看`attachToRenderTree`的源碼實現:
```
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element.assignOwner(owner);
});
owner.buildScope(element, () {
element.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element;
}
```
該方法負責創建根 Element,即 RenderObjectToWidgetElement,并且將 Element 與 Widget 進行關聯,即創建出 WidgetTree 對應的 ElementTree。如果 Element 已經創建過了,則將根 Element 中關聯的 Widget 設為新的,由此可以看出 Element 只會創建一次,后面會進行復用。那么BuildOwner是什么呢?其實他就是Widget framework的管理類,它跟蹤哪些Widget需要重新構建。
### 渲染
回到runApp的實現中,當調用完`attachRootWidget`后,最后一行會調用 `WidgetsFlutterBinding` 實例的 `scheduleWarmUpFrame()` 方法,該方法的實現在 SchedulerBinding 中,它被調用后會立即進行一次繪制(而不是等待"Vsync" 信號),在此次繪制結束前,該方法會鎖定事件分發,也就是說在本次繪制結束完成之前Flutter將不會響應各種事件,這可以保證在繪制過程中不會再觸發新的重繪。下面是`scheduleWarmUpFrame()` 方法的部分實現(省略了無關代碼):
```
void scheduleWarmUpFrame() {
...
Timer.run(() {
handleBeginFrame(null);
});
Timer.run(() {
handleDrawFrame();
resetEpoch();
});
// 鎖定事件
lockEvents(() async {
await endOfFrame;
Timeline.finishSync();
});
...
}
```
可以看到該方法中主要調用了`handleBeginFrame()` 和 `handleDrawFrame()` 兩個方法,在看這兩個方法之前我們首先了解一下 Frame 和c 的概念:
- Frame: 一次繪制過程,我們稱其為一幀。Flutter engine受顯示器垂直同步信號"VSync"的趨勢不斷的觸發繪制。我們之前說的Flutter可以實現60fps(Frame Per-Second),就是指一秒鐘可以觸發60次重繪,FPS值越大,界面就越流暢。
- FrameCallback:SchedulerBinding 類中有三個FrameCallback回調隊列, 在一次繪制過程中,這三個回調隊列會放在不同時機被執行:
1. transientCallbacks:用于存放一些臨時回調,一般存放動畫回調。可以通過`SchedulerBinding.instance.scheduleFrameCallback` 添加回調。
2. persistentCallbacks:用于存放一些持久的回調,不能在此類回調中再請求新的繪制幀,持久回調一經注冊則不能移除。`SchedulerBinding.instance.addPersitentFrameCallback()`,這個回調中處理了布局與繪制工作。
3. postFrameCallbacks:在Frame結束時只會被調用一次,調用后會被系統移除,可由 `SchedulerBinding.instance.addPostFrameCallback()` 注冊,注意,不要在此類回調中再觸發新的Frame,這可以會導致循環刷新。
現在請讀者自行查看`handleBeginFrame()` 和 `handleDrawFrame()` 兩個方法的源碼,可以發現前者主要是執行了transientCallbacks隊列,而后者執行了 persistentCallbacks 和 postFrameCallbacks 隊列。
### 繪制
渲染和繪制邏輯在RenderBinding 中實現,查看其源發,發現在其`initInstances()`方法中有如下代碼:
```
void initInstances() {
... //省略無關代碼
//監聽Window對象的事件
ui.window
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;
//添加PersistentFrameCallback
addPersistentFrameCallback(_handlePersistentFrameCallback);
}
```
我們看最后一行,通過`addPersistentFrameCallback` 向persistentCallbacks隊列添加了一個回調 `_handlePersistentFrameCallback`:
```
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
}
```
該方法直接調用了RenderBinding的`drawFrame()`方法:
```
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout(); //布局
pipelineOwner.flushCompositingBits(); //重繪之前的預處理操作,檢查RenderObject是否需要重繪
pipelineOwner.flushPaint(); // 重繪
renderView.compositeFrame(); // 將需要繪制的比特數據發給GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}
```
我們看看這些方法分別做了什么:
#### flushLayout()
```
void flushLayout() {
...
while (_nodesNeedingLayout.isNotEmpty) {
final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
for (RenderObject node in
dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
if (node._needsLayout && node.owner == this)
node._layoutWithoutResize();
}
}
}
}
```
源碼很簡單,該方法主要任務是更新了所有被標記為“dirty”的RenderObject的布局信息。主要的動作發生在`node._layoutWithoutResize()`方法中,該方法中會調用`performLayout()`進行重新布局。
#### flushCompositingBits()
```
void flushCompositingBits() {
_nodesNeedingCompositingBitsUpdate.sort(
(RenderObject a, RenderObject b) => a.depth - b.depth
);
for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
if (node._needsCompositingBitsUpdate && node.owner == this)
node._updateCompositingBits(); //更新RenderObject.needsCompositing屬性值
}
_nodesNeedingCompositingBitsUpdate.clear();
}
```
檢查RenderObject是否需要重繪,然后更新`RenderObject.needsCompositing`屬性,如果該屬性值被標記為`true`則需要重繪。
#### flushPaint()
```
void flushPaint() {
...
try {
final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// 反向遍歷需要重繪的RenderObject
for (RenderObject node in
dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
if (node._needsPaint && node.owner == this) {
if (node._layer.attached) {
// 真正的繪制邏輯
PaintingContext.repaintCompositedChild(node);
} else {
node._skippedPaintingOnLayer();
}
}
}
}
}
```
該方法進行了最終的繪制,可以看出它不是重繪了所有 RenderObject,而是只重繪了需要重繪的 RenderObject。真正的繪制是通過`PaintingContext.repaintCompositedChild()`來繪制的,該方法最終會調用Flutter engine提供的Canvas API來完成繪制。
#### compositeFrame()
```
void compositeFrame() {
...
try {
final ui.SceneBuilder builder = ui.SceneBuilder();
final ui.Scene scene = layer.buildScene(builder);
if (automaticSystemUiAdjustment)
_updateSystemChrome();
ui.window.render(scene); //調用Flutter engine的渲染API
scene.dispose();
} finally {
Timeline.finishSync();
}
}
```
這個方法中有一個Scene對象,Scene對象是一個數據結構,保存最終渲染后的像素信息。這個方法將Canvas畫好的Scene傳給`window.render()`方法,該方法會直接將scene信息發送給Flutter engine,最終又engine將圖像畫在設備屏幕上。
#### 最后
需要注意的是:由于RenderBinding只是一個mixin,而with它的是WidgetBinding,所以我們需要看看WidgetBinding中是否重寫該方法,查看WidgetBinding的`drawFrame()`方法源碼:
```
@override
void drawFrame() {
...//省略無關代碼
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame(); //調用RenderBinding的drawFrame()方法
buildOwner.finalizeTree();
}
}
```
我們發現在調用`RenderBinding.drawFrame()`方法前會調用 `buildOwner.buildScope()` (非首次繪制),該方法會將被標記為“dirty” 的 Element 進行 `rebuild()` 。
### 總結
本節介紹了Flutter APP從啟動到顯示到屏幕上的主流程,讀者可以結合前面章節對Widget、Element以及RenderObject的介紹來加強細節理解。
- 緣起
- 起步
- 移動開發技術簡介
- Flutter簡介
- 搭建Flutter開發環境
- 常見配置問題
- Dart語言簡介
- 第一個Flutter應用
- 計數器示例
- 路由管理
- 包管理
- 資源管理
- 調試Flutter APP
- Dart線程模型及異常捕獲
- 基礎Widgets
- Widget簡介
- 文本、字體樣式
- 按鈕
- 圖片和Icon
- 單選框和復選框
- 輸入框和表單
- 布局類Widgets
- 布局類Widgets簡介
- 線性布局Row、Column
- 彈性布局Flex
- 流式布局Wrap、Flow
- 層疊布局Stack、Positioned
- 容器類Widgets
- Padding
- 布局限制類容器ConstrainedBox、SizeBox
- 裝飾容器DecoratedBox
- 變換Transform
- Container容器
- Scaffold、TabBar、底部導航
- 可滾動Widgets
- 可滾動Widgets簡介
- SingleChildScrollView
- ListView
- GridView
- CustomScrollView
- 滾動監聽及控制ScrollController
- 功能型Widgets
- 導航返回攔截-WillPopScope
- 數據共享-InheritedWidget
- 主題-Theme
- 事件處理與通知
- 原始指針事件處理
- 手勢識別
- 全局事件總線
- 通知Notification
- 動畫
- Flutter動畫簡介
- 動畫結構
- 自定義路由過渡動畫
- Hero動畫
- 交錯動畫
- 自定義Widget
- 自定義Widget方法簡介
- 通過組合現有Widget實現
- 實例:TurnBox
- CustomPaint與Canvas
- 實例:圓形漸變進度條(自繪)
- 文件操作與網絡請求
- 文件操作
- Http請求-HttpClient
- Http請求-Dio package
- 實例:Http分塊下載
- WebSocket
- 使用Socket API
- Json轉Model
- 包與插件
- 開發package
- 插件開發:平臺通道簡介
- 插件開發:實現Android端API
- 插件開發:實現IOS端API
- 系統能力調用
- 國際化
- 讓App支持多語言
- 實現Localizations
- 使用Intl包
- Flutter核心原理
- Flutter UI系統
- Element和BuildContext
- RenderObject與RenderBox
- Flutter從啟動到顯示