Flutter框架启动源码剖析

入口函数,其主要作用是注入给定的小控件并将其附加到屏幕上。

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

1.初始化一个widgetsBinding的全局单例
2.创建跟widget并添加到renderView上,在这个过程中完成Element树和RenderObject树的生成
3.执行渲染

WidgetsBinding 初始化

 static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance!;
  }

这里通过子类widgetsFlutterBinding实例化了一个widgetsBinding对象。但是这里widgetsFlutterBinding没有显示声明构造方法,因此我们查看它的父类构造方法实现

abstract class BindingBase {
  BindingBase() {
    developer.Timeline.startSync('Framework initialization');
    initInstances();
    initServiceExtensions();
    developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
    developer.Timeline.finishSync();
  }

可以看到,这里主要调用了initInstances()做了一些初始化操作,但是基类BindingBase自己的initInstances()是一个空的实现,因此要查看其他父类中的实现。这里我们需要注意到widgetsFlutterBinding类的继承情况

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
}

因此,这里initInstances()方法会调用最外侧的widgetsBinding中的具体实现。检查代码可知,混入的每一个类中都实现了initInstances方法,并且还调用了super.initInstances(),这样一来,就会从最外侧的widgetsBinding开始,依次链式调用每一个混入类中的initInstances()方法,完成各个Binding的初始化。

接下来我们先进入widgetsBinding中的initInstances实现,逻辑不多,主要是创建了BuildOwner对象,并给window设置了一些回调函数

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;

    assert(() {
      _debugAddStackFilters();
      return true;
    }());

    _buildOwner = BuildOwner();
    buildOwner!.onBuildScheduled = _handleBuildScheduled;
    window.onLocaleChanged = handleLocaleChanged;
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
  }

再看看RendererBinding中的initInstances实现,这里创建了PipelineOwner对象,也给window设置了另一些回调函数

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    window
      ..onMetricsChanged = handleMetricsChanged
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
      ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
      ..onSemanticsAction = _handleSemanticsAction;
    // 创建一个根RenderObject
    initRenderView();
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    // 注册presistentCallbacks回调
addPersistentFrameCallback(_handlePersistentFrameCallback);
    initMouseTracker();
    if (kIsWeb) {
      addPostFrameCallback(_handleWebFirstFrame);
    }
  }

这里我们可以查看一些跟RenderObject的创建

void initRenderView() {
    assert(!_debugIsRenderViewInitialized);
    assert(() {
      _debugIsRenderViewInitialized = true;
      return true;
    }());
    renderView = RenderView(configuration: createViewConfiguration(), window: window);
    renderView.prepareInitialFrame();
  }

这里其他Binding的初始化先略过,我们查看一下出现多次的window是什么东西。根据官方的解释,window是Flutter框架链接宿主操作系统的接口

我们可以发下,混入的那些Binding基本上都是监听并处理window对象的一些事件,然后将这些事件根据Flutter框架层的模型进行包装、抽象最后分发。

查看官方文档可知,widgetsFlutterBinding是将Framework与Flutter引擎榜单的胶水。BindingBase相当于所以Binding的基类,定义了一些公共的行为。

*GestureBinding:榜单Framework手势子系统,是Framework事件模型与底层事件的绑定入口
*ServiceBinding:用于绑定平台消息通道(message channel),主要处理原生和Flutter通信
*SchedulerBinding:监听刷新事件,绑定Framework绘制调度子系统
*PaintingBinding:绑定绘制库,主要用于处理图片缓存
*SemanticsBinding:语义化层与Flutter引擎的桥梁,主要是辅助功能的底层这次
*RendererBinding:是渲染树与Flutter引擎的桥梁
*WidgetsBinding:它是Flutter Widget层与引擎的桥梁

构建Element和RenderObject树

再看runApp下方法中scheduleAttachRootWidget方法,它实际上调用了如下方法,它完成了Widget、RenderObjectElement三者的关联。具体代码在attachToRenderTree方法中

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

BuildOwner是widget框架的管理器类,该类跟着哪些widgets需要重建,并处理其他使用于widgets树的任务,如管理树的非活动元素列表,并在调试时的热重载期间在必要时触发“reassemble”命令。

  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);
      });
      // This is most likely the first time the framework is ready to produce
      // a frame. Ensure that we are asked for one.
      SchedulerBinding.instance!.ensureVisualUpdate();
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element!;
  } 

首先执行 element 为null, 所以执行creteElement方法创建Element,而真正执行构建树的操作是owner.buildScope方法。这个方法首先执行传入的回调,即执行element.mount(null,null)方法

 @override
  void mount(Element? parent, dynamic newSlot) {
    assert(parent == null);
    super.mount(parent, newSlot);
    _rebuild();
  }

mount 方法会首先调用父类的mount方法,即调用到RenderObjectElement类的 mount 方法如下,在此处构建RenderObject对象,同时调用attachRenderObject方法生成RenderObject

 @override
  void mount(Element? parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    // ...
    _renderObject = widget.createRenderObject(this);
    attachRenderObject(newSlot);
    _dirty = false;
  }

再看上面的_rebuild方法,其中主要调用了updateChild方法,updateChild方法的主要作用是用给定的新配置(widge)更新给定的子元素。这里可以关注catch中的代码,正是此处社鞥从了Flutter中常见的红屏报错页面的ErrorWidget

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: ErrorDescription('attaching to the render tree'),
      );
      FlutterError.reportError(details);
      final Widget error = ErrorWidget.builder(details);
      _child = updateChild(null, error, _rootChildSlot);
    }
  }

updateChilid 方法移除注释与断言后如下,newWidgetchild的值存在几种不同的组合情况, 当首次进入时,会执行inflateWidget,为根Widget创建一个新的element。

@protected
  Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }
    final Element newChild;
    if (child != null) {
      bool hasSameSuperclass = true;
            assert(() {
        final int oldElementClass = Element._debugConcreteSubtype(child);
        final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
        hasSameSuperclass = oldElementClass == newWidgetClass;
        return true;
      }());
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && 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;
        }());
        newChild = child;
      } else {
        deactivateChild(child);
        assert(child._parent == null);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }
    return newChild;
  }

inflateWidget方法删除断言如下,可以看到,这里创建了newChild后又调用了mount方法,依次递归变了Widget树的子节点,不断为Widget创建对应的Element、RenderObject, 以完成“三棵树”的构建与关联。

@protected
  Element inflateWidget(Widget newWidget, dynamic newSlot) {
    final Key? key = newWidget.key;
    if (key is GlobalKey) {
      final Element? newChild = _retakeInactiveElement(key, newWidget);
      if (newChild != null) {
        newChild._activateWithParent(this, newSlot);
        final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
        return updatedChild!;
      }
    }
    final Element newChild = newWidget.createElement();
    newChild.mount(this, newSlot);
    return newChild;
  }

执行渲染

接下runApp 调用scheduleWarmUpFrame进行了第一次绘制,具体实现在SchedulerBinding中。 该方法会锁定事件调度直到完成为止,即在这次绘制完成之前都不会接收event(触摸事件等)。
该方法中主要调用了handleBeginFrame()handleDrawFrame()

void scheduleWarmUpFrame() {
    if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
      return;

    _warmUpFrame = true;
    Timeline.startSync('Warm-up frame');
    final bool hadScheduledFrame = _hasScheduledFrame;
// 在这里使用计时器来确保microtasks在两者之间刷新 
    Timer.run(() {
      handleBeginFrame(null);
    });
    Timer.run(() {
      handleDrawFrame();
      resetEpoch();
      _warmUpFrame = false;
      if (hadScheduledFrame)
        scheduleFrame();
    });

    // 锁定事件,使触摸事件在等到预定帧结束前不会自行插入
    lockEvents(() async {
      await endOfFrame;
      Timeline.finishSync();
    });
  }

其中handleBeginFrame方法用于渲染前的一些准备,主要是处理transientCallbacks回调,也就是触发动画相关的Ticker回调。 而真正处理渲染的是handleDrawFrame方法,它由引擎调用以生成新帧。

void handleDrawFrame() {
    Timeline.finishSync(); // end the "Animate" phase
    try {
      // 处理persistentCallbacks回调
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (final FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp!);

      // 处理postFrameCallbacks回调
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
      final List<FrameCallback> localPostFrameCallbacks =
          List<FrameCallback>.from(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      for (final FrameCallback callback in localPostFrameCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp!);
    } finally {
      _schedulerPhase = SchedulerPhase.idle;
      Timeline.finishSync(); // end the Frame
      _currentFrameTimeStamp = null;
    }
  }

根据官方文档的解释,有一下三个回调队列

  • transientCallbacks:由系统的Window.onBenginFrame回调触发,一般用于处理动画的回调。
  • persistentCallbacks:由系统的Window.onDrawFrame回调触发。用久callback,一经注册无法移除,由widgetsBinding.instance.addPersitentFrameCallback()注册,主要产后护理布局与绘制工作。
  • postFrameCallbacks:在persistentCallbacks回调之后,Window.onDrawFrame返回之前执行。它只会调用一次,调用后就会被系统移除。可由widgetsBinding.Instance.addPostFrameCallback()注册,通常用于State的更新。

可由看的,这里的代码逻辑主要就是处理persistentCallbackspostFrameCallbacks的回调,而框架正好又在 RenderBinding 初始化时注册了一个persistentCallbacks回调

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    /// ...
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    /// ....
  }

  void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
    _scheduleMouseTrackerUpdate();
  }


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

以上代码中需要特别注意,回调中并不是直接调用RenderBinding中的drawFrame()方法,而是依据widgetsFlutterBinding混入的顺序调用。
根据Dart mixin 语法,混入多个类后,调用同名方法时,是从最外层开始调用。可以看到,这里是调用的widgetsBinding.drawFrame()

/// [WidgetsBinding]
/// 抽取构建和渲染管道以生成一帧。
/// 这个方法被handleDrawFrame调用,当需要布局和绘制一帧时,引擎会自动调用这个方法
  void drawFrame() {
    TimingsCallback? firstFrameCallback;
    if (_needToReportFirstFrame) {
      firstFrameCallback = (List<FrameTiming> timings) {
        if (!kReleaseMode) {
          developer.Timeline.instantSync('Rasterized first useful frame');
          developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
        }
        SchedulerBinding.instance!.removeTimingsCallback(firstFrameCallback!);
        firstFrameCallback = null;
        _firstFrameCompleter.complete();
      };
      // 只有再调用 [window.render] 时才会被调用
      // 当 [sendFramesToEngine] 在帧中被设置为false时,它将不会被调用,我们需要删除回调
      SchedulerBinding.instance!.addTimingsCallback(firstFrameCallback!);
    }

    try {
      if (renderViewElement != null)
        buildOwner!.buildScope(renderViewElement!);
      super.drawFrame();
      buildOwner!.finalizeTree();
    } finally {
     // assert
    }
    if (!kReleaseMode) {
      if (_needToReportFirstFrame && sendFramesToEngine) {
        developer.Timeline.instantSync('Widgets built first useful frame');
      }
    }
    _needToReportFirstFrame = false;
    if (firstFrameCallback != null && !sendFramesToEngine) {
      // 这个帧是延时的,并不是第一个发送给引擎的应该报告的帧
      _needToReportFirstFrame = true;
      SchedulerBinding.instance!.removeTimingsCallback(firstFrameCallback!);
    }
  }

实际上,这里的主要逻辑是try中的代码

try {
      if (renderViewElement != null)
        // 将被标记为dirty的Element进行rebuild()
        buildOwner!.buildScope(renderViewElement!);
        // 调用父类的drawFrame,这里实际上调用的是 RenderBinding中的drawFrame()方法
        super.drawFrame();
        // 通过卸载任何不再活动的元素来完成元素构建过程
        buildOwner!.finalizeTree();
    } 

其中通过调用super.drawFrame()又再次回到RenderBinding中的drawFrame()方法

//更新需要计算布局的谊染对象。在这个阶段,计算每个渲染对象的大小和位置。
pipelineOwner.flushLayout();
//更新具有dirty compositing bits的所有渲染对象。在此阶段, 每个渲染对象将了解其子对象是否需要合成。
//
在选择如何实现视觉效果(例如剪裁)时,在绘画阶段将使用此信息。
pipeline0wner.flushCompositingBits();
//访问需要绘制的渲染对象进行绘制
pipeline0wner.flushPaint();
//该方法将画好的layer传给引擎, 该方法调用结束后,屏幕就会显示内容
renderView.compositeFrame();
//如果启用了语义,则该方法将编译渲染对象的语义,
传给系统用于辅助功能,如搜索等。
pipelineOwner.flushSemantics();

小结

根据官方drawFrame 文档的描述,绘制过程经历一下十个阶段:
1、动画阶段:handleBeginFrame方法是用window.onBeginFrame注册的,按注册顺序调用所有用scheduleFrameCallback注册的transientCallbacks, 其中包括所有驱动AnimationController对象的Ticker实例,也就是说此时所有的活动Animation对象的tick在此刻回调。
2、Microtask: 在handleBeginFrame放回后,任何被transientCallBacks安排的微任务都会被执行。这通常包括自Ticker和AnimationController的完成该帧的Future回调。
3、构建阶段:重建widget树中所有的dirty元素(见 State.build),查阅State.setState了解更多关于标记一个widget dirty的构建细节,有关此步骤的更新信息,请参见BuildOwner。
4、布局阶段:系统中的所有标记dirty的 RenderObject都会被布局。(参见 RenderObject.performLayout)。 请参阅RenderObject.markNeedsLayout, 了解关于标记一个对象以进行布局更多的细节。)
5、合成位阶段:更新任何标记dirty的RenderObject对象上的 compositing bits。(请参见RenderObject.markNeedsCompositingBitsUpdate)。
6、绘制阶段:系统中的所有标记dirty的 RenderObject都会被重新绘制,这将生产Layer树。 (请参阅RenderObject.markNeedsPaint,以获取有关将对象标记为dirty的更新详细信息)。
7、合成阶段:图层将被转化为一个Scene并发送给GPU。
8、语义阶段:系统中所有标记dirty的RenderObject的语义都会被更新(见 RenderObject.assembleSemanticsNode)。这将生产SemanticsNode树。请参阅RenderObject.markNeedsSemanticsUpdate以了解关于标记一个对象为dirty的语义的细节。
9、widget层的完成阶段:widget树已完成,这将导致在从widget树中删除的任何对象上调用State.dispose。 请参见``BuildOwner.finalizeTree````
10、调度层的最终化阶段:在 drawFrame返回后, handleDrawFrame会调用postFrameCallbacks回调(用 addPostFrameCallback注册的)

总结

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容