一文讀懂Flutter的三棵樹渲染機制和原理

Flutter是一個優秀的UI框架,藉助它開箱即用的Widgets咱們可以構建出漂亮和高性能的用戶界面。那這些Widgets究竟是如何工做的又是如何完成渲染的。html

在本文中呢,咱們就來探析Widgets背後的故事-Flutter渲染機制之三棵樹。緩存

目錄

  • 什麼是三棵樹?
  • 三棵樹的協同
  • 三棵樹的工做原理

什麼是三棵樹?

在Flutter中和Widgets一塊兒協同工做的還有另外兩個夥伴:Elements和RenderObjects;因爲它們都是有着樹形結構,因此常常會稱它們爲三棵樹。markdown

  • Widget:Widget是Flutter的核心部分,是用戶界面的不可變描述。作Flutter開發接觸最多的就是Widget,能夠說Widget撐起了Flutter的半邊天;
  • Element:Element是實例化的 Widget 對象,經過 Widget 的 createElement() 方法,是在特定位置使用 Widget配置數據生成;
  • RenderObject:用於應用界面的佈局和繪製,保存了元素的大小,佈局等信息;

初次運行時的三棵樹的

初步認識了三棵樹以後,那Flutter是如何建立佈局的?以及三棵樹之間他們是如何協同的呢?接下來就讓咱們經過一個簡單的例子來剖析下它們內在的協同關係:架構

class ThreeTree extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.red,
      child: Container(color: Colors.blue)
    );
  }
}
複製代碼

上面這個例子很簡單,它由三個Widget組成:ThreeTree、Container、Text。那麼當Flutter的runApp()方法被調用時會發生什麼呢?框架

當runApp()被調用時,第一時間會在後臺發生如下事件:less

  • Flutter會構建包含這三個Widget的Widgets樹;
  • Flutter遍歷Widget樹,而後根據其中的Widget調用createElement()來建立相應的Element對象,最後將這些對象組建成Element樹;
  • 接下來會建立第三個樹,這個樹中包含了與Widget對應的Element經過createRenderObject()建立的RenderObject;

下圖是Flutter通過這三個步驟後的狀態:ide

Flutter三棵樹

從圖中能夠看出Flutter建立了三個不一樣的樹,一個對應着Widget,一個對應着Element,一個對應着RenderObject。每個Element中都有着相對應的Widget和RenderObject的引用。能夠說Element是存在於可變Widget樹和不可變RenderObject樹之間的橋樑。Element擅長比較兩個Object,在Flutter裏面就是Widget和RenderObject。它的做用是配置好Widget在樹中的位置,而且保持對於相對應的RenderObject和Widget的引用。工具

三棵樹的做用

簡而言之是爲了性能,爲了複用Element從而減小頻繁建立和銷燬RenderObject。由於實例化一個RenderObject的成本是很高的,頻繁的實例化和銷燬RenderObject對性能的影響比較大,因此當Widget樹改變的時候,Flutter使用Element樹來比較新的Widget樹和原來的Widget樹:oop

//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(建立十分耗費性能)則須要儘量少的建立,並儘量的複用;

看到這裏你是否會以爲整個Flutter APP就像是一個RecycleView呢?佈局

由於在框架中,Element是被抽離開來的,因此你不須要常常和它們打交道。每一個Widget的build(BuildContext context)方法中傳遞的context就是實現了BuildContext接口的Element。

更新時的三棵樹

由於Widget是不可變的,當某個Widget的配置改變的時候,整個Widget樹都須要被重建。例如當咱們改變一個Container的顏色爲橙色的時候,框架就會觸發一個重建整個Widget樹的動做。由於有了Element的存在,Flutter會比較新的Widget樹中的第一個Widget和以前的Widget。接下來比較Widget樹中第二個Widget和以前Widget,以此類推,直到Widget樹比較完成。

class ThreeTree extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.orange,
      child: Container(color: Colors.blue,),
    );
  }
}
複製代碼

Flutter遵循一個最基本的原則:判斷新的Widget和老的Widget是不是同一個類型:

  • 若是不是同一個類型,那就把Widget、Element、RenderObject分別從它們的樹(包括它們的子樹)上移除,而後建立新的對象;
  • 若是是一個類型,那就僅僅修改RenderObject中的配置,而後繼續向下遍歷;

在咱們的例子中,ThreeTree Widget是和原來同樣的類型,它的配置也是和原來的ThreeTreeRender同樣的,因此什麼都不會發生。下一個節點在Widget樹中是Container Widget,它的類型和原來是同樣的,可是它的顏色變化了,因此RenderObject的配置也會發生對應的變化,而後它會從新渲染,其餘的對象都保持不變。

Flutter三棵樹

注意這三個樹,配置發生改變以後,Element和RenderObject實例沒有發生變化。

上面這個過程是很是快的,由於Widget的不變性和輕量級使得他能快速的建立,這個過程當中那些重量級的RenderObject則是保持不變的,直到與其相對應類型的Widget從Widget樹中被移除。

當Widget的類型發生改變時

class ThreeTree extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.orange,
      child: FlatButton(
        onPressed: () {},
        child: Text('三棵樹'),
      ),
    );
  }
}
複製代碼

和剛纔流程同樣,Flutter會重新Widget樹的頂端向下遍歷,與原有樹中的Widget類型進行對比。

Flutter三棵樹

由於FlatButton的類型與Element樹中相對應位置的Element的類型不一樣,Flutter將會從各自的樹上刪除這個Element和相對應的ContainerRender,而後Flutter將會重建與FlatButton相對應的Element和RenderObject。

Flutter三棵樹

當新的RenderObject樹被重建後將會計算佈局,而後繪製在屏幕上面。Flutter內部使用了不少優化方法和緩存策略來處理,因此你不須要手動來處理這些。

以上即是Flutter的總體渲染機制,能夠看出Flutter利用了三棵樹很巧妙的解決的性能的問題。

參考

相關文章
相關標籤/搜索