目錄介紹
- 01.Flutter三棵樹背景
- 02.Flutter中的三棵樹
- 03.Flutter三棵樹關系
- 04.運行時三棵樹結構
- 05.三棵樹的作用介紹
Flutter三棵樹背景
1.1 先思考一些問題
- Widget與Element是什么關系?它們是一一對應的還是怎么理解?
- createState 方法在什么時候呼叫?state 里面為啥可以直接獲取到 widget 物件?
- Widget 頻繁更改創建是否會影響性能?復用和更新機制是什么樣的?
- Widget、Element、RenderObject 三棵樹之間的關系是怎樣的?
1.2 Flutter中Dom樹
- 如何理解 DOM 樹這個概念
- 它由頁面中每一個控制元件組成,這些控制元件所形成的一種天然的嵌套關系使其可以表示為 “樹” 結構,可以將這個概念應用在 Flutter 中,
- 例如默認的計數器應用的結構如下圖:
02.Flutter中的三棵樹
- 即Widget樹、Element樹和RenderObject樹,
- Widget樹:控制元件的配置資訊,不涉及渲染,更新代價極低,
- RenderObject樹:真正的UI渲染樹,負責渲染UI,更新代價極大,
- Element樹:Widget樹和RenderObject樹之間的粘合劑,負責將Widget樹的變更以最低的代價映射到RenderObject樹上,
- Widget 樹
- 我們平時用 Widget 使用宣告式的形式寫出來的界面,可以理解為 Widget 樹,這是要介紹的第一棵樹,
- Widget的功能是“描述一個UI元素的配置資料”,它就是說,Widget其實并不是表示最侄訓制在設備螢屏上的顯示元素,而它只是描述顯示元素的一個配置資料,
- RenderObject 樹
- Flutter 引擎需要把我們寫的 Widget 樹的資訊都渲染到界面上,這樣人眼才能看到,跟渲染有關的當然有一顆渲染樹 RenderObject tree,這是第二顆樹,渲染樹節點叫做 RenderObject,這個節點里面處理布局、繪制相關的事情,
- 這兩個樹的節點并不是一一對應的關系,有些 Widget是要顯示的,有些 Widget ,比如那些繼承自 StatelessWidget & StatefulWidget 的 Widget 只是將其他 Widget 做一個組合,這些 Widget 本身并不需要顯示,因此在 RenderObject 樹上并沒有相對應的節點,
- Element 樹
- Widget 樹是非常不穩定的,動不動就執行 build方法,一旦呼叫 build 方法意味著這個 Widget 依賴的所有其他 Widget 都會重新創建,如果 Flutter 直接決議 Widget樹,將其轉化為 RenderObject 樹來直接進行渲染,那么將會是一個非常消耗性能的程序,那對應的肯定有一個東西來消化這些變化中的不便,來做cache,
- 因此,這里就有另外一棵樹 Element 樹,Element 樹這一層將 Widget 樹的變化(類似 React 虛擬 DOM diff)做了抽象,可以只將真正需要修改的部分同步到真實的 RenderObject 樹中,最大程度降低對真實渲染視圖的修改,提高渲染效率,而不是銷毀整個渲染視圖樹重建,
03.Flutter三棵樹關系
3.1 三棵樹架構關系
- 三棵樹架構圖
- 總結的關系
- widget 樹和 Element 樹節點是一一對應關系,每一個 Widget 都會有其對應的 Element,但是 RenderObject 樹則不然,只有需要渲染的 Widget 才會有對應的節點,
- Element 樹相當于一個中間層,大管家,它對 Widget 和 RenderObject 都有參考,
- 當 Widget 不斷變化的時候,將新 Widget 拿到 Element 來進行對比,看一下和之前保留的 Widget 型別和 Key 是否相同,如果都一樣,那完全沒有必要重新創建 Element 和 RenderObject,只需要更新里面的一些屬性即可,這樣可以以最小的開銷更新 RenderObject,引擎在決議 RenderObject 的時候,發現只有屬性修改了,那么也可以以最小的開銷來做渲染,
- 簡單總結一下
- Widget 樹就是配置資訊的樹,我們平時寫代碼寫的就是這棵樹,
- RenderObject 樹是渲染樹,負責計算布局,繪制,Flutter 引擎就是根據這棵樹來進行渲染的,
- Element 樹作為中間者,管理著將 Widget 生成 RenderObject和一些更新操作,
- 舉個通俗例子
- UI 渲染就像蓋一棟大樓,Widget 代表圖紙,表示我們想造怎樣的大樓,RenderObject 是根據圖紙干活的工人,而 Element 是監工,負責協調各方資源,統一調配,外部人員有事需要先找這個監工,
3.2 三者創建關系圖
- 創建關系圖
- 用文字描述三者創建關系
- 首先是 Widget 通過呼叫其 createElement 方法創建出 Element 物件,
- Element 繼續呼叫其持有 Widget 物件(Stateless)或 State 物件(Stateful)的 build 方法創建其子 widget 物件,往復回圈,繼續創建子Element,子 Element 持有父 Element 的參考,因此最侄訓形成出一顆 Element 樹,
- 對于有 layout/paint 的能力控制元件,會創建 RenderObjectElement,在該 Element 的 mount 階段會創建其對應的 RenderObject 物件,
04.運行時三棵樹結構
4.1 三棵樹結構
- 認識了三棵樹之后,那Flutter是如何創建布局的?以及三棵樹之間他們是如何協同的呢?
- 接下來就讓我們通過一個簡單的例子來剖析下它們內在的協同關系:
class Tree extends StatelessWidget { @override Widget build(BuildContext context) { return Container( color: Colors.brown, child: Row( children: [ new Image.network( "https://p1.ssl.qhmsg.com/dr/220__/t01d5ccfbf9d4500c75.jpg", width: 100, height: 100, ), new Text( "從網路加載圖片", style: TextStyle( fontSize: 16 ), ), ], ), ); } } - 當runApp()被呼叫時,第一時間會在后臺發生以下事件:
- Flutter會構建包含Widget(Container,Row,Image,Text)的Widgets樹;
- Flutter遍歷Widget樹,然后根據其中的Widget呼叫createElement()來創建相應的Element物件,最后將這些物件組建成Element樹;
- 接下來會創建第三個樹,這個樹中包含了與Widget對應的Element通過createRenderObject()創建的RenderObject;
- 具體Flutter經過這三個步驟后的狀態
- 總結一下三棵樹結構
- Widget Tree: Widget 是 Flutter 面向開發者的上層介面,我們通過 widget 的層層嵌套,會形成一顆 Widget 樹,一個 Widget 可在多個位置復用,Flutter Framework 層為我們提供了一些常用的包裝或者容器的 Widget,比如 Container,其內部繼續嵌套了其他 Widget,如 Padding、Align 等等,所以,開發者撰寫的 Widget 樹和實際生成的 Widget 樹都會略有差別,如圖中虛線圓形標注的 ColorBox、RawImage 等,
- Element Tree :每一個 Widget 都會對應一個 Element,只不過 Element 分類不同,
- RenderObject Tree:RenderObject 只負責最終的測量、布局和繪制,因此最終的 RenderObject Tree 是 Element Tree 剔除掉哪些包裝,最后組織而成的 Tree,
4.2 為何搞這多樹
- 分層:開發只關注widget
- Framework 將復雜的內部設計、渲染邏輯與開發介面隔離開,應用層只需關注 Widget 開發即可,
- 高效:提交繪制效率
- Tree 最大的共同特點就是快取,因為 Element、RenderObject 銷毀重建成本很高,一旦可以復用 ,那么快取可以大幅減少這種開銷,
- 比如:當 Element 不需要重建時,更新 Widget 的參考就可以了;Layer Tree 的設計是將繪制圖層分開,方便提取和合成,合成層中的 transform 和 opacity 效果,都只是幾何變換、透明度變換等,不會觸發 layout 和 paint,直接由 GPU 完成即可,
05.三棵樹的作用介紹
5.1 簡單了解更新操作
- 簡而言之是為了性能,為了復用Element從而減少頻繁創建和銷毀RenderObject,
- 因為實體化一個RenderObject的成本是很高的,頻繁的實體化和銷毀RenderObject對性能的影響比較大,所以當Widget樹改變的時候,Flutter使用Element樹來比較新的Widget樹和原來的Widget樹:
//framework.dart @protected Element updateChild(Element child, Widget newWidget, dynamic newSlot) { if (newWidget == null) { if (child != null) deactivateChild(child); return null; } Element newChild; if (child != null) { assert(() { final int oldElementClass = Element._debugConcreteSubtype(child); final int newWidgetClass = Widget._debugConcreteSubtype(newWidget); hasSameSuperclass = oldElementClass == newWidgetClass; return true; }()); 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); assert(child.widget == newWidget); assert(() { child.owner._debugElementWasRebuilt(child); return true; }()); newChild = child; } else { deactivateChild(child); assert(child._parent == null); newChild = inflateWidget(newWidget, newSlot); } } else { newChild = inflateWidget(newWidget, newSlot); } assert(() { if (child != null) _debugRemoveGlobalKeyReservation(child); final Key key = newWidget?.key; if (key is GlobalKey) { key._debugReserveFor(this, newChild); } return true; }()); return newChild; } ... static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; } - 如果某一個位置的Widget和新Widget不一致,才需要重新創建Element;
- 如果某一個位置的Widget和新Widget一致時(兩個widget相等或runtimeType與key相等),則只需要修改RenderObject的配置,不用進行耗費性能的RenderObject的實體化作業了;
- 因為Widget是非常輕量級的,實體化耗費的性能很少,所以它是描述APP的狀態(也就是configuration)的最好工具;
- 重量級的RenderObject(創建十分耗費性能)則需要盡可能少的創建,并盡可能的復用;
5.2 更新時三棵樹操作
- 因為Widget是不可變的,當某個Widget的配置改變的時候,整個Widget樹都需要被重建,
- 例如當我們改變一個Text文本的時候,框架就會觸發一個重建整個Widget樹的動作,
- 因為有了Element的存在,Flutter會比較新的Widget樹中的第一個Widget和之前的Widget,
- 接下來比較Widget樹中之后Widget和之前Widget,以此類推,直到Widget樹比較完成,
@override Widget build(BuildContext context) { return Container( color: Colors.brown, height: double.infinity, child: Row( children: [ new Image.network( "https://p1.ssl.qhmsg.com/dr/220__/t01d5ccfbf9d4500c75.jpg", width: 100, height: 100, ), new Text( "改變UI", style: TextStyle( fontSize: 16 ), ), ], ), ); } - Flutter遵循一個最基本的原則:判斷新的Widget和老的Widget是否是同一個型別:
- 如果不是同一個型別,那就把Widget、Element、RenderObject分別從它們的樹(包括它們的子樹)上移除,然后創建新的物件;
- 如果是一個型別,那就僅僅修改RenderObject中的配置,然后繼續向下遍歷,
推薦:https://github.com/yangchong211/YCFlutterUtils
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/382907.html
標籤:其他




