深入解析 Flutter 三顆樹:Widget、Element 與 RenderObject 完整指南

深入解析 Flutter 三顆樹:Widget、Element 與 RenderObject 完整指南

Overview of Content

本篇文章將帶你深入了解 Flutter 的三顆重要樹結構:WidgetElementRenderObject。透過清晰的章節分解,我們將探討元件的包裹與繼承關係,從 Widget 樹如何連接 Element 實例,再到 RenderObject 的工作原理,逐步揭開這些複雜結構之間的互動。

你將學到 Widget 元件的初始化過程、掛載流程,並理解每一個 Element 在生命週期中的角色與運作方式。如果你正在學習 Flutter,這篇文章將幫助你掌握底層架構,提升開發效率!

關於 Flutter 的渲染機制,主要是透過三棵樹來進行的,分別是 1 最常操控的 Widget2 包在中間銜接渲染的 Element3 真正進行渲染的 RenderObject

graph LR subgraph Flutter 渲染機制 Widget --> Element --> RenderObject end

這三顆樹是 UI 架構的核心概念,它們共同協作來構建、描述和渲染用戶界面

以下使用的 Flutter 版本為 3.22.2


Element 元件關係:包裹、繼承、樹關係

首先我們先從我們開發 Flutter 時最常接觸到的 Widget(也就是元件)開始看它們之間的依賴關係,進發現我們平常所用的 Widget 的「包裹」、「繼承」與「樹」關係

Widget 樹:包裹、樹關係

● 直接創建一個 Flutter 專案,它會有基本的 Demo 示例,從 MyApp 類開始分析 Widget 樹狀圖… (這裡我們省略一步一步分析,直接看最表面的 Widget 關係)


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)的結構,並且其中存在著「多元樹 🌲」的關係

關係如下圖所示

Widget 繼承關係:發現 Element

● 上面有使用到的組件有 ScaffoldAppBarTextCenterColumeIcon... 等等的組件,現在我們來看這些組件的「繼承關係」,會發現它們 最終都會歸到 Widget 這個抽象類中

而 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

A. createElement():此方法是 Widget 最核心的部分,負責將 Widget 與 Element 系統進行對接

這裡我們會發現 createElement() 方法,這是第一次窺見到 Element(三顆樹中其中一個)… 該方法會讓 Widget 與 Element 產生關係

B. == 與 hashCode:這兩個方法用於比較和管理 Widget 的唯一性,特別是在有狀態管理和 Widget 重建過程中

C. canUpdate():這個方法決定了當 Widget 樹發生變化時,是否應該替換現有的 Widget,還是只進行更新

image


認識三顆樹:Widget、Element、RenderObject

在 Flutter 框架中,Widget 樹、Element 樹 和 RenderObject 樹 是 UI 架構的核心概念,它們共同協作來構建、描述和渲染用戶界面

認識 Widget 樹:連接 Element 實例

認識 Widget 樹

Widget 是 Flutter 中最基本的構建單元,描述了應用程序的外觀和結構,並且 每一個 Widget 都是一個靜態的、不可變的配置

Widget 是一個抽象類,並且有一個一定要實現的抽象方法 createElement,該函數會返回一個 Element 類的實例(這就是三顆樹的其中一個)

這樣就可以知道 Widget 的另外一個重點功能是在創建 Element 物件

而我們在寫 Flutter 應用的時候其實是不常碰到 Element,而是透過創建各種 Widget 來建構畫面,而其實 Widget 是讓我們與 Element 做隔離的元件

graph LR 開發者 --> |使用| Widget Widget -.-> |創建| Element

看看 Widget 的繼承關係,可以知道 所有繼承 Widget 的類都需要實現 Element 類,所以 Element 與 Widget 是一個一個對應的,像是…

A. StatefulWidget → StatefulElement

B. StatelessWidget → StatelessElement

C. RenderObjectWidget → RenderObjectElement

不同 Widget 的 Element 實作

● 再來看看 Element 與各種各自實作 Element 的繼承關係,這邊除了有常見的 StatefulElement & StatelessElement,還有看見另外一個 RenderObjectElement 類;接下來會做一些簡單的介紹…

A. StatefulElement 類

● 使用場景: 用於管理與 StatefulWidget 關聯的 Element

● 主要功能:

當 StatefulWidget 重建時,StatefulElement 保持 State 不變這樣即使 Widget 被重建狀態仍然保留(例如:計數器、表單中的輸入數據等)

● StatefulElement 與一個 State 物件相關聯,這個 State 是可變的,並且在 Widget 的生命週期中持續存在

也就是我們平常在更新 Widget 畫面時,會使用的 setState 方法,就是用來刷新 StatefulElement 的 State!

● StatefulElement 通過調用 State 的 build() 方法來構建和更新其子樹

graph TD StatefulWidget --> |關聯| StatefulElement StatefulElement --> State StatefulElement -->|build| Widget State -->|setState| StatefulElement Widget -->|rebuild| StatefulElement

B. StatelessElement 類

● 使用場景: 用於管理與 StatelessWidget 關聯的 Element

● 主要功能:

StatelessElement 不會保留任何狀態,當它的父 Widget 樹更新時,只會調用 StatelessWidget 的 build() 方法來重建 UI

也就是 StatelessElement 不具有 State,所以重新建構時是重新建構 Element

● 由於 StatelessWidget 是不可變的,它們每次更新時都會丟棄舊的元素並使用新元素重新構建(也就是缺乏 State,所以不會保留任何狀態)

StatelessElement 不關心狀態的變化,因為 StatelessWidget 只關注不可變的 UI 呈現

graph TD StatelessWidget --> StatelessElement StatelessElement -->|build| Widget Widget -->|rebuild| StatelessElement

C. RenderObjectElement 類(跟佈局更相關的 Element)

● 使用場景: 用於管理與 RenderObjectWidget 關聯的 Element

● 主要功能:

RenderObjectElement 主要負責創建和管理 RenderObject,這是用於處理佈局和繪製的物件

● 當 Widget 發生變化時,RenderObjectElement 決定如何更新或重建其對應的 RenderObject

● 它與 StatefulElementStatelessElement 不同,RenderObjectElement 的重點是「處理佈局、尺寸和繪製邏輯」,而不是單純地重建子 Widget 樹(請注意這裡說的是邏輯… 至於繪製則是交給 RenderObject 類)

它通常會用於像 ContainerCustomPaint 等需要處理具體佈局和繪製的 Widget

graph TD RenderObjectWidget --> RenderObjectElement RenderObjectElement -->|createRenderObject| RenderObject RenderObject -->|layout 跟 paint| Screen RenderObjectElement -->|update| RenderObject

認識 RenderObject 樹:Widget 與 RenderObject 關係

● 我們知道一個 Widget 通常是依賴著其他 Widget 建構 (初始專案的 MyApp 就是這樣一個 Widget),而現在我們就來找找 Flutter SDK 提供的原生 Widget 最終是如何實現的

TextColumnCenterIconImage Widget… 來做觀察 (注意顏色、線條,個代表了不同意義),可以看到最終元件都會與於 RenderObject 產生關係(大部分都會透過 Element 與 RenderObject 產生關係)

可以把 Widget 看成是一個 設計稿,將稿 (Widget) 完成後就不會再改變,這時就只需要放入對應的元件(RenderObject) 就可以生成不同的 UI 介面

RenderObject 才是真正渲染畫面的物件

那 RenderObject 跟 RenderObjectElement 是同樣含義嗎? 並不是~ 兩者主要負責的責任不同

A. RenderObject 類:負責繪製

RenderObject 是一個專門負責處理 佈局繪製命中測試邊界處理 的物件,「它是渲染層的核心」,負責實際在螢幕上繪製和排列可視的內容

RenderObject 決定 Widget 應該如何呈現在螢幕上,它處理像素繪製、邊界計算以及 Widget 的尺寸與位置

B. RenderObjectElement 類:負責連接

RenderObjectElement 是 Element 的一個具體子類,「它負責將 Widget 和 RenderObject 連接起來」…

graph LR RenderObject <--> |連接| RenderObjectElement <--> |連接| Widget RenderObject -.-> 繪製

進一步認識 RenderObject

並且它管理 Widget 和 RenderObject 之間的關係,並確保 RenderObject 正確地反映 Widget 的狀態

● RenderObject 是一個大家族,像是 RenderViewRenderBox

而其中 RenderBox 有許多的衍生類(像是 RenderWrapRenderStackRednerFlex… 等等),簡單功能介紹如下表

類別名稱簡介
RenderObjectFlutter 渲染系統的基礎類,負責定義佈局、繪製、命中測試等基本功能,其他渲染對象都是從它繼承而來
RenderBoxRenderObject 的子類,定義了 2D 矩形盒模型的佈局規則,許多常見的渲染對象(如 RenderFlex, RenderStack)都繼承自 RenderBox
RenderView渲染樹的根節點,表示應用程式的「視圖」,它負責與 Flutter 引擎交互,並決定渲染區域的大小和邊界
RenderWrap一種專門的 RenderBox,負責在可用空間內將子對象按行或列排列,並根據需要進行換行,適合用於實現自適應佈局
RenderStack另一種 RenderBox,允許子對象重疊繪製,類似於 HTML 中的 z-index,用於堆疊式佈局
RenderFlex定義了一個彈性盒模型,允許將子對象水平或垂直排列,並根據可用空間分配每個子對象的大小,類似於 CSS 中的 flexbox
RenderFlow一個更加靈活和強大的佈局類別,它允許開發者自定義佈局規則,通過覆寫佈局邏輯來控制子對象的排列方式
RenderImage專門用來繪製圖像的渲染對象,負責處理圖像的佈局和繪製邏輯,常見於顯示圖片的 Widget,如 Image

從上表的介紹中,我們可以很清楚的知道 RenderBox 的子類負責各式各樣的佈局,而 RenderView 負責個別元件的渲染

image

● 再來看看 RenderObject 中幾個比較重要的方法(它的方法太多了,這邊我們只挑幾種介紹),從這些方法就可以看出 RenderObject 可布局、繪畫、響應事件

RenderObject 方法說明
layout就像是 Android's onLayout,用來 對元件布局
paint就像是 Android's onDraw,用來 繪製元件
handleEvent就像是 Android's dispatchTouchEvent ,用來處理事件

看看 SDK 提供的 Widget 是否有重寫 layout、paint、handleEvent 方法,並且每個 Widget 的著重重點在哪裡

A. 可見元件 TextWidget:藉由上圖,我們這邊直接看 TextWidget 對應的 RenderBox 的子類 RenderParagraph

我們可以看到 RenderParagraph#paint 方法在繪製時主要是透過 TextPainter 去繪製文字,而繪製是繪製在 Canvas 之上


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);
  }
}

B. 可見元件 ImageWidget:藉由上圖,我們這邊直接看 ImageWidget 對應的 RenderBox 的子類 RenderImage

我們可以看到 RenderImage#paint 方法在繪製時主要是透過 paintImage 方法去繪製圖片,繪製同樣是繪製在 Canvas 之上


// 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);

}

C. 布局元件 Column:這裡 paint 就不是重點,重點在 layout


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 相關的依賴上下文

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 一般)

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 相關的依賴上下文

enum _ElementLifecycle {
  initial,    // 初始化
  active,    // 激活
  inactive,    // 未激活
  defunct,    // 死亡
}

而生命週期的變動,是可以通過幾種方法的調用來達到… 舉例:像是通過調用 Element#mount 方法,就可以激活該元件到 active 狀態

mount 之後會再詳細介紹


// Element

void mount(Element? parent, Object? newSlot) {
    assert(_lifecycleState == _ElementLifecycle.initial);
    ...

    // 將 Widget 改為激活狀態
    _lifecycleState = _ElementLifecycle.active;

    ...
  }

三棵樹的根源

Flutter App 在初始化的過程就會接觸到三棵樹(WidgetElementRenderObject)的根源,知道這些根源我們就可以大概地去瞭解 Flutter App 是如何與 UI 框架的最初根源,好方便我們之後追尋;

而這裡我們要找的就是三棵樹的根源

找 Element 根類:RootElement 的創建

● 這邊我透過在 Element#mount 方法中添加一個 print 方法,來看看 element tree 在掛載時的結構層級


  // framework.dart
  
  void mount(Element? parent, Object? newSlot) {
    ...
    _parent = parent;

    print("_parent: ${parent.runtimeType}");

    ...
  }

如下圖所見

由於這個 Tree 太長,我就不全部列出了,沒有太大的意義,重點我們可以看到 Element 的根物件的類是 RootElement

● 或是你在 Debug 模式下把斷點設置在 MyApp#build 函數中,並在進入斷點時,查看 _parent 成員,一直點到最底層也可以看到跟物件為 RootElement

● 接著,我們要找尋 RootElement 的持有者(是誰創建的),可以透過以下方法

A. 先把斷點下在 RootElement 類的建構函數,在 Debug 模式運行 APP

B. 在進入斷點後,單步執行,就可以發現 RootElement 是由 RootWidget 類創建

找 RanderObject 根類

● 接續上個小節,在 Debug 模式下,我們透過查看 Element 的根結點類(RootElement)的 _renderObject 成員

就可以發現負責渲染畫面的 渲染樹(RanderObject)的根節點就是 _ReusableRenderView,而開啟後會發現 _ReusableRenderView 就是繼承於 RenderView

_ReusableRenderView 被創建的時機則是在 runApp 初始化的過程中,下面小節會再介紹


runApp 初始化、建構

我們來分析 runApp() 這個函數:從這個函數我們往下看,就可以看到三棵樹的初始化過程做了什麼事情,並且清楚的知道 Flutter App 是如何初始化應用並與 UI 框架產生關聯的


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 有以下功能

A. GestureBinding 手勢系統

B. SchedulerBinding 調度系統

C. ServicesBinding 綁定平台消息通道

D. PaintingBinding 繪製

E. WidgetsBinding 連接組件 & 引擎


// 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 方法,而這個方法會被其許多混合繼承類複寫


// foundation/binding.dart

abstract class BindingBase {

  BindingBase() {
    ...
    initInstances();
    ...
  }


  @protected
  @mustCallSuper
  void initInstances() {
    assert(!_debugInitialized);        // 保證該函數只會執行一次
    assert(() {
      _debugInitialized = true;
      return true;
    }());
  }

}

而這些方法執行的順序就較為不同 (簡單來說 由最後一個覆寫的物件開始執行),但是!它內部有是使用到 super 關鍵字,由於 superwith 的配合,最終會導致 initInstances 的順序仍是由 GestureBindingWidgetsBinding

A. 先執行 WidgetsBinding 類的 initInstances 方法


// widgets/binding.dart

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    super.initInstances();    // 呼叫到 RendererBinding
    _instance = this;
    ...
    _buildOwner = BuildOwner();
  }
}

B. 再接續會執行 RendererBinding 類的 initInstances 方法


mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();    // 呼叫到 SemanticsBinding
    ...
  }
}

C. 再接續會執行 SemanticsBinding 類的 initInstances 方法


mixin SemanticsBinding on BindingBase {
  @override
  void initInstances() {
    super.initInstances();   // 呼叫到 PaintingBinding
    ...
  }
}

D. 再接續會執行 PaintingBinding 類的 initInstances 方法


mixin PaintingBinding on BindingBase, ServicesBinding {
  @override
  void initInstances() {
    super.initInstances();   // 呼叫到 SchedulerBinding
    ...
  }
}  

E. 再接續會執行 SchedulerBinding 類的 initInstances 方法


mixin SchedulerBinding on BindingBase {
  @override
  void initInstances() {
    super.initInstances();   // 呼叫到 SemanticsBinding
    ...
  }
}

F. 再接續會執行 SemanticsBinding 類的 initInstances 方法


mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
  @override
  void initInstances() {
    super.initInstances();   // 最終回到 BindingBase 的 initInstances 方法
    // 執行完 BindingBase 的 initInstances 方法後又回到到這裡繼續執行
    ...


    // 執行完往 SchedulerBinding 回推
  }
}

withsuper 執行順序

如同前面的說,是從最後一個覆寫得類開始執行,但在這裡透過 super 不斷執行父類方法(像是 WidgetsBinding 不斷執行 super 後會接續到 GestureBinding),最終全部執行完後又是從 GestureBinding 執行到 WidgetsBinding

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


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


// 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


// rendering/binding.dart

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
    
  late final RenderView renderView = _ReusableRenderView(
    view: platformDispatcher.implicitView!,
  );
    
}

但這個做法可能在新版本被遺棄,改由 renderViews 管理(因為 RendererBinding 或許需要管理多個 RenderView)

根 Widget 調用 mount:掛載 RootWidget

● 我們前面說在初始化時會創建 RootWidget 物件(根 Widget),接著我們來看 Widget 何時被掛載上去的,我們從 attachToBuildOwner 函數開始…


// 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();
    }
  }

A. 在 attachToBuildOwner 函數中會調用 RootWidget#attach 方法

B. 而在 RootWidget#attach 方法中又會發現它調用了 RootElement#mount 方法,剛方法會把 RootElement 掛載到 Flutter Widget 樹中進行初始化


// 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

並且分析至此,我們可以稍為整理一下根元素所創建的樹個別是哪些,如下表所示

真正物件創建、連接時機
WidgetRootWidget創建時機:runApp 調用 attachRootWidget 方法
ElementRootElement創建時機:RootWidget 類中對應的 createElement 方法
RenderObject_ReusableRenderView(就是 RenderView)初始化 RendererBinding 時的 renderView 成員

連接的入口:attach 函數

● 在 attach 函數中,我們可以看到它會呼叫 createElement 方法,該方法會就會創建 Element 物件(根物件會創建 RootElement


  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!;
  }

並且接下來我們會分析幾個重點

A. RootElement#mount 方法

B. Element#mount 方法

Element 的 mount 方法:初始化生命週期

● 我們來看看最原始的 Element#mount 方法,所有繼承於 Element 類的元素都需要呼叫這個方法… 從以下程式中,可以發現 mount 主要就是決定 Widget parent、深度、擁有者… 等等資訊


// 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() 方法


// 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
  }

  ...
}

A. _rebuild() 方法中主要做的事情就是呼叫 Element#updateChild(...) 方法,該方法主要是返回子 Widget 元素


// widgets/binding.dart

  Element? _child;

  void _rebuild() {
    try {
      _child = updateChild(_child, (widget as RootWidget).child, /* slot */ null);
    } catch (exception, stack) {
      ... 省略部分
    }

  }

B. updateChild 函數:這裡我們當成是初次加載,所以在初次呼叫時 _child 成員為 null,透過一些省略… 在帶入 updateChild 函數後我們可以簡化為以下程式來看

我們可以看到它主要是再呼叫了 inflateWidget 方法


// 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;
  }

C. inflateWidget 函數:該函數會先判斷 Key 是否是 GlobalKey,如果是 GlobalKey 則呼叫 updateChild 更新… 否則就呼叫 createElement 加載子類的 Element


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();
      }
    }

}

這裡可以很明顯地看出一個遞迴加載 Widget 的過程


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 總共有四種狀況,如下表所示

child 狀態newWidget == nullnewWidget != null
child == null^1^ 返回 null^3^ 用 inflateWidget 返回新元素
child != null^2^ 移除原來的 Child,返回 null^4^ 返回 Child or inflateWidget

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 父類去找


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);

}

StatelessElement 對應 StatelessWidget

● 當 inflateWidget 調用 createElement 後會創建 StatelessElement 物件

其反應的順序簡單來說就是 ComponentElement#mount -> _firstBuild -> rebuild -> performRebuild


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 的遍歷以深度為優先,在設計模式看來又是 裝飾模式

Element 的設計模式 (裝飾 & 責任鏈)

A. 裝飾模式:ComponentElement 持有 Element,在透過 ComponentElement#build 來切換下一個子 Widget

B. 責任鏈:都是繼承 Element,並且要實 做 performRebuild 方法


更多的 Flutter/Dart 語言相關文章

了解 Flutter 如何在跨平台開發中佔據重要地位,掌握快速上手的技巧與項目建置流程,開啟你的跨平台開發之旅!

探索跨平台與 Flutter 技術的未來:從認識到 Flutter 專案建置 | 3 種跨平台

Dart 語言基礎

探討 Dart 語言:宣告、數據類型、操作符 | 從基礎到應用指南

快速掌握 Dart 語言的核心概念,包括變數宣告、數據類型及操作符,為 Flutter 開發奠定扎實基礎。

Dart 函數與方法、異常處理、引用庫 | Java 比較

深入了解 Dart 的函數與異常處理特性,並與 Java 的處理方式進行比較,幫助你跨語言切換更加順暢。

深入解析 Dart 語言:命名慣例、類特性、建構函數與抽象特性

學習 Dart 類的設計邏輯及命名慣例,深入探索抽象類與 Mixin 的強大應用場景。

深入探索 Dart 的併發與異步處理:從 Isolate 到 Event Loop 的全面指南 | Future、Stream

徹底搞懂 Dart 的併發與異步處理,掌握 Isolate 與 Event Loop 的運行機制,助你提升應用效能!

深入 Flutter 框架

深入解析 Flutter Navigator:常見錯誤、解決方法與路由跳轉技巧、動畫

從常見問題到自定義解決方案,學會如何利用 Navigator 實現路由跳轉與流暢動畫效果。

深入理解 Flutter 中的數據共享:從普遍方案到 InheritedWidget | 3 種方案

探討數據共享的最佳實踐,了解 InheritedWidget 等三種主要方案,幫助你優化應用結構。

深入解析 Flutter 三顆樹:Widget、Element 與 RenderObject 完整指南

拆解 Flutter 的內部結構,全面了解 Widget、Element 和 RenderObject 之間的關係,提升你的 Flutter 開發技能!

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響