美文网首页Flutter圈子FlutterFlutter
Flutter源码解析之runApp方法究竟做了什么

Flutter源码解析之runApp方法究竟做了什么

作者: 超丶赛亚叼 | 来源:发表于2018-09-13 13:16 被阅读548次

    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的深入探索请期待下一篇文章。

    相关文章

      网友评论

      本文标题:Flutter源码解析之runApp方法究竟做了什么

      本文链接:https://www.haomeiwen.com/subject/xcswgftx.html