Flutter源码分析系列(一):runApp方法究竟做了什么

入口

我们从一切的起点main.dart说起,这里我们一定会调用runApp方法,这个方法可以说是Flutter程序的入口:

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

传入的Widget即是我们需要显示的界面Widget。
继续分析源码,其中WidgetsFlutterBinding是一个单例类,WidgetsFlutterBinding继承了BindingBase并且with了大量的mixin,可以说这个类就是将Widget架构和Flutter底层Engine连接的桥梁。
ensureInitialized()负责初始化以及返回实例,该方法会进行大量初始化操作。

Widget到Element到RenderObject的流程

attachRootWidget

初始化完成拿到实例后接下来会调用attachRootWidget方法,该方法完成了Widget到Element到RenderObject的整个关联过程,代码如下:

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

实际上就是将传入的Widget包装到RenderObjectToWidgetAdapter,它继承自RenderObjectWidget,负责将Widget、Element、RenderObject三者关联起来,其中的RenderObject对应前面初始化操作中创建的renderView。
其中renderView_renderViewElement为WidgetsFlutterBinding的成员,可以看出每个app只存在一个renderViewElement和renderView,并且一一对应。

attachToRenderTree

继续看attachToRenderTree方法的实现:

RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
    if (element == null) {
        owner.lockState(() {
            // 创建根Element,RenderObjectToWidgetElement
            element = createElement();
            assert(element != null);
            element.assignOwner(owner);
        });
        owner.buildScope(element, () {
            // 这里会根据WidgetTree构建ElementTree
            element.mount(null, null);
        });
    } else {
        element._newWidget = this;
        element.markNeedsBuild();
    }
    return element;
}

该方法负责创建根Element,即RenderObjectToWidgetElement,并且将Element与Widget进行关联,即创建出WidgetTree对应的ElementTree。如果Element已经创建过了,则将根Element中关联的Widget设为新的,由此可以看出Element只会创建一次,后面会进行复用。

mount

如果Element是首次创建,会调用mount,该方法由父类到子类会做下面几件事:

Element
将该Element标记为active的,设置parent为null,slot为null,depth为1,如果对应的widget的key为GlobalKey,在这里进行注册,即将Key与Element进行关联,设置inheritedWidgets,用于由上至下传递数据。

RenderObjectElement
创建对应的RenderObject,并attach到对应的slot位置。

RootRenderObjectElement
没做什么事,只是assert一下parent和slot为null。

RenderObjectToWidgetAdapter
调用_rebuild()方法创建ElementTree。

如果不是首次创建,这种情况一般是多次调用了runApp方法,则更新对应的跟Widget,并调用markNeedsBuild()方法准备重建ElementTree。

rebuild

下面先看下_rebuild()的代码:

void _rebuild() {
    try {
        // 实际上是调用updateChild更新ElementTree
        _child = updateChild(_child, widget.child, _rootChildSlot);
        assert(_child != null);
    } catch (exception, stack) {
        // 这里就是红屏产生的地方
        final FlutterErrorDetails details = new FlutterErrorDetails(
            exception: exception,
            stack: stack,
            library: 'widgets library',
            context: 'attaching to the render tree'
        );
        // 这里打印了错误栈
        FlutterError.reportError(details);
        // 这里就是创建了红屏的Widget,显示在屏幕上
        final Widget error = ErrorWidget.builder(details);
        _child = updateChild(null, error, _rootChildSlot);
    }
}

updateChild

实际上该方法只执行了updateChild(),该方法至关重要,ElementTree的生成主要就在方法中实现,我们来细看一下代码,注意代码中添加的注释:

// child表示要更新的Element,newWidget表示对应Element的Widget,newSlot用来标识Element的所在位置,返回该位置对应的新Element
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    assert(() {
        // Debug下保证一个GlobalKey只对应一个Widget
        if (newWidget != null && newWidget.key is GlobalKey) {
            final GlobalKey key = newWidget.key;
            key._debugReserveFor(this);
        }
        return true;
    }());
    if (newWidget == null) {
        // 如果newWidget为空,child非空表示需要移除旧Element
        if (child != null)
            deactivateChild(child);
        // 将此Element的位置设为null
        return null;
    }
    if (child != null) {
        // 都非空且是相同Widget,更新位置标识即可
        if (child.widget == newWidget) {
            if (child.slot != newSlot)
                updateSlotForChild(child, newSlot);
            // 更新后返回原Element
            return child;
        }
        // 若不是相同Widget则判断是否有相同的类型和相同的Key,是的话则更新Widget信息到Element
        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;
            }());
            // 更新后返回原Element
            return child;
        }
        // 若不符合更新的要求,则抛弃掉原Element,抛弃掉的Element会被回收到`_inactiveElements`列表中,不会立即被销毁
        deactivateChild(child);
        assert(child._parent == null);
    }
    // 其他情况下需要创建新的Element
    return inflateWidget(newWidget, newSlot);
}

inflateWidget

继续看inflateWidget方法:

@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {
    assert(newWidget != null);
    final Key key = newWidget.key;
    if (key is GlobalKey) {
        // 先使用key去被回收的列表中看看是否有可以复用的Element
        final Element newChild = _retakeInactiveElement(key, newWidget);
        if (newChild != null) {
            assert(newChild._parent == null);
            assert(() { _debugCheckForCycles(newChild); return true; }());
            newChild._activateWithParent(this, newSlot);
            // 找到后就复用被回收的Element,并且更新它的Child
            final Element updatedChild = updateChild(newChild, newWidget, newSlot);
            assert(newChild == updatedChild);
            return updatedChild;
        }
    }
    // 没有可以复用的Element了,只能创建新的
    final Element newChild = newWidget.createElement();
    assert(() { _debugCheckForCycles(newChild); return true; }());
    // mount新的Element
    newChild.mount(this, newSlot);
    assert(newChild._debugLifecycleState == _ElementLifecycle.active);
    return newChild;
}

这里新创建的Element继续调用mount,于是又会触发新一轮的updateChild,最终对应WidgetTree的整个ElementTree就构建完成了。

开始渲染

回到最初的入口代码调用,最后一行会调用WidgetsFlutterBinding实例的scheduleWarmUpFrame进行第一次绘制,该方法的实现在SchedulerBinding类中。这次draw完成之前都不会接收各种event(触摸事件等等),其他scheduledFrame会被延迟到该次draw完成之后。
该方法主要调用了handleBeginFrame()handleDrawFrame()两个方法,在看这两个方法之前首先了解一下Frame和FrameCallbacks的概念:

Frame

Frame即每一帧的绘制过程,engine通过VSync信号不断地触发Frame的绘制,实际上就是调用SchedulerBinding类中的_handleBeginFrame()_handleDrawFrame()这两个方法,这个过程中会完成动画、布局、绘制等工作。

FrameCallbacks

Frame绘制期间,有三个callbacks列表会被调用,这三个列表是SchedulerBinding类中的成员,它们的调用顺序如下:

  1. transientCallbacks,由Ticker触发和停止,一般用于动画的回调。
  2. persistentCallbacks,永久callback,一经添加无法移除,由WidgetsBinding.instance.addPersitentFrameCallback()注册,这个回调处理了布局与绘制工作。
  3. postFrameCallbacks,只会调用一次,调用后会被系统移除,可由WidgetsBinding.instance.addPostFrameCallback()注册,该回调一般用于State的更新。

handleBeginFrame

接下来看下handleBeginFrame方法的代码,这里去掉了assert以及profile相关的代码:

try {
  // TRANSIENT FRAME CALLBACKS
  Timeline.startSync('Animate', arguments: timelineWhitelistArguments);
  _schedulerPhase = SchedulerPhase.transientCallbacks;
  final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
  _transientCallbacks = <int, _FrameCallbackEntry>{};
  callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
    if (!_removedIds.contains(id))
      _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
  });
  _removedIds.clear();
} finally {
  _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
}

实际上这里执行的操作不多,就是执行了transientCallbacks。

handleDrawFrame

这里进行persistentCallbacks和postFrameCallbacks的回调,主要的操作都发生在这里,详情见下一节。

真正的渲染操作

系统只在persistentCallbacks注册了一个回调,实际上为RenderBinding类中的drawFrame()方法以及其子类WidgetsBinding类中的drawFrame()方法:

RendererBinding

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

** WidgetsBinding **

@override
void drawFrame() {
    try {
        if (renderViewElement != null)
            buildOwner.buildScope(renderViewElement);
        super.drawFrame();
        buildOwner.finalizeTree();
    } finally {
        ...
    }

}

先看子类的实现,首先调用的是buildOwner.buildScope(),该方法会将被标记为dirty的Element进行rebuild()(调用过setState()或正在进行动画的Widget此时会是dirty的)。然后调用父类的super.drawFrame(),最后调用buildOwner.finalizeTree()还记得之前回收被抛弃的Element的列表_inactiveElements吗?列表中的Element们在这里会被彻底清除掉,接下来进入父类的drawFrame()方法继续分析。

pipelineOwner.flushLayout()

这个方法看名字就能猜到,这就是进行布局的地方了:

void flushLayout() {
    ...
    while (_nodesNeedingLayout.isNotEmpty) {
        final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
        _nodesNeedingLayout = <RenderObject>[];
        for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
            if (node._needsLayout && node.owner == this)
                node._layoutWithoutResize();
        }
    }
    ...
}

代码删去了assert和profile相关的内容,当RenderObject的宽高等布局相关的属性被set时(通过更改Widget的属性),它会被添加到_nodesNeedingLayout列表中,以标记为需要重新进行layout。这里遍历了该列表,并调用_layoutWithoutResize()进行布局。

flushCompositingBits()

  void flushCompositingBits() {
    ...
    _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
    for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
      if (node._needsCompositingBitsUpdate && node.owner == this)
        node._updateCompositingBits();
    }
    _nodesNeedingCompositingBitsUpdate.clear();
    ...
  }

该方法用于判断RenderObject是否拥有自己的layer,如果该状态变化了,就会将该RenderObject标记为需要进行重绘的,然后在下面flushPaint()方法中进行重绘。

flushPaint()

void flushPaint() {
    ...
    final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
    _nodesNeedingPaint = <RenderObject>[];
    // Sort the dirty nodes in reverse order (deepest first).
    for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
        if (node._needsPaint && node.owner == this) {
            if (node._layer.attached) {
                PaintingContext.repaintCompositedChild(node);
            } else {
                node._skippedPaintingOnLayer();
            }
        }
    }
    ...
}

该方法就是进行绘制的地方,可以看出它不是重绘了所有RenderObject,而是只重绘了被标记为dirty的RenderObject,这些RenderObject会调用engine下的skia库进行绘制。

compositeFrame()

void compositeFrame() {
    ...
    final ui.SceneBuilder builder = new ui.SceneBuilder();
    layer.addToScene(builder, Offset.zero);
    final ui.Scene scene = builder.build();
    if (automaticSystemUiAdjustment)
        _updateSystemChrome();
    ui.window.render(scene);
    scene.dispose();
    ...
}

这个方法将画好的layer传给engine,该方法调用结束之后,手机屏幕就会显示出内容了。

flushSemantics()

Semantics用于将一些Widget的信息传给系统用于搜索、App内容分析等场景,这与Flutter绘制流程关系不大,这里略过。

End

到此整个runApp方法就分析完了,回顾一下整个过程,总结来说就是根据传入的Widget生成对应的ElementTree和RenderTree,之后开始进行首帧的布局和绘制。其中Widget用来描述页面的属性,这个对象是十分轻量级的且是不可变的,同一个Widget可以描述多个Element的配置,Element同时持有了Widget和RenderObject,它汇总了所有的属性信息,重绘时只将需要修改的部分通知到RenderObject。对于普通开发者,只需要关注最上层的Widget就可以了,十分简单高效。
关于Element和RenderObject的深入探索请期待下一篇文章。

    原文作者:超丶赛亚叼
    原文地址: https://www.jianshu.com/p/a9364f778bf8
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞