Flutter 启动流程源码解析

一: 概述

学Flutter印象最深的一句话是:万物皆Widget。 但Widget是不可变的,页面发生变化时,Widget一定会被重新构建。 在我们理解中对象的创建带来的性能损耗是非常巨大的,这与Flutter宣传的高性能不符合。这其中的缘由是,Widget并不是“逻辑处理”与“视图渲染”对象,它是一个非常轻量级的描述类。 因此Widget的批量创建销毁对Flutter的性能损耗是非常小的。

Flutter会根据Widget创建出唯一的Element,然后会创建出一个继承自Element的唯一RenderObjet。Widget负责页面描述,Element负责信息存储中枢调度,RenderObjet是真正的绘制实例。 由于他们结构类似于HTML中的DOM树,因此形象的将其称为Flutter的三棵树。

二: 启动流程图

本文将从源码解析Flutter的启动流程,顺便标记三棵树的创建时机。源码均复制自 Stable 1.17.5, 非核心代码有删减。下图是我根据源码调用顺序绘制的简单流程图。

三: 启动详细流程

3.1 main

void main() => runApp(MyApp());

3.2 runApp

[flutter/lib/src/widgets/binding.dart]

/// 把传入 widget 充满全屏
///
/// 传入的 widget 会被强制充满全屏,若想调整 widget 对齐方式,可考虑[Align]、[Center]等
///
/// 再次调用[runApp]将会把屏幕之前的root widget替换为传入的widget。
/// 新的 widget 树会与之前的 widget树进行比较,任何差异都会被应用到底层的渲染树上
/// 类似于调用 [State.setState] 后 [StatefulWidget] 重新构建
///
/// 如果需要,使用[WidgetsFlutterBinding]初始化绑定。
///
/// 参见:
///
///  * [WidgetsBinding.attachRootWidget], widget 结构创建根 widget
///  * [RenderObjectToWidgetAdapter.attachToRenderTree], 元素结构创建根元素。
///  * [WidgetsBinding.handleBeginFrame], _transientCallbacks 的函数回调
///     以确保小部件、元素和渲染树都构建好了。
void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

从这里可大致推测出,runApp做了三件事:

  • 初始化 WidgetsFlutterBinding
  • 将界面App加载到 flutter 树中
  • 计算面积,开始绘制

3.3 ensureInitialized

[flutter/lib/src/widgets/binding.dart]

/// 给程序做一个功能集的绑定
///
/// 粘合framework 和 引擎层的胶水
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}

这是一个功能集(flutter使用 mixin语法,实现多继承的效果)包含:

  • GestureBinding:提供了window.onPointerDataPacket 回调,绑定Framework手势子系统,是Framework事件模型与底层事件的绑定入口。
  • ServicesBinding:提供了window.onPlatformMessage 回调, 用于绑定平台消息通道(message channel),主要处理原生和Flutter通信。
  • SchedulerBinding:提供了window.onBeginFrame和window.onDrawFrame回调,监听刷新事件,绑定Framework绘制调度子系统。
  • PaintingBinding:绑定绘制库,主要用于处理图片缓存。
  • SemanticsBinding:语义化层与Flutter engine的桥梁,主要是辅助功能的底层支持。
  • RendererBinding: 提供了window.onMetricsChanged 、window.onTextScaleFactorChanged 等回调。它是渲染树与Flutter engine的桥梁。
  • WidgetsBinding:提供了window.onLocaleChanged、onBuildScheduled 等回调。它是Flutter widget层与engine的桥梁。

3.4 scheduleAttachRootWidget

[flutter/lib/src/widgets/binding.dart]

/// 生成一个计时器,同时附加到根 widget 上.
///
/// [runApp]时配置,若是想同步构建 widget 树,需使用[attachRootWidget]
void scheduleAttachRootWidget(Widget rootWidget) {
  Timer.run(() {
    attachRootWidget(rootWidget);
  });
}

3.4.1 attachRootWidget

[flutter/lib/src/widgets/binding.dart]

/// 将 widget 附加到 [renderViewElement]上(renderViewElement 是 Element 树的根元素)
///
/// [runApp] 时配置生成 widget 树
///
/// 参见: [RenderObjectToWidgetAdapter.attachToRenderTree].
void attachRootWidget(Widget rootWidget) {
  //构造了一个RenderObjectToWidgetAdapter实例,它继承于Widget,所以它本质就是Widget。然后我们写的Widget(也就是rootWidget变量)作为child参数传进去,而Render树的根结点RenderView也作为container参数传进去。
  //它就是我们Widget树的根结点,我们写的Widget就是挂在它下面,它对应的RenderObject就是RenderView。

  // _renderViewElement是element的根树,首次调用[runApp]时初始化
  _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
    container: renderView,
    debugShortDescription: '[root]',
    child: rootWidget,
  ).attachToRenderTree(buildOwner, renderViewElement);
  // buildOwner负责管理 Widget 树的构建 
  // 初始化[_buildOwner]必须在[initInstances]方法中完成,
  // 因为它需要 [ServicesBinding] 设置 [defaultBinaryMessenger] 实例。
}

3.4.2 attachToRenderTree

[flutter/lib/src/widgets/binding.dart]

  /// 将当前 widget 充满屏幕,返回 element 会作为 [Element] 树的子树
  ///
  /// If `element` is null, 该方法会创建一个 element . else 传入 element 将有会替换该 widget。
  ///
  /// 用于[runApp]引导应用程序
  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
    if (element == null) {
      owner.lockState(() {   //lockState,在下面代码执行过程中,禁止调用setState方法
        element = createElement(); //
        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;
  }
3.4.2.1 createElement
@override
RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);

  /// 创建 [RenderObject]托管的 节点
  ///
  /// 创建的[RenderObject]不会添加到 render 树中,需要调用  
  ///[RenderObjectToWidgetAdapter.attachToRenderTree] 添加
  RenderObjectToWidgetElement(RenderObjectToWidgetAdapter<T> widget) : super(widget);
3.4.2.2 buildScope

[flutter/lib/src/widgets/framework.dart]

/// 为更新的 widget 树建立一个作用域,并调用给定的' callback '(如果有的话)
/// 然后,使用 [scheduleBuildFor] 按深度顺序构建所有标记为dirty的元素
///
/// 这种机制可以防止构建方法过渡性地要求其他构建方法运行,从而可能导致无限循环
///
/// 脏列表在`callback'返回之后被处理,并使用[scheduleBuildFor]按深度顺序构建所有标记为脏的元素
/// 如果在此方法运行时将元素标记为脏元素,则此元素必须比“ context”节点深,并且比任何先前构建的节点深
///

/// 要在不执行任何其他工作的情况下刷新当前脏列表,可以调用此函数而无需回调
/// framework会在每帧绘制中调用 [WidgetsBinding.drawFrame]
///
/// 一次只能激活一个[buildScope]。
///
/// 每个[buildScope]都有各自的[lockState]作用域。
///
/// 若想在每次调用此方法时打印控制台消息,将[debugPrintBuildScope]设置为true。
/// 该方法在调试 widgets 未被标记为dirty或过于频繁地被标记为dirty的问题时非常有用。
void buildScope(Element context, [ VoidCallback callback ]) {
    if (callback == null && _dirtyElements.isEmpty)
        return;
    Timeline.startSync('Build', arguments: timelineWhitelistArguments);
    try {
        _scheduledFlushDirtyElements = true;
        if (callback != null) {
            Element debugPreviousBuildTarget;
            _dirtyElementsNeedsResorting = false;
            try {
                callback(); //回调mount()
            }
            ...
        }
        _dirtyElements.sort(Element._sort);
        _dirtyElementsNeedsResorting = false;
        int dirtyCount = _dirtyElements.length;
        int index = 0;
        while (index < dirtyCount) {
            try {
                _dirtyElements[index].rebuild(); //针对脏元素执行rebuild操作
            }
            ...
        }
    } finally {
        ...
        Timeline.finishSync();
    }
}
3.4.2.3 mount

[flutter/lib/src/widgets/binding.dart]

@override
void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _rebuild();
}
3.4.2.3.1 mount

[flutter/lib/src/widgets/framework.dart]

/// 将这个元素添加到给定父节点的给定位置中。
///
/// 首次将新创建的元素添加到树时,framework 调用此函数。使用此方法初始化依赖于拥有父类的状态。
/// 独立于父类的 State 更容易在构造函数中初始化。
///
/// 此方法将元素从“initial(初始)” 生命周期状态转换为 “active (活动)” 生命周期状态。
///
/// 重写此方法的子类可能也要重写[update], [visitChildren], 
/// [RenderObjectElement.insertRenderObjectChild],
/// [RenderObjectElement.moveRenderObjectChild], 
/// [RenderObjectElement.removeRenderObjectChild].
@mustCallSuper
void mount(Element parent, dynamic newSlot) {
  _parent = parent;
  _slot = newSlot;
  _depth = _parent != null ? _parent.depth + 1 : 1;
  _active = true;
  if (parent != null) // Only assign ownership if the parent is non-null
    _owner = parent.owner;
  final Key key = widget.key;
  if (key is GlobalKey) {
    key._register(this);
  }
  _updateInheritance();
}
3.4.2.3.2 mount

[flutter/lib/src/widgets/framework.dart]

/// 实现类重写mount
  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this);//通过Elemet生成 RenderObject树
    attachRenderObject(newSlot);
    _dirty = false;
  }
3.4.2.4 _rebuild

[flutter/lib/src/widgets/binding.dart]

void _rebuild() {
  try {
    _child = updateChild(_child, widget.child, _rootChildSlot);
  } 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);
  }
}
3.4.2.5 updateChild

[flutter/lib/src/widgets/framework.dart]

/// 用给定的新配置更新给定的子节点。
///
/// 此方法是 widgets 系统的核心。每次根据更新后的配置 “添加”、“更新”、“删除子节点”时都会调用它。
///
/// `newSlot`参数指定此元素的 [slot](节点槽点) 的新值。
///
/// 若`child`为null,而`newWidget`不为null,则需要为其创建一个新元素,
/// 并使用newWidget配置一个[Element]。
///
/// 若`newWidget`为null,而`child`不为null,则需将其删除,因为它没有配置信息了。
///
/// 若两个值都不为 null,则需要将 'child' 的配置更新为 'newWidget' 提供的新配置。
/// 如果可以将“ newWidget”提供给现有的子节点(由[Widget.canUpdate]确定),则将其给定。
/// 否则,需要丢弃旧的子节点,并为新配置创建一个新的子节点。
///
/// 如果两者都为null,则说明没有也不会有子节点了,因此啥也不干
///
/// [updateChild]方法如果必须创建一个子节点,则返回一个新的子节点;
/// 如果必须更新子节点,则返回传入的子节点;如果必须删除该子节点而没替换它,则返回null。 
///
/// 下面的表格总结了上面的内容:
///
/// |                     | **newWidget == null**  | **newWidget != null**   |
/// | :-----------------: | :--------------------- | :---------------------- |
/// |  **child == null**  |  Returns null.         |  Returns new [Element]. |
/// |  **child != null**  |  删除Old child, returns null. | 如果可能的话更新Old child,返回child或新的 [Element]. |

@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
  if (newWidget == null) {
    if (child != null)
      deactivateChild(child);
    return null;
  }
  Element newChild;
  if (child != null) {
    bool hasSameSuperclass = 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);
      newChild = child;
    } else {
      deactivateChild(child);
      newChild = inflateWidget(newWidget, newSlot);
    }
  } else {
    newChild = inflateWidget(newWidget, newSlot);
  }
  return newChild;
}

3.5.scheduleWarmUpFrame

[flutter/lib/src/widgets/binding.dart]

/// 尽可能快的帧绘制,并非等待Vsync信号的响应
///
/// 在程序启动时使用,以便第一帧绘制能额外获得几毫秒
///
/// 锁定事件调度,直至此帧绘制完成 
///
/// 若提前执行了 [scheduleFrame] 或 [scheduleForcedFrame],它会延时执行 
///
/// 若别的帧绘制调度已开始,或别的地方调用了[scheduleWarmUpFrame] 则此方法不会执行
///
/// 更推荐 [scheduleFrame] 来更新试图
void scheduleWarmUpFrame() {
  if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
    return;
  _warmUpFrame = true;
  Timeline.startSync('Warm-up frame');
  final bool hadScheduledFrame = _hasScheduledFrame;
  Timer.run(() {
    handleBeginFrame(null);
  });
  Timer.run(() {
    handleDrawFrame();//
    resetEpoch();// 重置时间
    _warmUpFrame = false;
    if (hadScheduledFrame)
      scheduleFrame();
  });
  // 事件锁定
  lockEvents(() async {
    await endOfFrame;
    Timeline.finishSync();
  });
}

3.5.1. handleBeginFrame

[flutter/lib/src/widgets/binding.dart]

/// 由引擎调用以准备 framework 生成新的帧。
///
/// 此函数调用[scheduleFrameCallback]注册的所有瞬时帧回调(TRANSIENT FRAME CALLBACKS)
/// 然后返回,运行任何预定的微任务(例如,通过瞬态帧回调解决的任何[Future]的处理程序),
/// 并调用[handleDrawFrame]以继续帧。
///
/// 若传入时间戳为空,则重用最后一帧的时间戳。
///
/// 若想在 debug 模式下的每帧开始处显示横,将[debugPrintBeginFrameBanner]设置为true。
/// 横幅将使用[debugPrint]打印到控制台,并包含帧号(每帧加1)和帧的时间戳。
/// 若传入时间戳为空,则显示字符串“warm-up frame”而非时间戳。
/// 它允许响应操作系统的“Vsync”信号,从而将 frames 主动推入的帧与引擎请求的帧区分开来。
///
/// 也可以将[debugPrintEndFrameBanner]设置为true,从而在每帧末尾显示横幅。
/// 这使的开发者可以区分打印的日志是在'帧期间'还是在'帧之间'(例如,响应事件或计时器)。
void handleBeginFrame(Duration? rawTimeStamp) {
  Timeline.startSync('Frame', arguments: timelineArgumentsIndicatingLandmarkEvent);
  _firstRawTimeStampInEpoch ??= rawTimeStamp;
  _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
  if (rawTimeStamp != null)
    _lastRawTimeStamp = rawTimeStamp;
    _hasScheduledFrame = false;
  try {
    // TRANSIENT FRAME CALLBACKS
    Timeline.startSync('Animate', arguments: timelineArgumentsIndicatingLandmarkEvent);
    _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;
  }
}

3.5.2. handleDrawFrame

[flutter/lib/src/widgets/binding.dart]

/// 由引擎调用以绘制出新的帧。
///
/// 此方法在 [handleBeginFrame] 之后立即被调用。
/// 它调用由[addPersistentFrameCallback]注册的所有回调,这些回调通常驱动渲染管道,
/// 然后调用由[addPostFrameCallback]注册的回调。
///
/// 可以参阅 [handleBeginFrame] 的挂载调试(debugging hooks),它在处理帧回调时可能很有用。
void handleDrawFrame() {
  assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
  Timeline.finishSync(); // end the "Animate" phase
  try {
    // PERSISTENT FRAME CALLBACKS
    _schedulerPhase = SchedulerPhase.persistentCallbacks;
    for (final FrameCallback callback in _persistentCallbacks)
      _invokeFrameCallback(callback, _currentFrameTimeStamp!);

    // POST-FRAME CALLBACKS
    _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;
  }
}

3.5.3. scheduleFrame

[flutter/lib/src/widgets/binding.dart]

/// 如有必要,可通过调用[Window.scheduleFrame]来调度一个新帧。
///
/// 调用此方法后,引擎将(最终)调用 [handleBeginFrame]。 
/// (该调用可能会延迟,例:如果屏幕处于关闭状态,则通常会延迟直到屏幕打开且应用程序可见为止。)
/// 在一个帧中调用此方法则会强制调度另一个帧,即使当前帧尚未完成。
///
/// 调度帧由操作系统提供的 “Vsync” 信号触发。
/// 历史上屏幕通过 “Vsync”信号 刷新显示内容
/// 现在硬件的操作更复杂些,但通过“Vsync” 信号重新渲染从而刷新APP的方案继续被延用。
///
/// 若想打印调度帧的堆栈信息,需将[debugPrintScheduleFrameStacks]设置为true。
///
/// 参见:
///
///  * [scheduleForcedFrame], 在调度帧时会忽略[lifecycleState]。
///  * [scheduleWarmUpFrame], 完全忽略“Vsync”信号并立即触发帧绘制。
void scheduleFrame() {
  if (_hasScheduledFrame || !framesEnabled)
    return;
  ensureFrameCallbacksRegistered();
  window.scheduleFrame();
  _hasScheduledFrame = true;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容