Flutter渲染之啟動與三棵樹的構建

JSShou發表於2021-07-11

呼叫圖

我們用下面的例子來分析Flutter啟動所做的事情

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

class MyApp extends StatelessWidget {
  const MyApp({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
複製程式碼

初始化Binding

runApp

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}
複製程式碼

runApp中,我們會呼叫WidgetsFlutterBinding.ensureInitialized()來初始化Binding

Binding

Binding是一系列單例,在啟動的時候初始化,其中包括了WidgetsFlutterBindingBindingBaseGestureBindingSchedulerBindingServicesBinding等,下面來分析它們的作用

  • BindingBase: 是所有類的基類,負責初始化其它類、初始化一些Native相關的資訊(如平臺是Android還是iOS)、註冊基本的Native事件(如熱更新、退出)
  • GestureBinding: 提供window.onPointerDataPacket回撥,接受Native事件,負責事件轉換及分發
  • SchedulerBinding: 使用了window.scheduleFrame來通知Native及使用window.onBeginFramewindow.onDrawFrame回撥來接收訊息,主要是負責通知Native在下一偵的事件下發與事件註冊,當我們呼叫setState後,就會觸發此類的方法,等待事件下發後進行渲染
  • ServicesBinding: 使用window.onPlatformMessage回撥,負責通道相關的初始化及通訊相關的處理
  • PaintingBinding: 與繪製相關的函式繫結,還處理一些圖片渲染相關的快取
  • SemanticsBinding: 註冊平臺相關的輔助函式
  • RendererBinding: 初始化PipelineOwnerrenderViewonMetricsChangedonTextScaleFactorChangedonPlatformBrightnessChangedonSemanticsEnabledChanged onSemanticsAction等,用於監聽並處理平臺渲染相關如字型、狀態列改變時的事件,是渲染輸與Flutter engine溝通的橋樑
  • WidgetsBinding: 初始化BuildOwner,註冊window.onLocaleChangedonBuildScheduled等回撥。它是Flutter widget層與engine的橋樑。

渲染樹的構建

attachRootWidget

[-> packages/flutter/lib/src/widgets/binding.dart]

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

上面會先初始化一個RenderObjectToWidgetAdapter,它是一個特殊的Widget,主要用Flutter樹根節點的初始化,其中傳了一個renderView,上面提到了是在RendererBinding初始化時就已經初始化了,它是根RenderObject節點,rootWidget就是我們runApp時傳入的Widget

後面呼叫了attachToRenderTree會開始一系列的樹構建

attachToRenderTree

[-> packages/flutter/lib/src/widgets/binding.dart]

RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
  // 如果element==null執行,第一次渲染element一定為null
  if (element == null) {
    owner.lockState(() {
      // 呼叫createElement建立Element
      element = createElement();
      assert(element != null);
      // 將owner傳給element,讓element持有owner
      element!.assignOwner(owner);
    });
    owner.buildScope(element!, () {
      element!.mount(null, null);
    });
    SchedulerBinding.instance!.ensureVisualUpdate();
  } else {
    element._newWidget = this;
    element.markNeedsBuild();
  }
  return element!;
}
複製程式碼

上面會先根據createElement建立一個Element物件,然後傳入ownerBuildOwner是Flutter處理更新的類,主要是處理髒節點收集與重繪,需要注意的是,根據Flutter的設計,全域性只有一個BuildOwner物件,在此處傳入後,它會在子節點傳遞。

然後會呼叫owner.buildScapebuildScape主要提供兩個作用,一是為Widget Tree更新建立一個作用域並執行回撥(像上面這種情況,不過回撥也可以不傳),二是當作用域中存在髒節點,它會呼叫Element.rebuild方法進行重新構建(就在此時呼叫的Widget的build重新渲染Element)。

接著會呼叫element.mount,這是一個Element非常重要的方法,這裡就開始構建三棵樹了.mount最簡單的作用就是為當前物件繫結父物件及為當前繫結_inheritedWidgets(你認為的沒錯,就是Flutter中用於資料共享的InheritedWidgetElement)。一般我們的子Element會重寫mount方法,用於孩子節點的初始化。

mount

[-> packages/flutter/lib/src/widgets/framework.dart]

@override
void mount(Element? parent, dynamic newSlot) {
  assert(parent == null);
  super.mount(parent, newSlot);
  _rebuild();
}
void _rebuild() {
  //...
    _child = updateChild(_child, widget.child, _rootChildSlot);
  //...
}

複製程式碼

所以在RenderObjectToWidgetElement中會呼叫_rebuild然後呼叫updateChild來初始化孩子節點。

updateChild

[-> packages/flutter/lib/src/widgets/framework.dart]

@protected
Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
  // ...
  final Element newChild;
  if (child != null) {
    bool hasSameSuperclass = true;
    if (hasSameSuperclass && child.widget == newWidget) {
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      newChild = child;
    } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
      //...
      child.update(newWidget);
      //...
      newChild = child;
    } else {
      deactivateChild(child);
      newChild = inflateWidget(newWidget, newSlot);
    }
  } else {
    newChild = inflateWidget(newWidget, newSlot);
  }
  return newChild;
}
複製程式碼

updateChild方法用於初始化或者更新孩子節點,當child != null時,主要是呼叫child.update來更新child,否則呼叫inflateWidget,這裡第一次渲染當然是會呼叫inflateWidget

inflateWidget

[-> packages/flutter/lib/src/widgets/framework.dart]

@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {
  // ...
  final Element newChild = newWidget.createElement();
  // ...
  newChild.mount(this, newSlot);
  return newChild;
}
複製程式碼

inflateWidget方法的作用就是根據Widget生成Element同時呼叫child.mount。這樣,一次樹的迴圈呼叫就接上了。

Widget樹構建

有人問StatelessWidgetStatefulWidget的build方法怎麼都沒呼叫。StatelessWidgetStatefulWidget對應的ElementStatelessElementStatefulElement,它們都是繼承至ComponentElement。根據上面的分析,mount方法是構建起點,我們可以看下ComponentElement中的mount方法。

_firstBuild

[-> packages/flutter/lib/src/widgets/framework.dart]

@override
void mount(Element? parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  assert(_child == null);
  assert(_lifecycleState == _ElementLifecycle.active);
  _firstBuild();
  assert(_child != null);
}
複製程式碼

呼叫了_firstBuild方法,然後再呼叫了Elementrebuild方法,接著呼叫到ComponentElementperformRebuild方法

performRebuild

[-> packages/flutter/lib/src/widgets/framework.dart]

void rebuild() {
  performRebuild();
}
//...
@override
void performRebuild() {
  Widget? built;
  try {
    // ...
    built = build();
  } catch (e, stack) {
    // ...
  } finally {
    _dirty = false;
  }
  try {
    _child = updateChild(_child, built, slot);
  } catch (e, stack) {
    // ...
    _child = updateChild(null, built, slot);
  }

  if (!kReleaseMode && debugProfileBuildsEnabled)
    Timeline.finishSync();
}
複製程式碼

performRebuildComponentElement實現的父類,其中呼叫了build方法返回一個Widget,然後就看到了熟悉的updateChild方法,這就接上了上面的分析。

RenderObject樹的構建

RenderObjectWidget

RenderObject是Flutter中用於實際繪製的類,對於StatefulWidgetStatelessWidget實際上都是沒有對應的RenderObject節點的,實際上它們只是提供一個容器,用來組合各種繪製。

只有繼承了RenderObjectWidgetWidget才有對應的RenderObject節點。Flutter中為了方便,又提供了三種繼承至RenderObjectWidget的類,每個類都有不同作用

  • LeafRenderObjectWidget 用於只有渲染功能,無子節點 (如Switch、Radio等)
  • SingleChildRenderObjectWidget 只含有一個子節點 (如SizedBox、Padding等)
  • MultiChildRenderObjectWidget 含有多個子節點(如Column、Stack等)

上面這些物件只是提供了一個容器給開發者使用,不過不是我們這次分析重點,先看看RenderObjectWidget

[-> packages/flutter/lib/src/widgets/framework.dart]

abstract class RenderObjectWidget extends Widget {
  const RenderObjectWidget({ Key? key }) : super(key: key);
  @override
  @factory
  RenderObjectElement createElement();
  @protected
  @factory
  RenderObject createRenderObject(BuildContext context);
  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
  @protected
  void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
複製程式碼

它提供了createRenderObject方法來構建RenderObject,看下其對應的Element->RenderObjectElementmount方法

createRenderObject

[-> packages/flutter/lib/src/widgets/framework.dart]

@override
void mount(Element? parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _renderObject = widget.createRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;
}
@override
void attachRenderObject(dynamic newSlot) {
  _slot = newSlot;
  _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
  // 將上一步生成的renderObject插入到父renderObject中
  _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
  final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
  if (parentDataElement != null)
    _updateParentData(parentDataElement.widget);
}
複製程式碼

其會先呼叫createRenderObject來構建RenderObject,然後呼叫attachRenderObject將上一步生成的renderObject插入到父renderObject中。這裡的通過_findAncestorParentDataElement來查詢的父類,因為上面說過,不是每個Element都有對應的RenderObject節點。

_findAncestorRenderObjectElement

[-> packages/flutter/lib/src/widgets/framework.dart]

RenderObjectElement? _findAncestorRenderObjectElement() {
  Element? ancestor = _parent;
  while (ancestor != null && ancestor is! RenderObjectElement)
    ancestor = ancestor._parent;
  return ancestor as RenderObjectElement?;
}
複製程式碼

所以這裡通過父ElementElement樹中找上一個為RenderObjectElement的類,而insertRenderObjectChild是由上面說的SingleChildRenderObjectWidgetMultiChildRenderObjectWidget來具體實現的,主要是將生成的RenderObject插入到剛剛查詢的父RenderObject中。因為根Element節點RenderObjectToWidgetElement是繼承至RenderObjectElement,所以,只要通過RenderObjectToWidgetElement.renderObject即可拿到整棵樹RenderObject樹。

總結

上面我們分析了Flutter第一次啟動時的大致過程,WidgetsFlutterBinding是總工頭,幾乎App所有的事情都是通過它來管理,BuildOwner是構建樹的負責人,它負責樹的構建和更新,RenderObjectToWidgetAdapter是一個特殊的Widget,它是渲染樹的根節點,也是構建樹的起點,Widget只是負責與開發人員對接,它就是一個工具人,開發人員把資料放到這裡,後面都是通過Element進行處理並生成RenderObject(有些Widget不一定會生成RenderObject)。

現在我們只是生成了一個渲染樹,可是介面到底是如何渲染的呢,回到上面的attachToRenderTree方法,其中構件樹生成後會呼叫SchedulerBinding.instance!.ensureVisualUpdate方法,這個方法最後會呼叫window.scheduleFrame,它會傳送通知給Native:我這邊樹都生成好了,你在下一偵給我發個通知,我要開始渲染頁面啦。

想看渲染過程,請聽下回分解。

相關文章