Flutter運行原理篇之Build構(gòu)建的過程

哈羅大家好,這個是我們Flutter的原理篇第二篇內(nèi)容宪躯,第一篇的內(nèi)容大家感興趣的話可以點擊這個《Flutter原理篇:聊一聊future,await,事件隊列,微任務(wù)》 連接去查看坝橡,我們今天來說一下《Flutter運行原理篇之Build構(gòu)建的過程》

Build構(gòu)建是Widget最開始執(zhí)行的步驟過程,我們要弄懂原理就要從Widget一開始怎么運行去洞察他棋电,首先我們知道Widget的更新運行要經(jīng)過5個步驟:

void drawFrame() {
  buildOwner!.buildScope(renderViewElement!); // 1.重新構(gòu)建widget
  super.drawFrame();
  //下面幾個是在super.drawFrame()執(zhí)行的
  pipelineOwner.flushLayout();          // 2.更新布局
  pipelineOwner.flushCompositingBits();     //3.更新“層合成”信息
  pipelineOwner.flushPaint();               // 4.重繪
  if (sendFramesToEngine) {
    renderView.compositeFrame();            // 5. 上屏霎俩,將繪制出的bit數(shù)據(jù)發(fā)送給GPU
  }
}

所以我們的Build只是其中的第一個重要的步驟而已,好的讓我們從頭開始吧!

在介紹這個原理之前先給大家普及或者回憶下一些基本的內(nèi)容饥悴,我怕有些初學(xué)的朋友連概率可能都不清楚一看源碼有可能會迷糊棠笑,其中有些我也是吸收了網(wǎng)路上面好的文章再加上我自己總結(jié)的經(jīng)驗在里面了

首先介紹的就是三棵樹的概念:這個概念想必是Flutter的初學(xué)者也會不陌生库车,上圖:

Pasted Graphic.png

這里需要注意:

  1. 三棵樹中阵漏,經(jīng)常我們聽過這三棵樹是一一對應(yīng)的,其實這種說法只是宏觀上的,微觀上是不對的栈妆,確切的說應(yīng)該是Widget 和 Element 是一一對應(yīng)的昧旨,但并不和 RenderObject 一一對應(yīng)

例如:Text的Widget 在他的build方法里面返回的確是一個叫做RichText的Widget蒋得,而RichText 又繼承于 MultiChildRenderObjectWidget 在RichText里面才有createRenderObject方法去生產(chǎn)一個RenderObject

Text的Build方法片段如下:

@override
Widget build(BuildContext context) {

    // 省略部分片段代碼
  Widget result = RichText(
    textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
    textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null.
    locale: locale, // RichText uses Localizations.localeOf to obtain a default if this is null
    softWrap: softWrap ?? defaultTextStyle.softWrap,
    overflow: overflow ?? defaultTextStyle.overflow,
    textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
    maxLines: maxLines ?? defaultTextStyle.maxLines,
    strutStyle: strutStyle,
    textWidthBasis: textWidthBasis ?? defaultTextStyle.textWidthBasis,
    textHeightBehavior: textHeightBehavior ?? defaultTextStyle.textHeightBehavior ?? DefaultTextHeightBehavior.of(context),
    text: TextSpan(
      style: effectiveTextStyle,
      text: data,
      children: textSpan != null ? <InlineSpan>[textSpan!] : null,
    ),
  );
    // 省略部分片段代碼
}
class RichText extends MultiChildRenderObjectWidget {

    // 省略部分片段代碼 

  @override
  RenderParagraph createRenderObject(BuildContext context) {
    assert(textDirection != null || debugCheckHasDirectionality(context));
    return RenderParagraph(text,
      textAlign: textAlign,
      textDirection: textDirection ?? Directionality.of(context),
      softWrap: softWrap,
      overflow: overflow,
      textScaleFactor: textScaleFactor,
      maxLines: maxLines,
      strutStyle: strutStyle,
      textWidthBasis: textWidthBasis,
      textHeightBehavior: textHeightBehavior,
      locale: locale ?? Localizations.maybeLocaleOf(context),
    );
  }

    // 省略部分片段代碼
}

這足以說明在微觀上并不是一一對應(yīng)的乒疏,我們再來簡單介紹下這三棵樹的對象有什么作用:

1.  Widget就是一個描述文件转绷,這些描述文件在我們進(jìn)行狀態(tài)改變時會不斷的調(diào)用build方法;
2.  Element是一個元素的起點,從他開始生成Widget,再從widget生成RenderObject鸭栖,他包括了這兩個對象屬性松却,你也可以理解為他們互相都包容
3.  RenderObject才是最終可以被layout可以被paint繪制的對象

只不過為了開發(fā)簡單便捷,一般情況下我們只會操作Widget而且溅话,把Element與RenderObject隔離開了玻褪,真的隔離開了嗎?其實并沒有公荧,例如我們再widget的build方法里面的BuildContext對象其實就是他對應(yīng)的Element對象,這個我們后面會說到

好了讓我開始說到Flutter的Widget構(gòu)建過程分兩種調(diào)用時機:

BuildOwner.buildScope() 會有兩種調(diào)用時機:

  • 樹構(gòu)建(應(yīng)用啟動時):runApp() 方法調(diào)用的 scheduleAttachRootWidget() 方法同规,它會構(gòu)建Widgets Tree循狰,Element Tree與RenderObject Tree三棵樹。
  • 樹更新(更新時):這里不會重新構(gòu)建三棵樹券勺,而是只會更新dirty區(qū)域的Element绪钥,從而開始Build構(gòu)建Widget

我們先來看看第一次構(gòu)建Build是怎么運行的:

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

WidgetsBinding.attachRootWidget

void attachRootWidget(Widget rootWidget) {
  _readyToProduceFrames = true;
  _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
    container: renderView,
    debugShortDescription: '[root]',
    child: rootWidget,
  ).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
}

RenderObjectToWidgetAdapter繼承自RenderObjectWidget,把他看成是一個普通的RenderObjectWidget即可关炼,RenderObjectWidget也就是從RenderObject到Element樹的橋梁程腹,注意此時的renderViewElement 肯定是null,接著往下看

RenderObjectToWidgetAdapter.attachToRenderTree

RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
  if (element == null) {
    owner.lockState(() {
      element = createElement();
      assert(element != null);
      element.assignOwner(owner);
    });
    owner.buildScope(element, () {
      element.mount(null, null);
    });
    // This is most likely the first time the framework is ready to produce
    // a frame. Ensure that we are asked for one.
    SchedulerBinding.instance.ensureVisualUpdate();
  } else {
    element._newWidget = this;
    element.markNeedsBuild();
  }
  return element;
}

createElement創(chuàng)建的element是RenderObjectToWidgetElement儒拂,他是一個RootRenderObjectElement寸潦,也就是根element。
element.mount(null, null);會向下遍歷并構(gòu)建整個widget樹社痛,這里我們看到了熟悉的兩個方法createElement與mount方法见转,只不過這里的createElement并不是創(chuàng)建我們自己寫的Widget對應(yīng)的Element,而是一個根Element蒜哀,這個mount確是整個非根Element創(chuàng)建的開端斩箫,讓我往下看:

RenderObjectToWidgetElement.mount

@override
void mount(Element parent, dynamic newSlot) {
  assert(parent == null);
  super.mount(parent, newSlot);
  _rebuild();
}

RenderObjectToWidgetElement._rebuild

void _rebuild() {
  try {
    _child = updateChild(_child, widget.child, _rootChildSlot);
  } catch (exception, stack) {
  }
}

此時的widget就是RenderObjectToWidgetAdapter,它的widget.child就是runApp傳遞進(jìn)去的widget,也就是運行的第一個應(yīng)用Widget

Element.updateChild

Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
  乘客。狐血。。
  Element newChild;
  if (child != null) {
    //省略部分代碼
  } else {
    newChild = inflateWidget(newWidget, newSlot);
  }

  return newChild;
}

由于是第一次易核,Element child是null匈织,執(zhí)行else里的邏輯,inflateWidget使用子widget來創(chuàng)建一個子Element耸成,此時的newWidget是runApp傳遞進(jìn)去的widget报亩。

Element.inflateWidget

Element inflateWidget(Widget newWidget, dynamic newSlot) {
  assert(newWidget != null);
  final Key key = newWidget.key;
  if (key is GlobalKey) {
    final Element newChild = _retakeInactiveElement(key, newWidget);
    if (newChild != null) {
      assert(newChild._parent == null);
      newChild._activateWithParent(this, newSlot);
      final Element updatedChild = updateChild(newChild, newWidget, newSlot);
      assert(newChild == updatedChild);
      return updatedChild;
    }
  }

  final Element newChild = newWidget.createElement();
  newChild.mount(this, newSlot);
  return newChild;
}

運行有兩步:

  1. 如果是GlobalKey的話,會先從GlobalKey中獲取引用的Element井氢,如果有 有效的element的話就復(fù)用
  2. 如果不是GlobalKey 或 沒有從GlobalKey中獲取到element 的話弦追,就用widget調(diào)用其createElement()來創(chuàng)建了一個element,接著就把新建的子element掛載到了element樹上花竞。

一般我們走的是第二步劲件,下面一個是重點,這里調(diào)用了newWidget.createElement 去創(chuàng)建這個Widget對應(yīng)的Element约急,也就是我們第一個運行的Widget對應(yīng)的Element

接下來到element.mount零远,他主要有兩個運行分支流程,

  • 一個是ComponentElement的厌蔽,
  • 一個是RenderObjectElement的牵辣,

ComponentElement.mount

void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  assert(_child == null);
  assert(_active);
  _firstBuild();
  assert(_child != null);
}

super.mount會把parent記錄在此element中。

  void mount(Element? parent, Object? newSlot) {
    assert(_lifecycleState == _ElementLifecycle.initial);
    assert(widget != null);
    assert(_parent == null);
    assert(parent == null || parent._lifecycleState == _ElementLifecycle.active);
    assert(slot == null);
    _parent = parent;
    _slot = newSlot;
    _lifecycleState = _ElementLifecycle.active;
    _depth = _parent != null ? _parent!.depth + 1 : 1;
    if (parent != null) {
      // Only assign ownership if the parent is non-null. If parent is null
      // (the root node), the owner should have already been assigned.
      // See RootRenderObjectElement.assignOwner().
      _owner = parent.owner;
    }
    assert(owner != null);
    final Key? key = widget.key;
    if (key is GlobalKey) {
      owner!._registerGlobalKey(key, this);
    }
    _updateInheritance();
  }

firstBuild會調(diào)用到performRebuild方法:

void performRebuild() {
  Widget built;
  try {
    built = build();
  } catch (e, stack) {
  } finally {
  }
  try {
    _child = updateChild(_child, built, slot);
    assert(_child != null);
  } catch (e, stack) {
  }
}

此方法會首先調(diào)用build方法奴饮,通過這一步來創(chuàng)建其子widget纬向,也就是build方法里面返回的Widget,ComponentElement主要有兩個子類stateful和stateless戴卜,會有不同的實現(xiàn)逾条。

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

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

/// An [Element] that uses a [StatefulWidget] as its configuration.
class StatefulElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.

  //省略部分代碼

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

  //省略部分代碼
}

看到這里大家就應(yīng)該很明白了,這個兩個Element的Build方法其實就是調(diào)用了Widget的Build方法投剥,也是我們最熟悉的build方法了他就是在這里被調(diào)用的

然后就又調(diào)用到了updateChild方法师脂,這就回到了上邊流程一直往下遍歷創(chuàng)建widget樹,不知道大家看明白了沒有江锨,其實就是首先從我們傳入的第一個Widget開始創(chuàng)建他對應(yīng)的Element吃警,然后這個Element調(diào)用了build方法然后間接調(diào)用了這個Widget的build方法,這個方法里面是我們的實現(xiàn)啄育,一般都會返回一個Widget(也就是他的子Widget)汤徽,然后再調(diào)用這個updateChild方法再調(diào)用inflateWidget方法去創(chuàng)建這個子Widget對應(yīng)的Element,然后再調(diào)用他的mount方法去調(diào)用子Widget的build方法去返回他的子子Widget灸撰,一直周而復(fù)始的這樣循環(huán)一直到這個Element是RenderObjectElement

上面說的是ComponentElement的mount方法運行流程谒府,還有一種是RenderObjectElement的mount方法拼坎,這種就是可渲染對象的mount方法與上面的流程會不一樣

RenderObjectElement.mount

void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);

  _renderObject = widget.createRenderObject(this);

  attachRenderObject(newSlot);
  _dirty = false;
}

此方法會創(chuàng)建一個RenderObject,之后就會把此RenderObject添加到RenderObject樹上完疫。

void attachRenderObject(dynamic newSlot) {
  assert(_ancestorRenderObjectElement == null);
  _slot = newSlot;
  _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
  _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);

  final ParentDataElement<ParentData> parentDataElement = _findAncestorParentDataElement();
  if (parentDataElement != null)
    _updateParentData(parentDataElement.widget);
}

_findAncestorRenderObjectElement往上找離它最近的父RenderObjectElement泰鸡,然后把當(dāng)前的renderObject給插入到父RenderObjectElement中。

SingleChildRenderObjectElement.insertRenderObjectChild

@override
void insertRenderObjectChild(RenderObject child, Object? slot) {
  final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject as RenderObjectWithChildMixin<RenderObject>;
  assert(slot == null);
  assert(renderObject.debugValidateChild(child));
  renderObject.child = child;
  assert(renderObject == this.renderObject);
}

我們來看看一個可渲染對象對應(yīng)的Element壳鹤,SingleChildRenderObjectElement的insertRenderObjectChild具體實現(xiàn)是什么樣的呢盛龄,很簡單他這里把上面找到的父類的可渲染對象renderObject的child賦值了當(dāng)前對象,這個無形中通過child字段就形成了一個renderObject的鏈條芳誓,這里是值得關(guān)注的

接著_findAncestorParentDataElement()余舶,它的作用是找到離它最近的父ParentDataElement,并用ParentDataWidget上的數(shù)據(jù)更新此renderObject上的ParentData數(shù)據(jù)

好了锹淌,上面是我們一開始運行程序的時候的構(gòu)建流程匿值,看起來其實也沒有那么復(fù)雜,接下來我們講解一下如果遇到更新的時候Widget的構(gòu)建流程是怎么樣的

狀態(tài)更新時赂摆,會把標(biāo)記變?yōu)閑lement dirty狀態(tài)(這個下面我們會具體分析)挟憔,由于一般狀態(tài)更新是StatefulWidget才可以的,所以我們這里只分析StatefulWidget即可烟号,大家應(yīng)該記得StatefulWidget是通過調(diào)用State.setState來標(biāo)識當(dāng)前需要更新子widget的绊谭,如下:

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

我們可以看到會先執(zhí)行傳遞進(jìn)來的函數(shù)類型參數(shù)(這個也就是我們在setState里面自定義更新的具體內(nèi)容),然后調(diào)用markNeedsBuild來標(biāo)記此element需要更新其子widget

void markNeedsBuild() {
  if (!_active)
    return;

  if (dirty)
    return;
  _dirty = true;
  owner.scheduleBuildFor(this);
}

此方法首先會判斷是否已經(jīng)調(diào)用過了汪拥,_dirty會在之后的rebuild后重新設(shè)置為true达传,
owner是BuildOwner,用來管理widget框架迫筑。此類用于追蹤需要rebuilding的widget宪赶,并作為一個整體處理應(yīng)用于widget樹的其他任務(wù),比如管理樹的 inactive element列表铣焊,以及在調(diào)試時hot reload期間在必要時觸發(fā)“reassemble”命令,一般BuildOwner是由WidgetsBinding持有罕伯,并且與 build/layout/paint管道的其余部分一起被操作系統(tǒng)驅(qū)動

BuildOwner.scheduleBuildFor

void scheduleBuildFor(Element element) {
  if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
    _scheduledFlushDirtyElements = true;
    onBuildScheduled();
  }
  _dirtyElements.add(element);
  element._inDirtyList = true;
}

首先調(diào)用onBuildScheduled()方法曲伊,此方法是一個回調(diào),他是在WidgetsBinding中賦值的追他,然后把此element加入到了_dirtyElements列表中了坟募。

WidgetsBinding.initInstances

void initInstances() {
  super.initInstances();
  _instance = this;

  _buildOwner = BuildOwner();
  buildOwner.onBuildScheduled = _handleBuildScheduled;
}

_handleBuildScheduled的具體實現(xiàn)如下:

void _handleBuildScheduled() {
  ensureVisualUpdate();
}

void ensureVisualUpdate() {
  switch (schedulerPhase) {
    case SchedulerPhase.idle:
    case SchedulerPhase.postFrameCallbacks:
      scheduleFrame();
      return;
    case SchedulerPhase.transientCallbacks:
    case SchedulerPhase.midFrameMicrotasks:
    case SchedulerPhase.persistentCallbacks:
      return;
  }
}

void scheduleFrame() {
  if (_hasScheduledFrame || !framesEnabled)
    return;

  ensureFrameCallbacksRegistered();
  window.scheduleFrame();
  _hasScheduledFrame = true;
}

先調(diào)用ensureFrameCallbacksRegistered(這個里面會注冊下一幀到來的回調(diào)方法),最后會去調(diào)用底層window.scheduleFrame()來注冊一個下一幀時回調(diào)邑狸,就類似于Android中的ViewRootImpl.scheduleTraversals()懈糯,下一幀來到后,會調(diào)用到的方法是在上邊的ensureFrameCallbacksRegistered()中注冊的回調(diào)单雾,

SchedulerBinding.ensureFrameCallbacksRegistered


void ensureFrameCallbacksRegistered() {
  window.onBeginFrame ??= _handleBeginFrame;
  window.onDrawFrame ??= _handleDrawFrame;
}

onBeginFrame :主要是用來執(zhí)行動畫赚哗,onDrawFrame :這個主要處理上邊case里面的persistentCallbacks

下面我們簡單介紹下frame 處理流程以及對應(yīng)的幾個狀態(tài):

當(dāng)有新的 frame 到來時她紫,開始調(diào)用 SchedulerBinding.handleDrawFrame 來處理 frame,具體處理過程就是依次執(zhí)行四個任務(wù)隊列:transientCallbacks屿储、midFrameMicrotasks贿讹、persistentCallbacks、postFrameCallbacks够掠,當(dāng)四個任務(wù)隊列執(zhí)行完畢后當(dāng)前 frame 結(jié)束民褂。
綜上,F(xiàn)lutter 將整個生命周期分為五種狀態(tài)疯潭,通過 SchedulerPhase 枚舉類來表示它們:

enum SchedulerPhase {
  
  /// 空閑狀態(tài)赊堪,并沒有 frame 在處理。這種狀態(tài)代表頁面未發(fā)生變化竖哩,并不需要重新渲染哭廉。
  /// 如果頁面發(fā)生變化,需要調(diào)用`scheduleFrame()`來請求 frame期丰。
  /// 注意群叶,空閑狀態(tài)只是指沒有 frame 在處理,通常微任務(wù)钝荡、定時器回調(diào)或者用戶事件回調(diào)都
  /// 可能被執(zhí)行街立,比如監(jiān)聽了tap事件,用戶點擊后我們 onTap 回調(diào)就是在idle階段被執(zhí)行的埠通。
  idle,

  /// 執(zhí)行”臨時“回調(diào)任務(wù)赎离,”臨時“回調(diào)任務(wù)只能被執(zhí)行一次,執(zhí)行后會被移出”臨時“任務(wù)隊列端辱。
  /// 典型的代表就是動畫回調(diào)會在該階段執(zhí)行梁剔。
  transientCallbacks,

  /// 在執(zhí)行臨時任務(wù)時可能會產(chǎn)生一些新的微任務(wù),比如在執(zhí)行第一個臨時任務(wù)時創(chuàng)建了一個
  /// Future舞蔽,且這個 Future 在所有臨時任務(wù)執(zhí)行完畢前就已經(jīng) resolve 了荣病,這中情況
  /// Future 的回調(diào)將在[midFrameMicrotasks]階段執(zhí)行
  midFrameMicrotasks,

  /// 執(zhí)行一些持久的任務(wù)(每一個frame都要執(zhí)行的任務(wù)),比如渲染管線(構(gòu)建渗柿、布局个盆、繪制)
  /// 就是在該任務(wù)隊列中執(zhí)行的.
  persistentCallbacks,

  /// 在當(dāng)前 frame 在結(jié)束之前將會執(zhí)行 postFrameCallbacks,通常進(jìn)行一些清理工作和
  /// 請求新的 frame朵栖。
  postFrameCallbacks,
}

好了我們繼續(xù)講解上面提到的回調(diào)handleDrawFrame

void handleDrawFrame() {
  try {
    // PERSISTENT FRAME CALLBACKS
    _schedulerPhase = SchedulerPhase.persistentCallbacks;
    for (final FrameCallback callback in _persistentCallbacks)
      _invokeFrameCallback(callback, _currentFrameTimeStamp!);

    // POST-FRAME CALLBACKS
    _schedulerPhase = SchedulerPhase.postFrameCallbacks;
    final List<FrameCallback> localPostFrameCallbacks =
        List<FrameCallback>.from(_postFrameCallbacks);
    _postFrameCallbacks.clear();
    for (final FrameCallback callback in localPostFrameCallbacks)
      _invokeFrameCallback(callback, _currentFrameTimeStamp!);
  } finally {
  }
}

看下persistentCallbacks列表在哪添加的callback颊亮,最終找到是在RendererBinding.initInstances中添加的callback

void initInstances() {
  super.initInstances();
  _instance = this;
  _pipelineOwner = PipelineOwner(
    onNeedVisualUpdate: ensureVisualUpdate,
    onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
    onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
  );

  initRenderView();

  addPersistentFrameCallback(_handlePersistentFrameCallback);
  initMouseTracker();
}

RendererBinding._handlePersistentFrameCallback

void _handlePersistentFrameCallback(Duration timeStamp) {
  drawFrame();
  _scheduleMouseTrackerUpdate();
}

WidgetsFlutterBinding中的WidgetsBinding繼承了RendererBinding重載了drawFrame方法,把build流程加入進(jìn)來了陨溅。

WidgetsBinding.drawFrame

@override
void drawFrame() {

  try {
    if (renderViewElement != null)
      buildOwner.buildScope(renderViewElement); //這里就是我們提到的刷新以后的build構(gòu)建
    super.drawFrame();
    buildOwner.finalizeTree();
  } finally {
  }
}

由于繼承關(guān)系的順序問題WidgetsBinding里面的super.drawFrame();又會調(diào)用到RendererBinding的drawFrame方法

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance!;
  }
}

我們再貼一下RendererBinding.drawFrame方法终惑,加上父類的drawFrame方法是不是就得出了本文一開始我們提到的整個渲染的幾個步驟呢

@protected
void drawFrame() {
  assert(renderView != null);
  pipelineOwner.flushLayout();
  pipelineOwner.flushCompositingBits();
  pipelineOwner.flushPaint();
  if (sendFramesToEngine) {
    renderView.compositeFrame(); // this sends the bits to the GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
    _firstFrameSent = true;
  }
}

好了,本文還是重點關(guān)注build構(gòu)建階段的內(nèi)容门扇,

void buildScope(Element context, [ VoidCallback callback ]) {
  if (callback == null && _dirtyElements.isEmpty)
    return;
  try {
    _scheduledFlushDirtyElements = true;
    if (callback != null) {
      assert(_debugStateLocked);
      Element debugPreviousBuildTarget;
      _dirtyElementsNeedsResorting = false;
      try {
// 可以添加一個回調(diào)在build之前執(zhí)行雹有。
        callback();
      } finally {
      }
    }
    _dirtyElements.sort(Element._sort);
    _dirtyElementsNeedsResorting = false;
    int dirtyCount = _dirtyElements.length;
    int index = 0;
    while (index < dirtyCount) {
      try {
        _dirtyElements[index].rebuild();
      } catch (e, stack) {
      }
      index += 1;
      if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
        _dirtyElements.sort(Element._sort);
        _dirtyElementsNeedsResorting = false;
        dirtyCount = _dirtyElements.length;
        while (index > 0 && _dirtyElements[index - 1].dirty) {
          index -= 1;
        }
      }
    }
  } finally {
    for (final Element element in _dirtyElements) {
      element._inDirtyList = false;
    }
    _dirtyElements.clear();
    _scheduledFlushDirtyElements = false;
    _dirtyElementsNeedsResorting = null;
  }
}

可以看到會遍歷dirtyElements列表中的element.rebuild()偿渡,而element.rebuild()最終會調(diào)用到performRebuild()。

Element.rebuild

@pragma('vm:prefer-inline')
void rebuild() {
  assert(_lifecycleState != _ElementLifecycle.initial);
  if (_lifecycleState != _ElementLifecycle.active || !_dirty)
    return;
  assert(() {
    debugOnRebuildDirtyWidget?.call(this, _debugBuiltOnce);
    if (debugPrintRebuildDirtyWidgets) {
      if (!_debugBuiltOnce) {
        debugPrint('Building $this');
        _debugBuiltOnce = true;
      } else {
        debugPrint('Rebuilding $this');
      }
    }
    return true;
  }());
  assert(_lifecycleState == _ElementLifecycle.active);
  assert(owner!._debugStateLocked);
  Element? debugPreviousBuildTarget;
  assert(() {
    debugPreviousBuildTarget = owner!._debugCurrentBuildTarget;
    owner!._debugCurrentBuildTarget = this;
    return true;
  }());
  performRebuild();
  assert(() {
    assert(owner!._debugCurrentBuildTarget == this);
    owner!._debugCurrentBuildTarget = debugPreviousBuildTarget;
    return true;
  }());
  assert(!_dirty);
}

Element.performRebuild方法會根據(jù)不同類型的子類element去重建子widget或重建子element件舵,分為以下兩種:

ComponentElement.performRebuild

void performRebuild() {
  Widget built;
  try {
    built = build();
  } catch (e, stack) {

  } finally {
    // We delay marking the element as clean until after calling build() so
    // that attempts to markNeedsBuild() during build() will be ignored.
    _dirty = false;
  }
  try {
    _child = updateChild(_child, built, slot);
  } catch (e, stack) {
  }
}

會先調(diào)用build方法創(chuàng)建一個子widget(這個就是build方法也就是我們自己在stateful控件里面實現(xiàn)的方法)卸察,然后調(diào)用Element.updateChild來更新

RenderObjectElement.performRebuild


void performRebuild() {
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}

因為RenderObjectElement是可渲染對象RenderObjec的對應(yīng)的Element,其不具備包裹性(這里說的包裹性指的是他沒有包裹widget)所以他沒有對應(yīng)的build方法可以調(diào)用铅祸,只是更新widget的配置坑质,這個更新我們晚點再說,我們先看看Element.updateChild里面的內(nèi)容

Element.updateChild

Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
  if (newWidget == null) {
    if (child != null)
      deactivateChild(child);
    return null;
  }
  Element newChild;
  if (child != null) {
    bool hasSameSuperclass = true;
    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);

      newChild = child;
    } else {
      deactivateChild(child);
      newChild = inflateWidget(newWidget, newSlot);
    }
  } else {
    newChild = inflateWidget(newWidget, newSlot);
  }

  return newChild;
}

由于不是第一次調(diào)用临梗,所以Element child不是null涡扼,進(jìn)入if邏輯,會進(jìn)行三個判斷:

  • 如果子widget不變盟庞,子element和子widget匹配(就是是否都是stateless吃沪,或都是stateful),那么更新slot
  • 如果子element和子widget匹配什猖,但子widget發(fā)生了變化票彪,就調(diào)用子element.update(newWidget)來更新widget配置,這種是我們最常見的情況
  • 最后一個判斷是子element和子widget不匹配不狮,那么就把老的child element加入到一個_inactiveElements列表中(變成未激活狀態(tài))降铸,然后進(jìn)行重建element

element.update方法會把newWidget記錄下來,以便于下次更新作為更新判斷使用摇零,并且這里也是Element里面用了新build的newWidget替換掉了老的widget推掸,這里是需要注意的


@mustCallSuper
void update(covariant Widget newWidget) {
  _widget = newWidget;
}

我們再看看Element另一個子類StatelessElement的update方法
StatelessElement.update


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

最后又調(diào)用rebuild,上面已經(jīng)提到了rebuild中會調(diào)用performRebuild()去重建其子widget驻仅,類似一個遞歸的流程

再來看看Element另一個子類StatefulElement的update方法谅畅,也就是我們的例子會調(diào)用到這的方法

StatefulElement.update

@override
void update(StatefulWidget newWidget) {
  super.update(newWidget);
  assert(widget == newWidget);
  final StatefulWidget oldWidget = _state._widget;
  // Notice that we mark ourselves as dirty before calling didUpdateWidget to
  // let authors call setState from within didUpdateWidget without triggering
  // asserts.
  _dirty = true;
  _state._widget = widget as StatefulWidget;
  try {
    final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
  } finally {
  }
  rebuild();
}

回調(diào)state.didUpdateWidget(這里就是我們在自定義Widget寫的生命周期回調(diào)函數(shù)就是在這里觸發(fā)的),最后又調(diào)用rebuild噪服,rebuild中會調(diào)用performRebuild()去重建其子widget毡泻,類似一個遞歸的流程

最后來看看可渲染對象對應(yīng)的Element-RenderObjectElement的update方法是怎么樣的:

RenderObjectElement.update

@override
void update(covariant RenderObjectWidget newWidget) {
  super.update(newWidget);
  assert(widget == newWidget);
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}

上面我們說過了RenderObjectElement是可渲染對象RenderObjec的對應(yīng)的Element,其不具備包裹性(這里說的包裹性指的是他沒有包裹widget)所以他沒有再構(gòu)建子Widget的build方法的行為粘优,update方法里面只是更新widget的配置仇味,我們下面會提到這個更新的步驟

好了寫到這里我們已經(jīng)很清楚了build構(gòu)建步驟觸發(fā)的時間與流程,另外我們也額外的了解到了一些生命周期函數(shù)的觸發(fā)時機在哪里敬飒,順便在給大家一個小的彩蛋:我們的Element鏈條是在哪里形成的呢邪铲,大家可以觀察下Element.mount函數(shù)芬位,里面通過_parent字段標(biāo)記了一個Element的父親无拗,再通過updateChild方法里面(代碼就不貼了往上看)的child字段來確定他的子類,這樣的話一個Element的鏈條就形成了(這里有些博客忽略的細(xì)節(jié)昧碉,希望能給到你幫助)

Element.mount

void mount(Element? parent, Object? newSlot) {
  assert(_lifecycleState == _ElementLifecycle.initial);
  assert(widget != null);
  assert(_parent == null);
  assert(parent == null || parent._lifecycleState == _ElementLifecycle.active);
  assert(slot == null);
  _parent = parent;     //重點看這里
  _slot = newSlot;
  _lifecycleState = _ElementLifecycle.active;
  _depth = _parent != null ? _parent!.depth + 1 : 1;
  if (parent != null) {
    // Only assign ownership if the parent is non-null. If parent is null
    // (the root node), the owner should have already been assigned.
    // See RootRenderObjectElement.assignOwner().
    _owner = parent.owner;
  }
  assert(owner != null);
  final Key? key = widget.key;
  if (key is GlobalKey) {
    owner!._registerGlobalKey(key, this);
  }
  _updateInheritance();
}

PS:我們的RenderObject的鏈條上面也已經(jīng)說過了哦英染,忘記的可以回看一下

最后我們再來提一下另一個菜單也就是我們上面留下的一個問題RenderObjectElement.performRebuild里面的updateRenderObject方法與上面提到的RenderObjectElement.update里面的updateRenderObject方法好像還沒有結(jié)論

void performRebuild() {
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}

其實這里會觸發(fā)我們整個渲染流程的第二步揽惹,也就是layout的步驟就是在這里觸發(fā)的,我們下一篇原理篇會重點講解下這個步驟四康,小伙伴們期待一下吧搪搏,好了本文就寫到這里了如果有喜歡的小伙伴歡迎點贊,有疑問的可以給我留言闪金,我都會回復(fù)大家的疯溺,我們下期再見···

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市哎垦,隨后出現(xiàn)的幾起案子囱嫩,更是在濱河造成了極大的恐慌,老刑警劉巖漏设,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件墨闲,死亡現(xiàn)場離奇詭異,居然都是意外死亡郑口,警方通過查閱死者的電腦和手機鸳碧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來犬性,“玉大人瞻离,你說我怎么就攤上這事∽卸幔” “怎么了琐脏?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長缸兔。 經(jīng)常有香客問我日裙,道長,這世上最難降的妖魔是什么惰蜜? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任昂拂,我火速辦了婚禮,結(jié)果婚禮上抛猖,老公的妹妹穿的比我還像新娘格侯。我一直安慰自己,他們只是感情好财著,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布联四。 她就那樣靜靜地躺著,像睡著了一般撑教。 火紅的嫁衣襯著肌膚如雪朝墩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天伟姐,我揣著相機與錄音收苏,去河邊找鬼亿卤。 笑死,一個胖子當(dāng)著我的面吹牛鹿霸,可吹牛的內(nèi)容都是我干的排吴。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼懦鼠,長吁一口氣:“原來是場噩夢啊……” “哼钻哩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肛冶,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤憋槐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后淑趾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阳仔,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年扣泊,在試婚紗的時候發(fā)現(xiàn)自己被綠了近范。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡延蟹,死狀恐怖评矩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情阱飘,我是刑警寧澤斥杜,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站沥匈,受9級特大地震影響蔗喂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜高帖,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一缰儿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧散址,春花似錦乖阵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吏祸,卻和暖如春对蒲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工齐蔽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人床估。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓含滴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親丐巫。 傳聞我的和親對象是個殘疾皇子谈况,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容