Flutter啟動流程初探

PHP技術大全 / 2019-03-15 15:03:17

最近開始研究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(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget
).attachToRenderTree(buildOwner, renderViewElement);
}

attachRootWidget方法中,通過RenderObjectToWidgetAdapter的attachToRenderTree去創建頂層視圖的element(renderViewElement)。

attachToRenderTree

RenderObjectToWidgetElement attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement 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 createElement() => RenderObjectToWidgetElement(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 widget) : super(widget);

這個是它的構造函數,可以看到上文提到的widget就是通過構造函數傳進來的。

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

這個是RenderObjectToWidgetAdapter構造RenderObjectToWidgetElement的方法。有次我們得知,上文提到的widget就是RenderObjectToWidgetAdapter。那么updateChild中的widget.child就是RenderObjectToWidgetAdapter的child變量。

回到最開始的attachRootWidget方法:

void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget
).attachToRenderTree(buildOwner, renderViewElement);
}

非常清楚了,RenderObjectToWidgetAdapter的child變量就是我們runApp中傳進來的rootWidget,也就是例子中的MyApp。

總結一下

下面我們來總結一下上面的流程:

  1. Flutter通過runApp方法啟動整個應用。

  2. runApp中通過attachRootWidget方法創建頂層視圖的element,而這個element是RenderObjectToWidgetElement。

  3. 創建完畢之后調用element的mount方法掛載。

  4. mount方法最后,會調用runApp中傳進來的業務rootWidget的createElement方法創建element。

  5. 最后調用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方法去渲染,具體邏輯之后有機會再深究吧~

干貨分享

敬請關注“PHP技術大全”微信公眾號


青海快三开奖信息