--- title: 'Flutter 三棵樹:深入 UI 框架' disqus: kyleAlien --- Flutter 三棵樹:深入 UI 框架 === ## Overview of Content 關於 Flutter 的渲染機制,主要是透過三棵樹來進行的,分別是 ^1^ 最常操控的 **Widget**、^2^ 包在中間銜接渲染的 **Element**、^3^ 真正進行渲染的 **RenderObject** > 這三顆樹是 UI 架構的核心概念,它們共同協作來構建、描述和渲染用戶界面 ```mermaid graph LR subgraph Flutter 渲染機制 Widget --> Element --> RenderObject end ``` :::info 以下使用的 Flutter 版本為 `3.22.2` ::: :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**深入解析 Flutter 三顆樹:Widget、Element 與 RenderObject 完整指南**](https://devtechascendancy.com/flutter-widget-element-renderobject-guide/) ::: [TOC] ## 元件關係:包裹、繼承、樹關係 首先我們先從我們開發 Flutter 時最常接觸到的 Widget(也就是元件)開始看它們之間的依賴關係,進發現我們平常所用的 Widget 的「包裹」、「繼承」與「樹」關係 ### Widget 樹:包裹、樹關係 * 直接創建一個 Flutter 專案,它會有基本的 Demo 示例,從 MyApp 類開始分析 Widget 樹狀圖… (這裡我們省略一步一步分析,直接看最表面的 Widget 關係) ```dart= void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); } } ``` 從以上範例中的分析,這裡我們可以先觀察最常使用到的 Widget 建構,它們是一層一層包裹(`Wrapper`)的結構,並且其中存在著「**多元樹** 🌲」的關係 關係如下圖所示 > ![](https://i.imgur.com/RJWxXvx.png) ### Widget 繼承關係:發現 Element * 上面有使用到的組件有 `Scaffold`、`AppBar`、`Text`、`Center`、`Colume`、`Icon`... 等等的組件,現在我們來看這些組件的「**繼承關係**」,會發現它們 **最終都會歸到 Widget 這個抽象類中** > ![](https://i.imgur.com/kwZ9lXN.png) 而 Widget 類的重點方法有四個,如下表 | Widget 重點方法 | 說明 | | - | - | | `createElement() → Element` | 創建並返回與此 Widget 對應的 Element。這是每個 Widget 都必須實現的方法,因為 Flutter 框架通過 Element 來管理 Widget 的佈局和繪製 | | `==(Object other) → bool` | 比較兩個 Widget 是否相等。通常是通過比對 runtimeType 和 key 來確定相等性 | | `hashCode → int` | 返回該 Widget 的哈希值,這是與 `==` 方法相配合使用的,用於在集合(如 `HashMap`)中有效地比較和存儲 Widget | | `canUpdate(Widget oldWidget, Widget newWidget) → bool` | 檢查是否可以更新一個現有的 Widget,這通常用於在 Widget 樹更新時決定是否需要重建或只是更新現有的 Element | 1. `createElement()`:此方法是 Widget 最核心的部分,負責將 Widget 與 Element 系統進行對接 :::info 這裡我們會發現 `createElement()` 方法,這是第一次窺見到 Element(三顆樹中其中一個)… 該方法會讓 Widget 與 Element 產生關係 ::: 2. `== 與 hashCode`:這兩個方法用於比較和管理 Widget 的唯一性,特別是在有狀態管理和 Widget 重建過程中 3. `canUpdate()`:這個方法決定了當 Widget 樹發生變化時,是否應該替換現有的 Widget,還是只進行更新 > ![image](https://hackmd.io/_uploads/H1O59EpnC.png) ## 認識三顆樹:Widget、Element、RenderObject 在 Flutter 框架中,Widget 樹、Element 樹 和 RenderObject 樹 是 **UI 架構的核心概念**,它們共同協作來構建、描述和渲染用戶界面 ### 認識 Widget 樹:連接 Element 實例 * **認識 Widget 樹**: Widget 是 Flutter 中最基本的構建單元,描述了應用程序的外觀和結構,並且 **每一個 Widget 都是一個靜態的、不可變的配置** * **Widget 是一個抽象類**,並且有一個一定要實現的抽象方法 **==createElement==,該函數會返回一個 Element 類的實例**(這就是三顆樹的其中一個) > 這樣就可以知道 **++Widget 的另外一個重點功能是在創建 Element 物件++** 而我們在寫 Flutter 應用的時候其實是不常碰到 Element,而是透過創建各種 Widget 來建構畫面,而其實 Widget 是讓我們與 Element 做隔離的元件 ```mermaid graph LR 開發者 --> |使用| Widget Widget -.-> |創建| Element ``` 看看 Widget 的繼承關係,可以知道 **所有繼承 Widget 的類都需要實現 Element 類,所以 Element 與 Widget 是一個一個對應的**,像是… 1. StatefulWidget → `StatefulElement` 2. StatelessWidget → `StatelessElement` 3. RenderObjectWidget → `RenderObjectElement` > ![](https://i.imgur.com/c23cmp6.png) ### 不同 Widget 的 Element 實作 * 再來看看 Element 與各種各自實作 Element 的繼承關係,這邊除了有常見的 `StatefulElement` & `StatelessElement`,還有**看見另外一個 `RenderObjectElement`** 類;接下來會做一些簡單的介紹… > ![](https://i.imgur.com/3yeEbHc.png) 1. **StatefulElement 類** * 使用場景: 用於管理與 StatefulWidget 關聯的 Element * 主要功能: * **當 StatefulWidget 重建時,StatefulElement 保持 State 不變**,**這樣即使 Widget 被重建**,**狀態仍然保留**(例如:計數器、表單中的輸入數據等) * StatefulElement 與一個 State 物件相關聯,這個 **State 是可變的**,並且在 Widget 的生命週期中持續存在 :::info 也就是我們平常在更新 Widget 畫面時,會使用的 `setState` 方法,就是用來刷新 StatefulElement 的 State! ::: * StatefulElement 通過調用 State 的 `build()` 方法來構建和更新其子樹 ```mermaid graph TD StatefulWidget --> |關聯| StatefulElement StatefulElement --> State StatefulElement -->|build| Widget State -->|setState| StatefulElement Widget -->|rebuild| StatefulElement ``` 2. **StatelessElement 類** * 使用場景: 用於管理與 StatelessWidget 關聯的 Element * 主要功能: * **StatelessElement 不會保留任何狀態**,當它的父 Widget 樹更新時,只會調用 StatelessWidget 的 `build()` 方法來重建 UI > 也就是 StatelessElement 不具有 State,所以重新建構時是重新建構 Element * 由於 StatelessWidget 是不可變的,它們每次更新時都會丟棄舊的元素並使用新元素重新構建(也就是缺乏 State,所以不會保留任何狀態) > StatelessElement 不關心狀態的變化,因為 StatelessWidget 只關注不可變的 UI 呈現 ```mermaid graph TD StatelessWidget --> StatelessElement StatelessElement -->|build| Widget Widget -->|rebuild| StatelessElement ``` 3. **RenderObjectElement 類**(跟佈局更相關的 Element) * 使用場景: 用於管理與 RenderObjectWidget 關聯的 Element * 主要功能: * **RenderObjectElement 主要負責創建和管理 RenderObject,這是用於處理佈局和繪製的物件** * 當 Widget 發生變化時,RenderObjectElement 決定如何更新或重建其對應的 RenderObject :::success * 它與 `StatefulElement` 和 `StatelessElement` 不同,**RenderObjectElement 的重點是「處理佈局、尺寸和繪製邏輯」**,而不是單純地重建子 Widget 樹(請注意這裡說的是邏輯… 至於繪製則是交給 `RenderObject` 類) > 它通常會用於像 `Container`、`CustomPaint` 等需要處理具體佈局和繪製的 Widget ::: ```mermaid graph TD RenderObjectWidget --> RenderObjectElement RenderObjectElement -->|createRenderObject| RenderObject RenderObject -->|layout、paint| Screen RenderObjectElement -->|update| RenderObject ``` ### 認識 RenderObject 樹:Widget 與 RenderObject 關係 * 我們知道一個 Widget 通常是依賴著其他 Widget 建構 (初始專案的 MyApp 就是這樣一個 Widget),而現在我們就來找找 Flutter SDK 提供的原生 Widget 最終是如何實現的 > ![](https://i.imgur.com/yeXFdxE.png) 以 `Text`、`Column`、`Center`、`Icon`、`Image` Widget… 來做觀察 (注意顏色、線條,個代表了不同意義),**可以看到最終元件都會與於 RenderObject 產生關係**(大部分都會透過 Element 與 RenderObject 產生關係) :::success * **可以把 Widget 看成是一個 ++設計稿++**,將稿 (Widget) 完成後就不會再改變,這時就只需要放入對應的元件(`RenderObject`) 就可以生成不同的 UI 介面 **RenderObject 才是真正渲染畫面的物件** ::: * **那 RenderObject 跟 RenderObjectElement 是同樣含義嗎?** **並不是~** 兩者主要負責的責任不同 1. **RenderObject 類**:負責繪製 RenderObject 是一個專門負責處理 `佈局`、`繪製`、`命中測試` 和 `邊界處理` 的物件,「**它是渲染層的核心**」,負責實際在螢幕上繪製和排列可視的內容 > RenderObject 決定 Widget 應該如何呈現在螢幕上,它處理像素繪製、邊界計算以及 Widget 的尺寸與位置 2. **RenderObjectElement 類**:負責連接 RenderObjectElement 是 Element 的一個具體子類,「**它負責將 Widget 和 RenderObject 連接起來**」… > 並且它管理 Widget 和 RenderObject 之間的關係,並確保 RenderObject 正確地反映 Widget 的狀態 ```mermaid graph LR RenderObject <--> |連接| RenderObjectElement <--> |連接| Widget RenderObject -.-> 繪製 ``` ### 進一步認識 RenderObject * RenderObject 是一個大家族,像是 `RenderView`、`RenderBox` 而其中 RenderBox 有許多的衍生類(像是 `RenderWrap`、`RenderStack`、`RednerFlex`… 等等),簡單功能介紹如下表 | 類別名稱 | 簡介 | | - | - | | RenderObject | Flutter 渲染系統的基礎類,負責定義佈局、繪製、命中測試等基本功能,其他渲染對象都是從它繼承而來 | | RenderBox | RenderObject 的子類,定義了 2D 矩形盒模型的佈局規則,許多常見的渲染對象(如 RenderFlex, RenderStack)都繼承自 RenderBox | | RenderView | 渲染樹的根節點,表示應用程式的「**視圖**」,它負責與 Flutter 引擎交互,並決定渲染區域的大小和邊界 | | RenderWrap | 一種專門的 RenderBox,負責在可用空間內將子對象按行或列排列,並根據需要進行換行,適合用於實現自適應佈局 | | RenderStack | 另一種 RenderBox,允許子對象重疊繪製,類似於 HTML 中的 z-index,用於堆疊式佈局 | | RenderFlex | 定義了一個彈性盒模型,允許將子對象水平或垂直排列,並根據可用空間分配每個子對象的大小,類似於 CSS 中的 flexbox | | RenderFlow | 一個更加靈活和強大的佈局類別,它允許開發者自定義佈局規則,通過覆寫佈局邏輯來控制子對象的排列方式 | | RenderImage | 專門用來繪製圖像的渲染對象,負責處理圖像的佈局和繪製邏輯,常見於顯示圖片的 Widget,如 Image | 從上表的介紹中,我們可以很清楚的知道 RenderBox 的子類負責各式各樣的佈局,而 RenderView 負責個別元件的渲染 > ![image](https://hackmd.io/_uploads/r1EuVra30.png) * 再來看看 RenderObject 中幾個比較重要的方法(它的方法太多了,這邊我們只挑幾種介紹),**從這些方法就可以看出 ==RenderObject 可布局、繪畫、響應事件==** | RenderObject 方法 | 說明 | | -------- | -------- | | `layout` | 就像是 Android's onLayout,用來 **對元件布局** | | `paint` | 就像是 Android's onDraw,用來 **繪製元件** | | `handleEvent` | 就像是 Android's dispatchTouchEvent ,用來**處理事件** | 看看 SDK 提供的 Widget 是否有重寫 layout、paint、handleEvent 方法,並且每個 Widget 的著重重點在哪裡 > ![](https://i.imgur.com/q2DpjoB.png) 1. 可見元件 TextWidget:藉由上圖,我們這邊直接看 TextWidget 對應的 RenderBox 的子類 `RenderParagraph` 我們可以看到 RenderParagraph#paint 方法在繪製時主要是透過 `TextPainter` 去繪製文字,而繪製是繪製在 Canvas 之上 ```dart= class RenderParagraph extends RenderBox with ... { final TextPainter _textPainter; @override void paint(PaintingContext context, Offset offset) { _layoutTextWithConstraints(constraints); ... 省略 // 查看 paint 方法 _textPainter.paint(context.canvas, offset); ... 省略 } } // ---------------------------------------------------------- class TextPainter { void paint(Canvas canvas, Offset offset) { assert(... 略); // 使用 canvas.drawParagraph 繪製 canvas.drawParagraph(_paragraph!, offset); } } ``` 2. 可見元件 ImageWidget:藉由上圖,我們這邊直接看 ImageWidget 對應的 RenderBox 的子類 `RenderImage` 我們可以看到 RenderImage#paint 方法在繪製時主要是透過 `paintImage` 方法去繪製圖片,繪製同樣是繪製在 Canvas 之上 ```java= // rendering/image.dart class RenderImage extends RenderBox { @override void paint(PaintingContext context, Offset offset) { ... 略 // 查看 paintImage 方法 paintImage( canvas: context.canvas, rect: offset & size, image: _image!, ... 略 ); } } // ------------------------------------------------------------- // decoration_image.dart void paintImage({ required Canvas canvas, required Rect rect, required ui.Image image, ... 略 }) { // 這裡就可以證明是用 canvas 來繪畫 Image canvas.translate(0.0, -dy); canvas.scale(1.0, -1.0); canvas.translate(0.0, dy); } ``` 3. 布局元件 Column:這裡 paint 就不是重點,重點在 layout ```java= class RenderFlex extends RenderBox with ... { @override void paint(PaintingContext context, Offset offset) { ... switch (_direction) { case Axis.horizontal: overflowChildRect = Rect.fromLTWH(0.0, 0.0, size.width + _overflow, 0.0); break; case Axis.vertical: overflowChildRect = Rect.fromLTWH(0.0, 0.0, 0.0, size.height + _overflow); break; } paintOverflowIndicator(context, offset, Offset.zero & size, overflowChildRect, overflowHints: debugOverflowHints); } } // --------------------------------------- abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget { void layout(Constraints constraints, { bool parentUsesSize = false }) { ... 略 } } ``` ### 認識 Element 樹:Element 重點成員、生命週期 * Element 是 Widget 的實例化對象,它是 Widget 和 RenderObject 之間的橋樑;**Element 樹負責管理 Widget 的渲染、狀態維護,以及與 RenderObject 之間的交互**(仲介的角色) 而 Element 的重點成員如下表所示 | Element 成員 | 功能 | | -------- | -------- | | `_dirty` | 是否更新渲染元件 | | `_owner` | 負責管理這個 Element 樹的生命周期和構建過程。每個 Element 都與一個 BuildOwner 相關聯 | | `_lifecycleState` | 標記 Element 的當前生命周期狀態,例如 active 或 inactive,這有助於追蹤 Element 是否在樹中活躍,還是即將被移除 | | `_parent` | 父類(不是傳統意義上的繼承,而是在其之上的 `Element` 物件實例),這可以看出 **Element 是 Tree 結構** | | `_renderObject` | 對應的 RenderObject,如果 Widget 需要渲染,Element 會持有一個 RenderObject 物件,該物件負責實際的佈局和繪製 | | `_depth` | 該 Element 在樹中的深度,這有助於確定 Element 的更新順序,越深的元素會在重建過程中後更新 | | `_buildContext` | 每個 Element 都實現了 BuildContext,通過它可以訪問與 Widget 相關的依賴上下文 | ```dart= abstract class Element extends DiagnosticableTree implements BuildContext { bool _dirty = true; BuildOwner? _owner; Element? _parent; // 生命狀態 _ElementLifecycle _lifecycleState = _ElementLifecycle.initial; Element(Widget widget) : assert(widget != null), _widget = widget; } ``` * 接著我們要知道 Element 也有自己的「生命週期, `_lifecycleState`」,其生命週期有下面幾種階段(就像是 Android 的 Activity 一般) | 生命狀態 | 說明 | | -------- | -------- | | `initial` | 剛建構 Widget 時尚未連接上 View Tree | | `active` | **透過 `mount` 方法連接到 View Tree**,並顯示在畫面上 | | `inactive` | 可透過 deactivateChild 將某個下層元素從 View Tree 剔除,也在畫面上 | | `defunct` | 當框架調用 unmount 時,會從樹中移除,並且再也不會回到 View Tree | ```dart= enum _ElementLifecycle { initial, // 初始化 active, // 激活 inactive, // 未激活 defunct, // 死亡 } ``` 而生命週期的變動,是可以通過幾種方法的調用來達到… 舉例:像是通過調用 Element#`mount` 方法,就可以激活該元件到 active 狀態 > `mount` 之後會再詳細介紹 ```java= // Element void mount(Element? parent, Object? newSlot) { assert(_lifecycleState == _ElementLifecycle.initial); ... // 將 Widget 改為激活狀態 _lifecycleState = _ElementLifecycle.active; ... } ``` ## 三棵樹的根源 Flutter App 在初始化的過程就會接觸到三棵樹(`Widget`、`Element`、`RenderObject`)的根源,知道這些根源我們就可以大概地去瞭解 Flutter App 是如何與 UI 框架的最初根源,好方便我們之後追尋; 而這裡我們要找的就是三棵樹的根源 ### 找 Element 根類:RootElement 的創建 * 這邊我透過在 Element#`mount` 方法中添加一個 `print` 方法,來看看 element tree 在掛載時的結構層級 ```dart= // framework.dart void mount(Element? parent, Object? newSlot) { ... _parent = parent; print("_parent: ${parent.runtimeType}"); ... } ``` 如下圖所見 由於這個 Tree 太長,我就不全部列出了,沒有太大的意義,**重點我們可以看到 ++Element 的根物件的類是 `RootElement`++** > ![image](https://hackmd.io/_uploads/BJCwnT1pA.png) :::success * 或是你在 Debug 模式下把斷點設置在 MyApp#`build` 函數中,並在進入斷點時,查看 `_parent` 成員,一直點到最底層也可以看到跟物件為 `RootElement` > ![image](https://hackmd.io/_uploads/SyYDpaJ6A.png) ::: * 接著,我們要找尋 RootElement 的持有者(是誰創建的),可以透過以下方法 1. 先把斷點下在 RootElement 類的建構函數,在 Debug 模式運行 APP > ![image](https://hackmd.io/_uploads/HkSiaTkpR.png) 2. 在進入斷點後,單步執行,就可以發現 **RootElement 是由 ==RootWidget==** 類創建 > ![image](https://hackmd.io/_uploads/HJZlCpJa0.png) ### 找 RanderObject 根類 * 接續上個小節,在 Debug 模式下,我們透過查看 Element 的根結點類(`RootElement`)的 `_renderObject` 成員 就可以發現負責渲染畫面的 **渲染樹(`RanderObject`)的根節點就是 `_ReusableRenderView`,而開啟後會發現 `_ReusableRenderView` 就是繼承於 ==RenderView==** 類 > ![image](https://hackmd.io/_uploads/S1P1lRy6C.png) :::info 而 `_ReusableRenderView` 被創建的時機則是在 `runApp` 初始化的過程中,下面小節會再介紹 ::: ## runApp 初始化、建構 我們來分析 ++`runApp()`++ 這個函數:從這個函數我們往下看,就可以看到三棵樹的初始化過程做了什麼事情,並且清楚的知道 Flutter App 是如何初始化應用並與 UI 框架產生關聯的 ```dart= void runApp(Widget app) { final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); _runWidget(binding.wrapWithDefaultView(app), binding, 'runApp'); } ``` ### 簡單分析 WidgetsFlutterBinding * **`WidgetsFlutterBinding` 類是連接 Flutter widget、Flutter engine 的樞紐**,其有許多種功能… 這邊我們簡單地進入源碼看一下 `WidgetsFlutterBinding` 類就可以發現幾件事情 * WidgetsFlutterBinding 類的實例是一個全局單例 * 它透過混合繼承的方式(`with`)讓 `WidgetsFlutterBinding` 有以下功能 1. GestureBinding 手勢系統 2. SchedulerBinding 調度系統 3. ServicesBinding 綁定平台消息通道 4. PaintingBinding 繪製 5. WidgetsBinding 連接組件 & 引擎 ```dart= // binding.dart static WidgetsBinding? get instance => _instance; static WidgetsBinding? _instance; class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding { static WidgetsBinding ensureInitialized() { if (WidgetsBinding.instance == null) // 單例 WidgetsFlutterBinding(); return WidgetsBinding.instance!; } } ``` ### WidgetsFlutterBinding 創建過程:with 與 super 配合 * 透過 Debug 模式下單步執行,可以發現在建構 `WidgetsFlutterBinding` 物件時,會先建構 BindingBase 父類,BindingBase 的建構函數會呼叫自身的 initInstances 方法,而這個方法會被其許多混合繼承類複寫 ```dart= // foundation/binding.dart abstract class BindingBase { BindingBase() { ... initInstances(); ... } @protected @mustCallSuper void initInstances() { assert(!_debugInitialized); // 保證該函數只會執行一次 assert(() { _debugInitialized = true; return true; }()); } } ``` > ![image](https://hackmd.io/_uploads/ryIQlCypC.png) 而這些方法執行的順序就較為不同 (簡單來說 **由最後一個覆寫的物件開始執行**),但是!它內部有是使用到 `super` 關鍵字,由於 `super` 與 `with` 的配合,最終會導致 `initInstances` 的順序仍是由 `GestureBinding` 到 `WidgetsBinding` 1. 先執行 WidgetsBinding 類的 initInstances 方法 ```dart= // widgets/binding.dart mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding { @override void initInstances() { super.initInstances(); // 呼叫到 RendererBinding _instance = this; ... _buildOwner = BuildOwner(); } } ``` 2. 再接續會執行 RendererBinding 類的 initInstances 方法 ```dart= mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable { @override void initInstances() { super.initInstances(); // 呼叫到 SemanticsBinding ... } } ``` 3. 再接續會執行 SemanticsBinding 類的 initInstances 方法 ```dart= mixin SemanticsBinding on BindingBase { @override void initInstances() { super.initInstances(); // 呼叫到 PaintingBinding ... } } ``` 4. 再接續會執行 PaintingBinding 類的 initInstances 方法 ```dart= mixin PaintingBinding on BindingBase, ServicesBinding { @override void initInstances() { super.initInstances(); // 呼叫到 SchedulerBinding ... } } ``` 5. 再接續會執行 SchedulerBinding 類的 initInstances 方法 ```dart= mixin SchedulerBinding on BindingBase { @override void initInstances() { super.initInstances(); // 呼叫到 SemanticsBinding ... } } ``` 6. 再接續會執行 SemanticsBinding 類的 initInstances 方法 ```dart= mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget { @override void initInstances() { super.initInstances(); // 最終回到 BindingBase 的 initInstances 方法 // 執行完 BindingBase 的 initInstances 方法後又回到到這裡繼續執行 ... // 執行完往 SchedulerBinding 回推 } } ``` :::warning * **`with` 與 `super` 執行順序** 如同前面的說,是從最後一個覆寫得類開始執行,**但在這裡透過 super 不斷執行父類方法**(像是 `WidgetsBinding` 不斷執行 super 後會接續到 `GestureBinding`),最終全部執行完後又是從 `GestureBinding` 執行到 `WidgetsBinding` ::: > ![](https://i.imgur.com/M4clzrD.png) ```mermaid graph TB WidgetsBinding --> |1. super| RendererBinding --> |2. super| SemanticsBinding --> |3. super| PaintingBinding --> |4. super| ServicesBinding --> |5. super| SchedulerBinding --> |6. super| BindingBase BindingBase -.-> |7.| SchedulerBinding -.-> |8.| ServicesBinding -.-> |9.| PaintingBinding -.-> |10.| SemanticsBinding -.-> |11.| RendererBinding -.-> |12.| WidgetsBinding ``` ### 連接根 Widget:創建 RootElement、RenderView ```dart= void runApp(Widget app) { final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); // 查看 _runWidget 方法 _runWidget(binding.wrapWithDefaultView(app), binding, 'runApp'); } void _runWidget(Widget app, WidgetsBinding binding, String debugEntryPoint) { assert(binding.debugCheckZone(debugEntryPoint)); binding ..scheduleAttachRootWidget(app) // 重點分析 ..scheduleWarmUpFrame(); } ``` * 回到上面 WidgetsFlutterBinding#ensureInitialized 完成後就會執行 `scheduleAttachRootWidget` 方法,這個方法就會創建 RootWidget 物件的實例,而 RootWidget 物件又會創建根元素 `RootElement` ```dart= // widgets/binding.dart mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding { Element? _renderViewElement; @protected void scheduleAttachRootWidget(Widget rootWidget) { // rootWidget 就是 MyApp Widget Timer.run(() { attachRootWidget(rootWidget); }); } void attachRootWidget(Widget rootWidget) { // 創建 RootWidget 物件實例 attachToBuildOwner(RootWidget( debugShortDescription: '[root]', child: rootWidget, )); } } ``` * 而這裡又要再連接到上面我們分析的 RendererBinding 類: RendererBinding 類在進行初始化時 `renderView` 成員,也就是說它會在初始化時被創建,而創建的子類型就是 `_ReusableRenderView` ```java= // rendering/binding.dart mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable { late final RenderView renderView = _ReusableRenderView( view: platformDispatcher.implicitView!, ); } ``` :::warning 但這個做法可能在新版本被遺棄,改由 renderViews 管理(因為 RendererBinding 或許需要管理多個 RenderView) ::: ### 根 Widget 調用 mount:掛載 RootWidget * 我們前面說在初始化時會創建 `RootWidget` 物件(根 Widget),接著我們來看 Widget 何時被掛載上去的,我們從 `attachToBuildOwner` 函數開始… ```dart= // widgets/binding.dart void attachToBuildOwner(RootWidget widget) { final bool isBootstrapFrame = rootElement == null; _readyToProduceFrames = true; // @ 查看 attach 方法 _rootElement = widget.attach(buildOwner!, rootElement as RootElement?); if (isBootstrapFrame) { SchedulerBinding.instance.ensureVisualUpdate(); } } ``` 1. 在 `attachToBuildOwner` 函數中會調用 RootWidget#`attach` 方法 2. 而在 RootWidget#`attach` 方法中又會發現它調用了 RootElement#`mount` 方法,剛方法會把 RootElement 掛載到 Flutter Widget 樹中進行初始化 ```dart= // widgets/binding.dart RootElement attach(BuildOwner owner, [ RootElement? element ]) { if (element == null) { owner.lockState(() { element = createElement(); assert(element != null); element!.assignOwner(owner); }); owner.buildScope(element!, () { // 調用 element!.mount(/* parent */ null, /* slot */ null); }); } else { element._newWidget = this; element.markNeedsBuild(); } return element!; } ``` ## 掛載根元素 RootElement 並且分析至此,我們可以稍為整理一下根元素所創建的樹個別是哪些,如下表所示 | 樹 | 真正物件 | 創建、連接時機 | | -------- | -------- | -------- | | Widget | `RootWidget` | 創建時機:runApp 調用 `attachRootWidget` 方法 | | Element | `RootElement` | 創建時機:RootWidget 類中對應的 `createElement` 方法 | | RenderObject | `_ReusableRenderView`(就是 RenderView) | 初始化 RendererBinding 時的 `renderView` 成員 | ### 連接的入口:attach 函數 * 在 `attach` 函數中,我們可以看到它會呼叫 `createElement` 方法,該方法會就會創建 Element 物件(根物件會創建 `RootElement`) ```dart= RootElement attach(BuildOwner owner, [ RootElement? element ]) { if (element == null) { owner.lockState(() { // 呼叫 createElement 方法,創建 RootElement 物件 element = createElement(); assert(element != null); element!.assignOwner(owner); }); owner.buildScope(element!, () { element!.mount(/* parent */ null, /* slot */ null); }); } else { element._newWidget = this; element.markNeedsBuild(); } return element!; } ``` 並且接下來我們會分析幾個重點 1. RootElement#`mount` 方法 2. Element#`mount` 方法 ### Element 的 mount 方法:初始化生命週期 * 我們來看看最原始的 Element#`mount` 方法,所有繼承於 Element 類的元素都需要呼叫這個方法… 從以下程式中,可以發現 `mount` 主要就是決定 Widget parent、深度、擁有者… 等等資訊 ```dart= // framework.dart abstract class Element extends DiagnosticableTree implements BuildContext { Element? _parent; BuildOwner? _owner; @mustCallSuper void mount(Element? parent, Object? newSlot) { // 判斷生命狀態,必須是初始化 assert(_lifecycleState == _ElementLifecycle.initial); // 判斷 widget 非空,這裡的 widget 就是 RootElement assert(widget != null); // parent 必須是空 assert(_parent == null); ... 其他斷言 _parent = parent; // 將傳進來的第一個參數作為 _parent,第一是傳進來是 null _slot = newSlot; // 切換到 active 狀態 _lifecycleState = _ElementLifecycle.active; // 紀錄當前樹的深度 _depth = _parent != null ? _parent!.depth + 1 : 1; if (parent != null) { // _owner = parent.owner; // 傳遞父的 owner } assert(owner != null); final Key? key = widget.key; if (key is GlobalKey) { owner!._registerGlobalKey(key, this); } _updateInheritance(); } } ``` ### RootElement 的 mount 方法:加載子佈局 * RootElement 也是繼承於 Element 類,而它自身也有複寫 `mount` 方法,並且在 `mount` 方法中呼叫私有的 `_rebuild()` 方法 ```dart= // widgets/binding.dart class RootElement extends Element with RootElementMixin { @override void mount(Element? parent, Object? newSlot) { assert(parent == null); // We are the root! super.mount(parent, newSlot); _rebuild(); assert(_child != null); super.performRebuild(); // clears the "dirty" flag } ... } ``` 1. `_rebuild()` 方法中主要做的事情就是呼叫 Element#`updateChild(...)` 方法,該方法主要是返回子 Widget 元素 ```dart= // widgets/binding.dart Element? _child; void _rebuild() { try { _child = updateChild(_child, (widget as RootWidget).child, /* slot */ null); } catch (exception, stack) { ... 省略部分 } } ``` 2. **`updateChild` 函數**:這裡我們當成是初次加載,所以在初次呼叫時 `_child` 成員為 null,透過一些省略… 在帶入 `updateChild` 函數後我們可以簡化為以下程式來看 我們可以看到它主要是再呼叫了 `inflateWidget` 方法 ```dart= // framework.dart // 假設帶入的 child 為 null Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) { ... final Element newChild; if (child != null) { ... } else { // @ 近一步查看 inflateWidget 方法 newChild = inflateWidget(newWidget, newSlot); } ... return newChild; } ``` 3. **`inflateWidget` 函數**:該函數會先判斷 Key 是否是 GlobalKey,如果是 GlobalKey 則呼叫 `updateChild` 更新… 否則就呼叫 `createElement` 加載子類的 Element ```dart= abstract class Element extends DiagnosticableTree implements BuildContext { // newWidget == MyApp Element inflateWidget(Widget newWidget, Object? newSlot) { ... try { final Key? key = newWidget.key; if (key is GlobalKey) { final Element? newChild = _retakeInactiveElement(key, newWidget); if (newChild != null) { ... // 若是有全域的 Key 則 updateChild final Element? updatedChild = updateChild(newChild, newWidget, newSlot); assert(newChild == updatedChild); return updatedChild!; } } // 若沒有全域的 Key 則呼叫 createElement final Element newChild = newWidget.createElement(); ... // 調用 Child 自己的 Element,並且 將自己作為 parent 傳入 (mount 的第一個參數 this) newChild.mount(this, newSlot); assert(newChild._lifecycleState == _ElementLifecycle.active); return newChild; } finally { if (isTimelineTracked) { FlutterTimeline.finishSync(); } } } ``` :::info 這裡可以很明顯地看出一個遞迴加載 Widget 的過程 ```mermaid sequenceDiagram participant Element participant ChildElement Element ->> ChildElement: 檢查是否是 GlobalKey alt GlobalKey found Element ->> Element: _retakeInactiveElement(key, newWidget) alt Element retaken Element ->> Element: updateChild(newChild, newWidget, newSlot) Element ->> Element: Return updatedChild end else Element ->> ChildElement: 呼叫 createElement() ChildElement ->> Element: 創建新 Element Element ->> Element: 加載到自身 Element ->> Element: Return newChild end ``` ::: ### updateChild 方法的細節:4 種狀況 * Element#updateChild 這個方法作用是返回一個 Element 對象 (作為調用者的 Child),以下針對該函數的入參 child、newWidget 來進行判斷 更新 Child 總共有四種狀況,如下表所示 | | `newWidget == null` | `newWidget != null` | | -------- | -------- | -------- | | child == null | ^1^ 返回 null | ^3^ 用 `inflateWidget` 返回新元素 | | child != null | ^2^ 移除原來的 Child,返回 null | ^4^ 返回 Child or inflateWidget | ```dart= abstract class Element extends DiagnosticableTree implements BuildContext { Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) { if (newWidget == null) { if (child != null) // 該元素的 Child 不為空 deactivateChild(child); // 2. 移除元素的 Child return null; // 1. 返回 null } final Element newChild; if (child != null) { // 4. 該元素的 Child 不為空 bool hasSameSuperclass = true; ... 斷言 if (hasSameSuperclass && child.widget == newWidget) // 創建出來的 Widget 和自身的 Child 相同 if (child.slot != newSlot) updateSlotForChild(child, newSlot); newChild = child; // 返回自己的 Child (省去加載) } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) { // cnaUpdate 重點 if (child.slot != newSlot) updateSlotForChild(child, newSlot); child.update(newWidget); ... 斷言 newChild = child; } else { deactivateChild(child); ... 斷言 newChild = inflateWidget(newWidget, newSlot); } } else { // 3. inflateWidget 返回新元素 newChild = inflateWidget(newWidget, newSlot); } ... 斷言 return newChild; } } ``` ### StatelessElement、StatefulElement:裝飾 & 責任鏈模式 * 從上面可以看到 Element#inflateWidget 調用了 MyApp 的 createElement 方法,而 MyApp 其實並沒有 createElement 方法,所以它會往 StatelessWidget 父類去找 > StatelessElement 對應 StatelessWidget ```java= class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } // ------------------------------------------------------------- // framework abstract class StatelessWidget extends Widget { const StatelessWidget({ Key? key }) : super(key: key); @override StatelessElement createElement() => StatelessElement(this); } ``` > ![](https://i.imgur.com/2jMIhRO.png) * 當 inflateWidget 調用 `createElement` 後會創建 `StatelessElement` 物件 其反應的順序簡單來說就是 `ComponentElement#mount` -> `_firstBuild` -> `rebuild` -> `performRebuild` ```java= class StatelessElement extends ComponentElement { /// Creates an element that uses the given widget as its configuration. StatelessElement(StatelessWidget widget) : super(widget); @override StatelessWidget get widget => super.widget as StatelessWidget; @override Widget build() => widget.build(this); // 把自身傳入也就是 BuildContext @override void update(StatelessWidget newWidget) { super.update(newWidget); assert(widget == newWidget); _dirty = true; // rebuild 最終會呼叫到 build() 方法 rebuild(); } } abstract class ComponentElement extends Element { ComponentElement(Widget widget) : super(widget); ... Element? _child; @override void mount(Element? parent, Object? newSlot) { super.mount(parent, newSlot); assert(_child == null); assert(_lifecycleState == _ElementLifecycle.active); _firstBuild(); assert(_child != null); } void _firstBuild() { rebuild(); } @override void performRebuild() { Widget? built; try { built = build(); // MyApp 返回 MaterialApp,透過這個來切換下一個 Widget ... } catch (e, stack) { ... } finally { ... } try { // 一開始傳入 _child 是 null _child = updateChild(_child, built, slot); assert(_child != null); } catch (e, stack) { ... } ... } } abstract class Element extends DiagnosticableTree implements BuildContext { void rebuild() { assert(_lifecycleState != _ElementLifecycle.initial); if (_lifecycleState != _ElementLifecycle.active || !_dirty) return; ... performRebuild(); ... } @protected void performRebuild(); } ``` 從這裡可以看出,**Flutter tree 的==遍歷以深度為優先==,在設計模式看來又是 ==裝飾模式==** > ![](https://i.imgur.com/njzalAY.png) * **Element 的設計模式 (裝飾 & 責任鏈)** 1. **裝飾模式**:ComponentElement 持有 Element,在透過 ComponentElement#build 來切換下一個子 Widget 2. **責任鏈**:都是繼承 Element,並且要實 做 performRebuild 方法 > ![](https://i.imgur.com/abep95W.png) ### 掛載多元素:Column Widget * 當我們使用 Column 組件時 inflateWidget 被調用後,加載的 Element 會是 `MultiChildRenderObjectElement` ```java= // framework abstract class Element extends DiagnosticableTree implements BuildContext { Element inflateWidget(Widget newWidget, Object? newSlot) { assert(newWidget != null); final Key? key = newWidget.key; ... // 若沒有全域的 Key 則呼叫 createElement final Element newChild = newWidget.createElement(); // 重點!!! ... newChild.mount(this, newSlot); // 調用 Child 自己的 Element,並且 將自己作為 parent 傳入 (mount 的第一個參數 this) assert(newChild._lifecycleState == _ElementLifecycle.active); return newChild; } } abstract class MultiChildRenderObjectWidget extends RenderObjectWidget { MultiChildRenderObjectWidget({ Key? key, this.children = const <Widget>[] }) : assert(children != null), super(key: key) { ... } final List<Widget> children; // inflateWidget 就是回傳 MultiChildRenderObjectElement @override MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this); } class MultiChildRenderObjectElement extends RenderObjectElement { @override Element inflateWidget(Widget newWidget, Object? newSlot) { final Element newChild = super.inflateWidget(newWidget, newSlot); assert(_debugCheckHasAssociatedRenderObject(newChild)); return newChild; } @override void mount(Element? parent, Object? newSlot) { super.mount(parent, newSlot); // 找到 _parent,以 MyApp 來說就是 Center ... } } ``` Colume 調用順序如下圖 > ![](https://i.imgur.com/S5dpeYP.png) * 調用 mount 方法時,會先調用 super.mount,就是要找到 `_parent`,以 MyApp 來說就是 Center > ![](https://i.imgur.com/54rx5Ik.png) * 只有 RenderObjectElement 才會有對應的渲染節點,而 **MultiChildRenderObjectElement 會呼叫 inflateWidget 將子類將掛載到 View Tree 上** ```java= // framework abstract class RenderObjectElement extends Element { RenderObject? _renderObject; @override void mount(Element? parent, Object? newSlot) { super.mount(parent, newSlot); ... _renderObject = widget.createRenderObject(this); // 創建渲染對象 ... attachRenderObject(newSlot); _dirty = false; } } class MultiChildRenderObjectElement extends RenderObjectElement { @override Element inflateWidget(Widget newWidget, Object? newSlot) { final Element newChild = super.inflateWidget(newWidget, newSlot); ... 斷言 return newChild; } @override void mount(Element? parent, Object? newSlot) { super.mount(parent, newSlot); final List<Element> children = List<Element>.filled(widget.children.length, _NullElement.instance, growable: false); Element? previousChild; for (int i = 0; i < children.length; i += 1) { // inflateWidget 最終會觸發到 Element#mount (掛載好自己) final Element newChild = inflateWidget(widget.children[i], IndexedSlot<Element?>(i, previousChild)); children[i] = newChild; previousChild = newChild; } _children = children; } } ``` ## Appendix & FAQ :::info ::: ###### tags: `Flutter`