Flutter框架分析(三)-- Widget,Element和RenderObject

Flutter框架分析分析系列文章:markdown

《Flutter框架分析(一)-- 總覽和Window》架構

《Flutter框架分析(二)-- 初始化》app

《Flutter框架分析(三)-- Widget,Element和RenderObject》框架

《Flutter框架分析(四)-- Flutter框架的運行》less

《Flutter框架分析(五)-- 動畫》ide

《Flutter框架分析(六)-- 佈局》函數

《Flutter框架分析(七)-- 繪製》佈局

前言

前面兩篇Flutter框架分析的文章介紹了渲染流水線,window和框架的初始化。這篇文章繼續來理一下對Flutter app開發者來講比較重要的WidgetElementRenderObject體系。Flutter的理念是一切都是Widget(Everythin is Widget)。開發者在開發Flutter app的時候主要都是在寫不少Widget。那麼這三者之間是什麼關係?它們是怎麼工做的呢?讓咱們來一探究竟。post

概覽

這塊的內容比較多且有些複雜,爲了避免讓你們迷失在源碼的海洋裏,咱們仍是舉個例子先簡單瞭解一下這個體系。字體

void main() {
  runApp(MyWidget());
}

class MyWidget extends StatelessWidget {
  final String _message = "Flutter框架分析";
  @override
  Widget build(BuildContext context) => ErrorWidget(_message);
}
複製代碼

這個例子的利用Flutter自帶的ErrorWidget顯示咱們自定義的一句話:「Flutter框架分析」。沒錯,這個ErrorWidget就是當你的代碼出bug的時候顯示在屏幕上的可怕的紅底黃字信息。放張截屏你們感覺一下。

這裏使用它是由於它是最簡單,層級最少的一個Widget。以方便咱們理解Flutter框架,避免被MaterialApp那深不可測的element tree和render tree勸退。

運行上述例子之後再打開Flutter Inspector看一下:

element tree
從上圖可見就三個層級 root-> MyWidget-> ErrorWidget。這看起來是個widget tree。這裏的root對應的是上篇文章裏說的 RenderObjectToWidgetAdapter。但這其實是這樣的一個element tree: RenderObjectToWidgetElement-> StatelessElement-> LeafRenderObjectElement。還記得咱們上篇文章裏說的, RenderObjectToWidgetElement是element tree的根節點。看看圖中上方紅框,這個根節點是持有render tree的根節點 RenderView的。它的子節點就是咱們本身寫的 MyWidget對應的 StatelessElement。而這個element是不持有 RenderObject的。只有最下面的 ErrorWidget對應的 LeafRenderObjectElement才持有第二個 RenderObject。因此 render tree是隻有兩層的: RenderView-> RenderErrorBox。以上所說用圖來表示就是這樣的:

widget element renderobject
圖中綠色鏈接線表示的是element tree的層級關係。黃色的鏈接線表示render tree的層級關係。

從上面這個例子能夠看出來,Widget是用來描述對應的Element的描述或配置。Element組成了element tree,Element的主要功能就是維護這棵樹,節點的增長,刪除,更新,樹的遍歷都在這裏完成。Element都是從Widget中生成的。每一個Widget都會對應一個Element。可是並不是每一個Widget/Element會對應一個RenderObject。只有這個Widget繼承自RenderObjectWidget的時候纔會有對應的RenderObject

總的來講就是如下幾點:

  • Widget是對Element的配置或描述。Flutter app開發者主要的工做都是在和Widget打交道。咱們不須要關心樹的維護更新,只須要專一於對Widget狀態的維護就能夠了,大大減輕了開發者的負擔。
  • Element負責維護element tree。Element不會去管具體的顏色,字體大小,顯示內容等等這些UI的配置或描述,也不會去管佈局,繪製這些事,它只管本身的那棵樹。Element的主要工做都處於渲染流水線的構建(build)階段。
  • RenderObject負責具體佈局,繪製這些事情。也就是渲染流水線的佈局(layout)和 繪製(paint)階段。

接下來咱們就結合源碼,來分析一下WidgetElementRenderObject

Widget

基類Widget很簡單

@immutable
abstract class Widget extends DiagnosticableTree {

  const Widget({ this.key });
  ...
  @protected
  Element createElement();
  ...
}
複製代碼

方法createElement()負責實例化對應的Element。由其子類實現。接下來看下幾個比較重要的子類:

StatelessWidget

abstract class StatelessWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatelessWidget({ Key key }) : super(key: key);
  
  @override
  StatelessElement createElement() => StatelessElement(this);

  @protected
  Widget build(BuildContext context);
}
複製代碼

StatelessWidget對Flutter開發者來說再熟悉不過了。它的createElement方法返回的是一個StatelessElement實例。

StatelessWidget沒有生成RenderObject的方法。因此StatelessWidget只是箇中間層,它須要實現build方法來返回子Widget

StatefulWidget

abstract class StatefulWidget extends Widget {
  @override
  StatefulElement createElement() => StatefulElement(this);

  @protected
  State createState();
}
複製代碼

StatefulWidget對Flutter開發者來說很是熟悉了。createElement方法返回的是一個StatefulElement實例。方法createState()構建對應於這個StatefulWidgetState

StatefulWidget沒有生成RenderObject的方法。因此StatefulWidget也只是箇中間層,它須要對應的State實現build方法來返回子Widget

State

說到StatefulWidget就不能不說說State

abstract class State<T extends StatefulWidget> extends Diagnosticable {
  T get widget => _widget;
  T _widget;
  
  BuildContext get context => _element;
  StatefulElement _element;

  bool get mounted => _element != null;

  void initState() { }

  void didUpdateWidget(covariant T oldWidget) { }

  void setState(VoidCallback fn) {
    final dynamic result = fn() as dynamic;
    _element.markNeedsBuild();
  }

  void deactivate() { }
  
  void dispose() { }

  Widget build(BuildContext context);

  void didChangeDependencies() { }
}
複製代碼

從源碼可見,State持有對應的WidgetElement。注意這一句BuildContext get context => _element;。咱們在調用build時候的入參BuildContex其實返回的就是Element

mounted,用來判斷這個State是否是關聯到element tree中的某個Element。若是當前State不是在mounted == true的狀態,你去調用setState()是會crash的。

函數initState()用來初始化State

函數didUpdateWidget(covariant T oldWidget)在這個State被換了個新的Widget之後被調用到。是的,State對應的Widget實例只要是相同類型的是能夠被換來換去的。

函數setState()咱們很熟悉了。這個函數只是簡單執行傳入的回調而後調用_element.markNeedsBuild()。你看,若是此時_element爲空的時候會不會出問題?因此建議你們在調用setState()以前用mounted判斷一下。另外要注意的一點是,這個函數也是觸發渲染流水線的一個點。後續我會在另外的文章裏從這個點出發,給你們說說渲染流水線如何在WidgetElementRenderObject架構下運行。

函數deactivate()State對應的Element被從樹中移除後調用,這個移除多是暫時移除。

函數dispose()State對應的Element被從樹中移除後調用,這個移除是永久移除。

函數build(BuildContext context),你們很熟悉了,很少說了。

函數didChangeDependencies()State的依賴發生變化的時候被調用,具體什麼樣的依賴後文再說。

StatefullWidgetState對Flutter app開發者來講可能會是打交道最多的。有些細節還須要結合Element作深刻的理解。

InheritedWidget

InheritedWidget既不是StatefullWidget也不是StatelessWidget。它是用來向下傳遞數據的。在InheritedWidget之下的子節點均可以經過調用BuildContext.inheritFromWidgetOfExactType()來獲取這個InheritedWidget。它的createElement()函數返回的是一個InheritedElement

abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => InheritedElement(this);

  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
複製代碼

RenderObjectWidget

RenderObjectWidget用來配置RenderObject。其createElement()函數返回RenderObjectElement。由其子類實現。相對於上面說的其餘Widget。這裏多了一個createRenderObject()方法。用來實例化RenderObject

abstract class RenderObjectWidget extends Widget {

  const RenderObjectWidget({ Key key }) : super(key: key);

  @override
  RenderObjectElement createElement();

  @protected
  RenderObject createRenderObject(BuildContext context);

  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }

  @protected
  void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
複製代碼

RenderObjectWidget只是個配置,當配置發生變化須要應用到現有的RenderObject上的時候,Flutter框架會調用updateRenderObject()來把新的配置設置給相應的RenderObject

RenderObjectWidget有三個比較重要的子類:

  • LeafRenderObjectWidget這個Widget配置的節點處於樹的最底層,它是沒有孩子的。對應LeafRenderObjectElement
  • SingleChildRenderObjectWidget,只含有一個孩子。對應SingleChildRenderObjectElement
  • MultiChildRenderObjectWidget,有多個孩子。對應MultiChildRenderObjectElement

Element

Element構成了element tree。這個類主要在作的事情就是維護這棵樹。 從上面對Widget的分析咱們能夠看出,好像每一個特別的Widget都會有一個對應的Element。特別是對於RenderObjectWidget。若是我有一個XXXRenderObjectWidget,它的createElement()一般會返回一個XXXRenderObjectElement。爲簡單起見。咱們的分析就僅限於比較基礎的一些Element。 首先來看一下基類Element

abstract class Element extends DiagnosticableTree implements BuildContext {
    Element _parent;
    Widget _widget;
    BuildOwner _owner;
    dynamic _slot;
    
    void visitChildren(ElementVisitor visitor) { }
    
    Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
        
    }
    
    void mount(Element parent, dynamic newSlot) {
        
    }
    
    void unmount() {
         
    }
    
    void update(covariant Widget newWidget) {
        
    }
    
    @protected
    Element inflateWidget(Widget newWidget, dynamic newSlot) {
    ...
      final Element newChild = newWidget.createElement();
      newChild.mount(this, newSlot);
      return newChild;
    }
  
    void markNeedsBuild() {
      if (dirty)
        return;
      _dirty = true;
      owner.scheduleBuildFor(this);
    }
    
    void rebuild() {
      if (!_active || !_dirty)
        return;
      performRebuild();
    }
  
    @protected
    void performRebuild();
}
複製代碼

Element持有當前的Widget,一個BuildOwner。這個BuildOwner是以前在WidgetsBinding裏實例化的。Element是樹結構,它會持有父節點_parent_slot由父Element設置,目的是告訴當前Element在父節點的什麼位置。因爲Element基類不知道子類會如何管理孩子節點。因此函數visitChildren()由子類實現以遍歷孩子節點。

函數updateChild()比較重要,用來更新一個孩子節點。更新有四種狀況:

  • Widget爲空,老Widget也爲空。則啥也不作。
  • Widget爲空,老Widget不爲空。這個Element被移除。
  • Widget不爲空,老Widget爲空。則調用inflateWidget()以這個Wiget爲配置實例化一個Element
  • Widget不爲空,老Widget不爲空。調用update()函數更新子Elementupdate()函數由子類實現。

Element被實例化之後會調用mount()來把本身加入element tree。要移除的時候會調用unmount()

函數markNeedsBuild()用來標記Element爲「髒」(dirty)狀態。代表渲染下一幀的時候這個Element須要被重建。

函數rebuild()在渲染流水線的構建(build)階段被調用。具體的重建在函數performRebuild()中,由Element子類實現。

Widget有一些比較重要的子類,對應的Element也有一些比較重要的子類。

ComponentElement

ComponentElement表示當前這個Element是用來組合其餘Element的。

abstract class ComponentElement extends Element {
  ComponentElement(Widget widget) : super(widget);

  Element _child;

  @override
  void performRebuild() {
    Widget built;
    built = build();
    _child = updateChild(_child, built, slot);
  }

  Widget build();
}
複製代碼

ComponentElement繼承自Element。是個抽象類。_child是其孩子。在函數performRebuild()中會調用build()來實例化一個Widgetbuild()函數由其子類實現。

StatelessElement

StatelessElement對應的Widget是咱們熟悉的StatelessWidget

class StatelessElement extends ComponentElement {

  @override
  Widget build() => widget.build(this);

  @override
  void update(StatelessWidget newWidget) {
    super.update(newWidget);
    _dirty = true;
    rebuild();
  }
}
複製代碼

build()函數直接調用的就是StatelessWidget.build()。如今你知道你寫在StatelessWidget裏的build()是在哪裏被調用的了吧。並且你看,build()函數的入參是this。咱們都知道這個函數的入參應該是BuildContext類型的。這個入參其實就是這個StatelessElement

StatefulElement

StatefulElement對應的Widget是咱們熟悉的StatefulWidget

class StatefulElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    _state._element = this;
    _state._widget = widget;
  }

  @override
  Widget build() => state.build(this);
  
   @override
  void _firstBuild() {
    final dynamic debugCheckForReturnedFuture = _state.initState() 
    _state.didChangeDependencies();
    super._firstBuild();
  }

  @override
  void deactivate() {
    _state.deactivate();
    super.deactivate();
  }

  @override
  void unmount() {
    super.unmount();
    _state.dispose();
    _state._element = null;
    _state = null;
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _state.didChangeDependencies();
  }
}
複製代碼

StatefulElement構造的時候會調用對應StatefulWidgetcreateState()函數。也就是說State是在實例化StatefulElement的時候被實例化的。而且State實例會被這個StatefulElement實例持有。從這裏也能夠看出爲何StatefulWidget的狀態要由單獨的State管理,每次刷新的時候可能會有一個新的StatefulWidget被建立,可是State實例是不變的。

build()函數調用的是咱們熟悉的State.build(this),如今你也知道了Statebuild()函數是在哪裏被調用的了吧。並且你看,build()函數的入參是this。咱們都知道這個函數的入參應該是BuildContext類型的。這個入參其實就是這個StatefulElement

咱們都知道State有狀態,當狀態改變時對應的回調函數會被調用。這些回調函數其實都是在StatefulElement裏被調用的。

在函數_firstBuild()裏會調用State.initState()State.didChangeDependencies()

在函數deactivate()裏會調用State.deactivate()

在函數unmount()裏會調用State.dispose()

在函數didChangeDependencies()裏會調用State.didChangeDependencies()

InheritedElement

InheritedElement對應的WidgetInheritedWidget。其內部實現主要是在維護對其有依賴的子ElementMap,以及在須要的時候調用子Element對應的didChangeDependencies()回調,這裏就不貼代碼了,你們感興趣的話能夠本身去看一下源碼。

RenderObjectElement

RenderObjectElement對應的WidgetRenderObjectWidget

abstract class RenderObjectElement extends Element {
  RenderObject _renderObject;
  
  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this);
    attachRenderObject(newSlot);
    _dirty = false;
  }
  
  @override
  void unmount() {
    super.unmount();
    widget.didUnmountRenderObject(renderObject);
  }
  
  @override
  void update(covariant RenderObjectWidget newWidget) {
    super.update(newWidget);
    widget.updateRenderObject(this, renderObject);
    _dirty = false;
  }
  
  @override
  void performRebuild() {
    widget.updateRenderObject(this, renderObject);
    _dirty = false;
  }
  
  @protected
  void insertChildRenderObject(covariant RenderObject child, covariant dynamic slot);

  @protected
  void moveChildRenderObject(covariant RenderObject child, covariant dynamic slot);

  @protected
  void removeChildRenderObject(covariant RenderObject child);

}
複製代碼

函數mount()被調用的時候會調用RenderObjectWidget.createRenderObject()來實例化RenderObject

函數update()performRebuild()被調用的時候會調用RenderObjectWidget.updateRenderObject()

函數unmount()被調用的時候會調用RenderObjectWidget.didUnmountRenderObject()

RenderObject

RenderObject負責渲染流水線佈局(layout)階段和繪製(paint)階段的工做。同時也維護render tree。對render tree的維護方法是來自基類AbstractNode。這裏咱們主要關注和渲染流水線相關的一些方法。

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {

  void markNeedsLayout() {
      ...
  }
  
  void markNeedsPaint() {
      ...
  }
  
  void layout(Constraints constraints, { bool parentUsesSize = false }) {
    ...  
    if (sizedByParent) {
        performResize();
    }
    ...
    performLayout();
    ...
  }
  
  void performResize();
  
  void performLayout();
  
  void paint(PaintingContext context, Offset offset) { }
}
複製代碼

markNeedsLayout()標記這個RenderObject須要從新作佈局。markNeedsPaint標記這個RenderObject須要重繪。這兩個函數只作標記。標記以後Flutter框架會調度一幀,在下一個Vsync信號到來以後才真正作佈局和繪製。

真正的佈局在函數layout()中進行。這個函數會作一次判斷,若是sizedByParenttrue。則會調用performResize()。代表這個RenderObject的尺寸僅由其父節點決定。而後會調用performLayout()作佈局。performResize()performLayout()都須要RenderObject的子類去實現。`

總結

WidgetElementRenderObject體系是Flutter框架的核心。其中Element須要好好理解。Flutter的渲染流水線中的構建(build)階段主要就是在維護更新element tree裏面的Element節點。只有理解了Element和element tree,纔是真正掌握了Flutter框架。這篇文章裏只是一些靜態的說明。下篇文章我會嘗試從渲染流水線動態運行的角度分析一下Flutter框架是怎麼運行的。

相關文章
相關標籤/搜索