Flutter啟動流程初探
最近開始研究Flutter了,俗話說工欲善其事必先利其器,在正式運用Flutter之前肯定要先了解了解它的工作機制,於是開始了Flutter以及Dart的原始碼學習之旅,這次就簡單的分析一下Flutter的啟動流程,作為記錄~
hello world
void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(title: 'Flutter Demo Home Page'), ); } }
上面是官網上一個demo的一部分,我們可以看到其中有一個main函式,內部使用了runApp並將業務檢視頂層的MyApp傳了進去。這樣我們的Flutter介面就能展示出來了。
runApp
void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() ..attachRootWidget(app) ..scheduleWarmUpFrame(); }
既然如此,我們就來看runApp的原始碼吧,內部邏輯很簡單,就是通過WidgetsFlutterBinding去初始化一些邏輯(ensureInitialized)方法,然後呼叫attachRootWidget並將我們的MyApp元件傳入。
attachRootWidget
void attachRootWidget(Widget rootWidget) { _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]', child: rootWidget ).attachToRenderTree(buildOwner, renderViewElement); }
attachRootWidget方法中,通過RenderObjectToWidgetAdapter的attachToRenderTree去建立頂層檢視的element(renderViewElement)。
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); }); } else { element._newWidget = this; element.markNeedsBuild(); } return element; }
這個方法就很有講究了,首先如果element是空,則呼叫createElement方法去建立,然後通過mount方法將其掛載到檢視樹上。
createElement
@override RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
createElement方法就是建立了一個RenderObjectToWidgetElement。由此我們知道,Flutter頂層檢視的element就是這個RenderObjectToWidgetElement。然後我們再來看它的mount方法。
mount
@override void mount(Element parent, dynamic newSlot) { assert(parent == null); super.mount(parent, newSlot); _rebuild(); }
void _rebuild() { try { _child = updateChild(_child, widget.child, _rootChildSlot); assert(_child != null); } catch (exception, stack) { final FlutterErrorDetails details = FlutterErrorDetails( exception: exception, stack: stack, library: 'widgets library', context: 'attaching to the render tree' ); FlutterError.reportError(details); final Widget error = ErrorWidget.builder(details); _child = updateChild(null, error, _rootChildSlot); } }
@protected Element updateChild(Element child, Widget newWidget, dynamic newSlot) { assert(() { if (newWidget != null && newWidget.key is GlobalKey) { final GlobalKey key = newWidget.key; key._debugReserveFor(this); } return true; }()); if (newWidget == null) { if (child != null) deactivateChild(child); return null; } if (child != null) { if (child.widget == newWidget) { if (child.slot != newSlot) updateSlotForChild(child, newSlot); return child; } if (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; }()); return child; } deactivateChild(child); assert(child._parent == null); } return inflateWidget(newWidget, newSlot); }
@protected 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); assert(() { _debugCheckForCycles(newChild); return true; }()); newChild._activateWithParent(this, newSlot); final Element updatedChild = updateChild(newChild, newWidget, newSlot); assert(newChild == updatedChild); return updatedChild; } } final Element newChild = newWidget.createElement(); assert(() { _debugCheckForCycles(newChild); return true; }()); newChild.mount(this, newSlot); assert(newChild._debugLifecycleState == _ElementLifecycle.active); return newChild; }
可以看到mount方法最終呼叫了inflateWidget方法。inflateWidget方法中,通過newWidget的createElement方法建立了子element並呼叫其mount方法。
這個newWidget是什麼呢?
_child = updateChild(_child, widget.child, _rootChildSlot);
這個是前面講到的頂層element RenderObjectToWidgetElement的_rebuild,可以看到newWidget相對應傳入的是widget.child,那麼這個widget又是什麼呢?我們繼續往前推,看一下RenderObjectToWidgetElement是如果構造的。
RenderObjectToWidgetElement(RenderObjectToWidgetAdapter<T> widget) : super(widget);
這個是它的建構函式,可以看到上文提到的widget就是通過建構函式傳進來的。
@override RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
這個是RenderObjectToWidgetAdapter構造RenderObjectToWidgetElement的方法。有次我們得知,上文提到的widget就是RenderObjectToWidgetAdapter。那麼updateChild中的widget.child就是RenderObjectToWidgetAdapter的child變數。
回到最開始的attachRootWidget方法:
void attachRootWidget(Widget rootWidget) { _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]', child: rootWidget ).attachToRenderTree(buildOwner, renderViewElement); }
非常清楚了,RenderObjectToWidgetAdapter的child變數就是我們runApp中傳進來的rootWidget,也就是例子中的MyApp。
總結一下
下面我們來總結一下上面的流程:
- Flutter通過runApp方法啟動整個應用。
- runApp中通過attachRootWidget方法建立頂層檢視的element,而這個element是RenderObjectToWidgetElement。
- 建立完畢之後呼叫element的mount方法掛載。
- mount方法最後,會呼叫runApp中傳進來的業務rootWidget的createElement方法建立element。
- 最後呼叫element的mount方法。
遍歷樹
上文最後我們得知,根檢視element的mount方法最終會呼叫業務根檢視的element的mount方法,那麼我們帶入我們的demo,業務根檢視MyApp是繼承自StatelessWidget。
abstract class StatelessWidget extends Widget { @override StatelessElement createElement() => StatelessElement(this); }
它的createElement方法建立了一個StatelessElement。
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; @override Widget build() => widget.build(this); @override void update(StatelessWidget newWidget) { super.update(newWidget); assert(widget == newWidget); _dirty = true; rebuild(); } }
StatelessElement繼承自ComponentElement,我們來看一下ComponentElement的mount方法。
@override void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); assert(_child == null); assert(_active); _firstBuild(); assert(_child != null); }
void _firstBuild() { rebuild(); }
void rebuild() { assert(_debugLifecycleState != _ElementLifecycle.initial); if (!_active || !_dirty) return; assert(() { if (debugOnRebuildDirtyWidget != null) { debugOnRebuildDirtyWidget(this, _debugBuiltOnce); } if (debugPrintRebuildDirtyWidgets) { if (!_debugBuiltOnce) { debugPrint('Building $this'); _debugBuiltOnce = true; } else { debugPrint('Rebuilding $this'); } } return true; }()); assert(_debugLifecycleState == _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); }
@override void performRebuild() { assert(() { if (debugProfileBuildsEnabled) Timeline.startSync('${widget.runtimeType}',arguments: timelineWhitelistArguments); return true; }()); assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true)); Widget built; try { built = build(); debugWidgetBuilderValue(widget, built); } catch (e, stack) { built = ErrorWidget.builder(_debugReportException('building $this', 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; assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false)); } try { _child = updateChild(_child, built, slot); assert(_child != null); } catch (e, stack) { built = ErrorWidget.builder(_debugReportException('building $this', e, stack)); _child = updateChild(null, built, slot); } assert(() { if (debugProfileBuildsEnabled) Timeline.finishSync(); return true; }()); }
最終呼叫了performRebuild方法,而在這個方法中:
_child = updateChild(_child, built, slot);
又回去呼叫前文提到的updateChild方法,這樣就做到了遍歷整個檢視樹,建立檢視了。
最後
最後,runApp中我們還有一個方法沒有提到:
void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() ..attachRootWidget(app) ..scheduleWarmUpFrame(); }
那就是scheduleWarmUpFrame,該方法在attachRootWidget之後,遍歷掛載完了整個檢視樹,通過scheduleWarmUpFrame方法去渲染,具體邏輯之後有機會再深究吧~