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

前言

前面兩篇Flutter框架分析的文章介紹了渲染流水線,window和框架的初始化。這篇文章繼續(xù)來理一下對Flutter app開發(fā)者來說比較重要的Widget,ElementRenderObject體系。Flutter的理念是一切都是Widget(Everythin is Widget)。開發(fā)者在開發(fā)Flutter app的時(shí)候主要都是在寫很多Widget。那么這三者之間是什么關(guān)系?它們是怎么工作的呢?讓我們來一探究竟。

概覽

這塊的內(nèi)容比較多且有些復(fù)雜,為了不讓大家迷失在源碼的海洋里,我們還是舉個(gè)例子先簡單了解一下這個(gè)體系。

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

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

這個(gè)例子的利用Flutter自帶的ErrorWidget顯示我們自定義的一句話:“Flutter框架分析”。沒錯(cuò),這個(gè)ErrorWidget就是當(dāng)你的代碼出bug的時(shí)候顯示在屏幕上的可怕的紅底黃字信息。放張截屏大家感受一下。

image

這里使用它是因?yàn)樗亲詈唵?,層級最少的一個(gè)Widget。以方便我們理解Flutter框架,避免被MaterialApp那深不可測的element tree和render tree勸退。

運(yùn)行上述例子以后再打開Flutter Inspector看一下:

element tree

從上圖可見就三個(gè)層級 root->MyWidget->ErrorWidget。這看起來是個(gè)widget tree。這里的root對應(yīng)的是上篇文章里說的RenderObjectToWidgetAdapter。但這實(shí)際上是這樣的一個(gè)element tree:RenderObjectToWidgetElement->StatelessElement->LeafRenderObjectElement。還記得我們上篇文章里說的,RenderObjectToWidgetElement是element tree的根節(jié)點(diǎn)??纯磮D中上方紅框,這個(gè)根節(jié)點(diǎn)是持有render tree的根節(jié)點(diǎn)RenderView的。它的子節(jié)點(diǎn)就是我們自己寫的MyWidget對應(yīng)的StatelessElement。而這個(gè)element是不持有RenderObject的。只有最下面的ErrorWidget對應(yīng)的LeafRenderObjectElement才持有第二個(gè)RenderObject。所以 render tree是只有兩層的: RenderView->RenderErrorBox。以上所說用圖來表示就是這樣的:

widget element renderobject

圖中綠色連接線表示的是element tree的層級關(guān)系。黃色的連接線表示render tree的層級關(guān)系。

從上面這個(gè)例子可以看出來,Widget是用來描述對應(yīng)的Element的描述或配置。Element組成了element tree,Element的主要功能就是維護(hù)這棵樹,節(jié)點(diǎn)的增加,刪除,更新,樹的遍歷都在這里完成。Element都是從Widget中生成的。每個(gè)Widget都會(huì)對應(yīng)一個(gè)Element。但是并非每個(gè)Widget/Element會(huì)對應(yīng)一個(gè)RenderObject。只有這個(gè)Widget繼承自RenderObjectWidget的時(shí)候才會(huì)有對應(yīng)的RenderObject

總的來說就是以下幾點(diǎn):

  • Widget是對Element的配置或描述。Flutter app開發(fā)者主要的工作都是在和Widget打交道。我們不需要關(guān)心樹的維護(hù)更新,只需要專注于對Widget狀態(tài)的維護(hù)就可以了,大大減輕了開發(fā)者的負(fù)擔(dān)。
  • Element負(fù)責(zé)維護(hù)element tree。Element不會(huì)去管具體的顏色,字體大小,顯示內(nèi)容等等這些UI的配置或描述,也不會(huì)去管布局,繪制這些事,它只管自己的那棵樹。Element的主要工作都處于渲染流水線的構(gòu)建(build)階段。
  • RenderObject負(fù)責(zé)具體布局,繪制這些事情。也就是渲染流水線的布局(layout)和 繪制(paint)階段。

接下來我們就結(jié)合源碼,來分析一下Widget,ElementRenderObject。

Widget

基類Widget很簡單

@immutable
abstract class Widget extends DiagnosticableTree {

  const Widget({ this.key });
  ...
  @protected
  Element createElement();
  ...
}

方法createElement()負(fù)責(zé)實(shí)例化對應(yīng)的Element。由其子類實(shí)現(xiàn)。接下來看下幾個(gè)比較重要的子類:

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開發(fā)者來講再熟悉不過了。它的createElement方法返回的是一個(gè)StatelessElement實(shí)例。

StatelessWidget沒有生成RenderObject的方法。所以StatelessWidget只是個(gè)中間層,它需要實(shí)現(xiàn)build方法來返回子Widget。

StatefulWidget

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

  @protected
  State createState();
}

StatefulWidget對Flutter開發(fā)者來講非常熟悉了。createElement方法返回的是一個(gè)StatefulElement實(shí)例。方法createState()構(gòu)建對應(yīng)于這個(gè)StatefulWidgetState。

StatefulWidget沒有生成RenderObject的方法。所以StatefulWidget也只是個(gè)中間層,它需要對應(yīng)的State實(shí)現(xiàn)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持有對應(yīng)的WidgetElement。注意這一句BuildContext get context => _element;。我們在調(diào)用build時(shí)候的入?yún)?code>BuildContex其實(shí)返回的就是Element。

mounted,用來判斷這個(gè)State是不是關(guān)聯(lián)到element tree中的某個(gè)Element。如果當(dāng)前State不是在mounted == true的狀態(tài),你去調(diào)用setState()是會(huì)crash的。

函數(shù)initState()用來初始化State。

函數(shù)didUpdateWidget(covariant T oldWidget)在這個(gè)State被換了個(gè)新的Widget以后被調(diào)用到。是的,State對應(yīng)的Widget實(shí)例只要是相同類型的是可以被換來換去的。

函數(shù)setState()我們很熟悉了。這個(gè)函數(shù)只是簡單執(zhí)行傳入的回調(diào)然后調(diào)用_element.markNeedsBuild()。你看,如果此時(shí)_element為空的時(shí)候會(huì)不會(huì)出問題?所以建議大家在調(diào)用setState()之前用mounted判斷一下。另外要注意的一點(diǎn)是,這個(gè)函數(shù)也是觸發(fā)渲染流水線的一個(gè)點(diǎn)。后續(xù)我會(huì)在另外的文章里從這個(gè)點(diǎn)出發(fā),給大家說說渲染流水線如何在Widget、ElementRenderObject架構(gòu)下運(yùn)行。

函數(shù)deactivate()State對應(yīng)的Element被從樹中移除后調(diào)用,這個(gè)移除可能是暫時(shí)移除。

函數(shù)dispose()State對應(yīng)的Element被從樹中移除后調(diào)用,這個(gè)移除是永久移除。

函數(shù)build(BuildContext context),大家很熟悉了,不多說了。

函數(shù)didChangeDependencies()State的依賴發(fā)生變化的時(shí)候被調(diào)用,具體什么樣的依賴后文再說。

StatefullWidgetState對Flutter app開發(fā)者來說可能會(huì)是打交道最多的。有些細(xì)節(jié)還需要結(jié)合Element做深入的理解。

InheritedWidget

InheritedWidget既不是StatefullWidget也不是StatelessWidget。它是用來向下傳遞數(shù)據(jù)的。在InheritedWidget之下的子節(jié)點(diǎn)都可以通過調(diào)用BuildContext.inheritFromWidgetOfExactType()來獲取這個(gè)InheritedWidget。它的createElement()函數(shù)返回的是一個(gè)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()函數(shù)返回RenderObjectElement。由其子類實(shí)現(xiàn)。相對于上面說的其他Widget。這里多了一個(gè)createRenderObject()方法。用來實(shí)例化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只是個(gè)配置,當(dāng)配置發(fā)生變化需要應(yīng)用到現(xiàn)有的RenderObject上的時(shí)候,F(xiàn)lutter框架會(huì)調(diào)用updateRenderObject()來把新的配置設(shè)置給相應(yīng)的RenderObject。

RenderObjectWidget有三個(gè)比較重要的子類:

  • LeafRenderObjectWidget這個(gè)Widget配置的節(jié)點(diǎn)處于樹的最底層,它是沒有孩子的。對應(yīng)LeafRenderObjectElement。
  • SingleChildRenderObjectWidget,只含有一個(gè)孩子。對應(yīng)SingleChildRenderObjectElement。
  • MultiChildRenderObjectWidget,有多個(gè)孩子。對應(yīng)MultiChildRenderObjectElement。

Element

Element構(gòu)成了element tree。這個(gè)類主要在做的事情就是維護(hù)這棵樹。
從上面對Widget的分析我們可以看出,好像每個(gè)特別的Widget都會(huì)有一個(gè)對應(yīng)的Element。特別是對于RenderObjectWidget。如果我有一個(gè)XXXRenderObjectWidget,它的createElement()通常會(huì)返回一個(gè)XXXRenderObjectElement。為簡單起見。我們的分析就僅限于比較基礎(chǔ)的一些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持有當(dāng)前的Widget,一個(gè)BuildOwner。這個(gè)BuildOwner是之前在WidgetsBinding里實(shí)例化的。Element是樹結(jié)構(gòu),它會(huì)持有父節(jié)點(diǎn)_parent。_slot由父Element設(shè)置,目的是告訴當(dāng)前Element在父節(jié)點(diǎn)的什么位置。由于Element基類不知道子類會(huì)如何管理孩子節(jié)點(diǎn)。所以函數(shù)visitChildren()由子類實(shí)現(xiàn)以遍歷孩子節(jié)點(diǎn)。

函數(shù)updateChild()比較重要,用來更新一個(gè)孩子節(jié)點(diǎn)。更新有四種情況:

  • Widget為空,老Widget也為空。則啥也不做。
  • Widget為空,老Widget不為空。這個(gè)Element被移除。
  • Widget不為空,老Widget為空。則調(diào)用inflateWidget()以這個(gè)Wiget為配置實(shí)例化一個(gè)Element。
  • Widget不為空,老Widget不為空。調(diào)用update()函數(shù)更新子Element。update()函數(shù)由子類實(shí)現(xiàn)。

Element被實(shí)例化以后會(huì)調(diào)用mount()來把自己加入element tree。要移除的時(shí)候會(huì)調(diào)用unmount()。

函數(shù)markNeedsBuild()用來標(biāo)記Element為“臟”(dirty)狀態(tài)。表明渲染下一幀的時(shí)候這個(gè)Element需要被重建。

函數(shù)rebuild()在渲染流水線的構(gòu)建(build)階段被調(diào)用。具體的重建在函數(shù)performRebuild()中,由Element子類實(shí)現(xiàn)。

Widget有一些比較重要的子類,對應(yīng)的Element也有一些比較重要的子類。

ComponentElement

ComponentElement表示當(dāng)前這個(gè)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。是個(gè)抽象類。_child是其孩子。在函數(shù)performRebuild()中會(huì)調(diào)用build()來實(shí)例化一個(gè)Widgetbuild()函數(shù)由其子類實(shí)現(xiàn)。

StatelessElement

StatelessElement對應(yīng)的Widget是我們熟悉的StatelessWidget

class StatelessElement extends ComponentElement {

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

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

build()函數(shù)直接調(diào)用的就是StatelessWidget.build()。現(xiàn)在你知道你寫在StatelessWidget里的build()是在哪里被調(diào)用的了吧。而且你看,build()函數(shù)的入?yún)⑹?code>this。我們都知道這個(gè)函數(shù)的入?yún)?yīng)該是BuildContext類型的。這個(gè)入?yún)⑵鋵?shí)就是這個(gè)StatelessElement。

StatefulElement

StatefulElement對應(yīng)的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構(gòu)造的時(shí)候會(huì)調(diào)用對應(yīng)StatefulWidgetcreateState()函數(shù)。也就是說State是在實(shí)例化StatefulElement的時(shí)候被實(shí)例化的。并且State實(shí)例會(huì)被這個(gè)StatefulElement實(shí)例持有。從這里也可以看出為什么StatefulWidget的狀態(tài)要由單獨(dú)的State管理,每次刷新的時(shí)候可能會(huì)有一個(gè)新的StatefulWidget被創(chuàng)建,但是State實(shí)例是不變的。

build()函數(shù)調(diào)用的是我們熟悉的State.build(this),現(xiàn)在你也知道了Statebuild()函數(shù)是在哪里被調(diào)用的了吧。而且你看,build()函數(shù)的入?yún)⑹?code>this。我們都知道這個(gè)函數(shù)的入?yún)?yīng)該是BuildContext類型的。這個(gè)入?yún)⑵鋵?shí)就是這個(gè)StatefulElement。

我們都知道State有狀態(tài),當(dāng)狀態(tài)改變時(shí)對應(yīng)的回調(diào)函數(shù)會(huì)被調(diào)用。這些回調(diào)函數(shù)其實(shí)都是在StatefulElement里被調(diào)用的。

在函數(shù)_firstBuild()里會(huì)調(diào)用State.initState()State.didChangeDependencies()。

在函數(shù)deactivate()里會(huì)調(diào)用State.deactivate()。

在函數(shù)unmount()里會(huì)調(diào)用State.dispose()。

在函數(shù)didChangeDependencies()里會(huì)調(diào)用State.didChangeDependencies()。

InheritedElement

InheritedElement對應(yīng)的WidgetInheritedWidget。其內(nèi)部實(shí)現(xiàn)主要是在維護(hù)對其有依賴的子ElementMap,以及在需要的時(shí)候調(diào)用子Element對應(yīng)的didChangeDependencies()回調(diào),這里就不貼代碼了,大家感興趣的話可以自己去看一下源碼。

RenderObjectElement

RenderObjectElement對應(yīng)的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);

}

函數(shù)mount()被調(diào)用的時(shí)候會(huì)調(diào)用RenderObjectWidget.createRenderObject()來實(shí)例化RenderObject

函數(shù)update()performRebuild()被調(diào)用的時(shí)候會(huì)調(diào)用RenderObjectWidget.updateRenderObject()。

函數(shù)unmount()被調(diào)用的時(shí)候會(huì)調(diào)用RenderObjectWidget.didUnmountRenderObject()

RenderObject

RenderObject負(fù)責(zé)渲染流水線布局(layout)階段和繪制(paint)階段的工作。同時(shí)也維護(hù)render tree。對render tree的維護(hù)方法是來自基類AbstractNode。這里我們主要關(guān)注和渲染流水線相關(guān)的一些方法。

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()標(biāo)記這個(gè)RenderObject需要重新做布局。markNeedsPaint標(biāo)記這個(gè)RenderObject需要重繪。這兩個(gè)函數(shù)只做標(biāo)記。標(biāo)記之后Flutter框架會(huì)調(diào)度一幀,在下一個(gè)Vsync信號到來之后才真正做布局和繪制。

真正的布局在函數(shù)layout()中進(jìn)行。這個(gè)函數(shù)會(huì)做一次判斷,如果sizedByParenttrue。則會(huì)調(diào)用performResize()。表明這個(gè)RenderObject的尺寸僅由其父節(jié)點(diǎn)決定。然后會(huì)調(diào)用performLayout()做布局。performResize()performLayout()都需要RenderObject的子類去實(shí)現(xiàn)。`

總結(jié)

Widget,ElementRenderObject體系是Flutter框架的核心。其中Element需要好好理解。Flutter的渲染流水線中的構(gòu)建(build)階段主要就是在維護(hù)更新element tree里面的Element節(jié)點(diǎn)。只有理解了Element和element tree,才是真正掌握了Flutter框架。這篇文章里只是一些靜態(tài)的說明。下篇文章我會(huì)嘗試從渲染流水線動(dòng)態(tài)運(yùn)行的角度分析一下Flutter框架是怎么運(yùn)行的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文在此,此處只為學(xué)習(xí) Widget與ElementWidget主要接口Stateless WidgetState...
    lltree閱讀 4,629評論 0 1
  • 目錄 一、Flutter 為何使用Dart開發(fā)語言二、Flutter的UI系統(tǒng)1.特點(diǎn)2.架構(gòu)簡介2.1 Flut...
    十拿九穩(wěn)啦閱讀 3,892評論 3 28
  • 國慶后面兩天在家學(xué)習(xí)整理了一波flutter,基本把能擼過能看到的代碼都過了一遍,此文篇幅較長,建議保存(star...
    Nealyang閱讀 4,453評論 1 17
  • (一) 走出芙蓉廣場地鐵站五號出口,陣陣寒風(fēng)迎面襲來,我不禁聳肩,手拽了拽身上的羽絨服。 抬頭望去,中天廣場寫字樓...
    寂寞捕手閱讀 796評論 19 14
  • “家一不小心就變成一個(gè)沒有溫暖、只有壓迫的地方。外面的世界固然荒涼。但是家卻可以更寒冷。” 1 天剛蒙蒙亮,我就醒...
    孚苼閱讀 628評論 2 5

友情鏈接更多精彩內(nèi)容