Flutter APP 启动过程源码分析

提示:本文设计到的 Flutter framework 层源码是基于 Flutter 1.20.0

void main() => runApp(MyApp());
void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

三行代码代表了Flutter APP 启动的三个主流程:

  1. binding初始化(ensureInitialized)
  2. 绑定根节点(scheduleAttachRootWidget)
  3. 绘制热身帧(scheduleWarmUpFrame)

1 binding初始化(ensureInitialized)

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

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

WidgetsFlutterBinding的ensureInitialized()其实就是一个获取WidgetsFlutterBinding单例的过程,真正的初始化实现代码在其7个mixin中。7个mixin按照严格的先后调用链关系完成不同 binding 的初始化。

WidgetsFlutterBinding继承了BindingBase,在调用自己的构造器之前会先先执行了父类BindingBase构造函数。

  BindingBase() {
    developer.Timeline.startSync('Framework initialization');

    assert(!_debugInitialized);
    initInstances();
    assert(_debugInitialized);

    assert(!_debugServiceExtensionsRegistered);
    initServiceExtensions();
    assert(_debugServiceExtensionsRegistered);

    developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});

    developer.Timeline.finishSync();
  }

这里会调用initInstances() ,由于7个 mixin 都重写了initInstances(), with 的最后一个 WidgetsBinding 覆盖了前面的 binding 的 initInstances(),所以 WidgetsBinding 的 initInstances() 会首先被调用,而 WidgetsBinding 的 initInstances 函数中先通过 super 向上调用 initInstances ,所以 initInstances 的执行顺序依次是:BindingBase -> GestureBinding -> SchedulerBinding -> ServicesBinding -> PaintingBinding -> SemanticsBinding -> RendererBinding -> WidgetsBinding,从而依次完成各个 Binding 的初始化相关工作。

1.1 GestureBinding

手势事件绑定,主要处理触屏幕指针事件的分发以及事件最终回调处理。

  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    window.onPointerDataPacket = _handlePointerDataPacket;
  }

这里将事件处理回调 _handlePointerDataPacket 赋值给 window,供window 收到屏幕指针事件后调用。window类是framework层与engine层处理屏幕相关事件的桥梁。_handlePointerDataPacket中会先调用hitTest进行命中测试。GestureBinding及RenderBinding都实现了hitTest方法,按照mixin顺序会优先调用RenderBinding.hitTest。RenderBinding.hitTest会从renderTree的根节点递归调用命中测试,返回命中的深度最大的节点到根节点路径上的所有节点。然后再执行dispatchEvent根据返回的hitTest命中节点列表遍历分发事件,事件分发的顺序是先子节点后父节点最终到根节点,类似Android的事件冒泡机制。

1.2 SchedulerBinding

绘制调度绑定

  @override
  void initInstances() {
    super.initInstances();
    _instance = this;

    // debug编译模式时统计绘制流程时长,开始、运行、构建、光栅化。
    if (!kReleaseMode) {
      int frameNumber = 0;
      addTimingsCallback((List<FrameTiming> timings) {
        for (final FrameTiming frameTiming in timings) {
          frameNumber += 1;
          _profileFramePostEvent(frameNumber, frameTiming);
        }
      });
    }
  }

SchedulerBinding的作用就是在debug编译模式时统计绘制流程时长,开始、运行、构建、光栅化。

1.3 ServicesBinding

  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    
    // 构建一个用于platform与flutter层通信的 BinaryMessenger
    _defaultBinaryMessenger = createBinaryMessenger();
    
    // 设置window监听回调,处理platform发送的消息
    window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
    initLicenses();
    
    // 设置处理platform发送的系统消息的 Handler
    SystemChannels.system.setMessageHandler(handleSystemMessage);
    
    // 设置AppLifecycleState生命周期回调
    SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
    
    // AppLifecycleState 为 resumed 和 inactive 时才允许响应Vsync信号进行绘制
    readInitialLifecycleStateFromNativeWindow();
  }

ServicesBinding的初始化的工作主要是两个:

  1. platform与flutter层通信相关服务的初始化
  2. 注册监听了flutter app的生命周期变化事件,根据生命周期状态决定是否允许发起绘制任务

设置处理 system 消息 handleSystemMessage,ServicesBinding 的handleSystemMessage 主要工作是异步处理系统发送的内存紧张信号,PaintingBinding 也实现了该方法。由于 PaintingBinding 被 with 的顺序在 ServicesBinding 后面,所以 PaintingBinding 的 handleSystemMessage 会覆盖 ServicesBinding 的 handleSystemMessage 被调用

  @override
  Future<void> handleSystemMessage(Object systemMessage) async {
    await super.handleSystemMessage(systemMessage);
    final Map<String, dynamic> message = systemMessage as Map<String, dynamic>;
    final String type = message['type'] as String;
    switch (type) {
      case 'fontsChange':
        _systemFonts.notifyListeners();
        break;
    }
    return;
  }

PaintingBinding.handleSystemMessage 优先调用 super.handleSystemMessage ,也就是先调用 ServicesBinding.handleSystemMessage异步处理系统发送的内存紧张信号,接着异步处理系统字体变动事件。

1.4 PaintingBinding

除了前面讲的监听系统字体变化事件,这里主要是在绘制热身帧之前预热Skia渲染引擎

  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    
    // 初始化图片缓存
    _imageCache = createImageCache();
    
    if (shaderWarmUp != null) {
      //第一帧绘制前的预热工作
      shaderWarmUp.execute();
    }
  }

1.5 SemanticsBinding

渲染辅助类绑定,主要负责关联语义树与Flutter Engine。Flutter维护了一个 semantic tree(语义树),页面构建的时候会根据各Widget的语义描述构建一棵 semantic tree。如在Image组件中配置 semanticLabel 语义内容,用户在IOS/Android手机开启无障碍功能时,触摸到该 Image 时通过语义树查找到对应的语义描述交给Flutter Engine,实现读屏等功能。

  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _accessibilityFeatures = window.accessibilityFeatures;
  }

1.6 RendererBinding

渲染绑定,RendererBinding是render tree 与 Flutter engine的粘合剂,它持有了render tree的根节点 renderView

  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    
    // 初始化PipelineOwner管理渲染流程
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    
    // 设置window的屏幕参数变化、文本缩放因子变化、亮度等变化、语义启用等回调。
    window
      ..onMetricsChanged = handleMetricsChanged
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
      ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
      ..onSemanticsAction = _handleSemanticsAction;
      
    // 初始化一个RenderView作为render tree的根节点,作为渲染执行入口
    initRenderView();
    
    // 设置是否根据render tree生成语义树
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    
    // 绘制回调
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    
    // 初始化鼠标监听
    initMouseTracker();
  }

GestureBinding.initInstances 方法中的事件处理,调用的就是这里的renderView.hitTest 从根节点开始命中测试的。正因为 RenderBinding 创建并持有了 RenderView 实例,所以 GestureBinding 中通过 mixin 机制将 RenderBinding 的 hitTest 方法混入,从而可以实现命中测试,相当于需要用到命中测试的地方都通过 mixin 委托给 RenderBinding 来实现了。

addPersistentFrameCallback 将绘制处理回调_handlePersistentFrameCallback 加入到 FrameCallback 类型回调列表,_handlePersistentFrameCallback 中的 drawFrame 实现绘制流水线。

WidgetsBinding

组件绑定

@override
  void initInstances() {
    super.initInstances();
    _instance = this;

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

    // 初始化BuildOwnder,处理需要绘制的Element的构建工作
    _buildOwner = BuildOwner();
    
    // 通过SchedulerBinding初始化window的onBeginFrame、onDrawFrame回调
    // 如果app可见,通过window.scheduleFrame向engine发起绘制请求
    buildOwner.onBuildScheduled = _handleBuildScheduled;
    
    // 语言环境变化处理
    window.onLocaleChanged = handleLocaleChanged;
    
    // platform访问权限变化处理
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    
    // 处理系统发送的push/pop页面请求
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
  }

WidgetsBinding属于最外层的 mixin,作为处理 Widget 相关事件的入口。在初始化过程中主要是生成了 BuildOwner 实例,以及window的onBeginFrame、onDrawFrame 回调,后面渲染流程会用到。

BindingBase先通过按顺序执行7个mixin的initInstances方法,完成了相关初始化工作,以及两个重要类的实例化PipelineOwner、BuildOwner。

然后就是执行了initServiceExtensions方法,实现了该方法的mixin按调用顺序为WidgetsBinding-->RendererBinding-->SchedulerBinding-->ServicesBinding主要就是在debug模式下注册相关拓展服务。

2 绑定根节点(scheduleAttachRootWidget)

由于是组件相关,scheduleAttachRootWidget 具体的实现在WidgetsBinding 里

  @protected
  void scheduleAttachRootWidget(Widget rootWidget) {
    Timer.run(() {
      // 将传入的Widget绑定到一个根节点并构建三棵树
      attachRootWidget(rootWidget);
    });
  }

将app的主widget(即传入的 rootWidget)和根节点绑定。其中render tree 的根节点就是前面初始化流程中RendererBinding.initInstances过程创建的RenderView,RenderView是继承自RenderObject的,所以还需要创建Element和Widget与之关联,而创建的 Element 和 Widget 分别对应另外两棵树的根节点。

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

先是通过传入的 rootWidget 及 RenderView 实例化了一个RenderObjectToWidgetAdapter对象,而RenderObjectToWidgetAdapter是继承自RenderObjectWidget,即创建了Widget树的根节点。继续调用 attachToRenderTree

  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
    if (element == null) {
      owner.lockState(() {
      
        // 创建了一个RenderObjectToWidgetElement实例作为element tree的根节点
        element = createElement();
        assert(element != null);
        
        // 绑定BuildOwner
        element.assignOwner(owner);
      });
      
      // 标记需要构建的element,并rebuild
      owner.buildScope(element, () {
        element.mount(null, null);
      });

      SchedulerBinding.instance.ensureVisualUpdate();
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element;
  }
@override
  RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);

attachToRenderTree 中通过 createElement() 创建了一个RenderObjectToWidgetElement 实例作为 element tree 的根节点,并绑定BuildOwner,通过 BuildOwner 构建需要构建的 element。

3 绘制热身帧(scheduleWarmUpFrame)

绑定完根节点后,就开始立即执行scheduleWarmUpFrame()绘制热身帧。不用绘制热身帧也可以渲染的,那为什么还需要绘制热身帧?

绘制热身帧的目的是:

  1. 前面 window.scheduleFrame 发起绘制请求是在收到Vsync信号后才开始的,app初始化时为了节省时间并未等待Vsync信号直接开始绘制,最多可以节省16.6ms(60Hz屏幕刷新率)等待时间。
  2. 在热身帧绘制结束前通过加锁来屏蔽期间的屏幕指针事件处理及_taskQueue中的回调,保证在绘制过程中不会再触发新的重绘。
  3. 在热身帧绘制结束后调用 resetEpoch() 来重置时间戳,避免热重载情况从热身帧到热重载帧的时间差,导致隐式动画的跳帧情况。

和普通绘制一样,热身帧也是通过handleBeginFrame、handleDrawFrame这两个回调来进行绘制流程,在前面 WidgetBinding 初始化时将这两个回调交给了window,具体代码逻辑是在 SchedulerBinding。

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

    _warmUpFrame = true;
    Timeline.startSync('Warm-up frame');
    final bool hadScheduledFrame = _hasScheduledFrame;
    
    // Timer任务会加入到event queue
    // 所以在执行绘制前先处理完microtask queue中的任务
    Timer.run(() {
      assert(_warmUpFrame);
      
      // 绘制Frame前工作,主要是处理Animate动画
      handleBeginFrame(null);
    });
    Timer.run(() {
      assert(_warmUpFrame);
      
      // 开始Frame绘制
      handleDrawFrame();
      
      // 重置时间戳
      resetEpoch();
      _warmUpFrame = false;
      if (hadScheduledFrame)
      
        // 后续Frame绘制请求
        scheduleFrame();
    });

    lockEvents(() async {
      await endOfFrame;
      Timeline.finishSync();
    });
  }

handleBeginFrame处理动画相关逻辑,动画回调后并不立即执行动画,而是改变了animation.value,并调用setSate()来发起绘制请求。动画的过程就是在 Vsync 信号到来时根据动画进度计算出对应的 value,而对应的 Widget 也会随着 animation.value 的变化而重建,从而形成动画,和Android的属性动画原理差不多。

 handleBeginFrame处理完后,会优先处理microTask任务队列。然后才是event Task,window.onDrawFrame(),对应SchedulerBinding.handleDrawFrame()。(Timer任务会加入到event queue,flutter的事件处理机制是优先处理 micro queue 中任务)

  void handleDrawFrame() {
    assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
    Timeline.finishSync(); // end the "Animate" phase
    try {
      // 处理Persistent类型回调,主要包括build\layout\draw流程
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (final FrameCallback callback in _persistentCallbacks)
        // 注释1
        _invokeFrameCallback(callback, _currentFrameTimeStamp);

      // 处理Post-Frame回调,主要是状态清理,准备调度下一帧绘制请求
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
      final List<FrameCallback> localPostFrameCallbacks =
          List<FrameCallback>.from(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      for (final FrameCallback callback in localPostFrameCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);
    } finally {
    
      // 处理完成,设置状态为idle
      _schedulerPhase = SchedulerPhase.idle;
      Timeline.finishSync(); // end the Frame
      assert(() {
        if (debugPrintEndFrameBanner)
          debugPrint('▀' * _debugBanner.length);
        _debugBanner = null;
        return true;
      }());
      _currentFrameTimeStamp = null;
    }
  }

上面代码中的注释1处的 _invokeFrameCallback 中处理的 callback 就是我们上面在 RendererBinding 中的 被添加进 FrameCallback 类型的列表中的 _handlePersistentFrameCallback,_handlePersistentFrameCallback 里面调用了 drawFrame,这里也是用到了 mixin 机制,在 WidgetsBinding.drawFrame() 中完成组件的构建任务,在 RendererBinding.drawFrame 完成组件的布局、绘制任务

  // RendererBinding
  void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
    _mouseTracker.schedulePostFrameCheck();
  }

  void drawFrame() {
    assert(renderView != null);
    // 布局
    pipelineOwner.flushLayout();
    // 更新 RenderObject 中需要绘制的内容
    pipelineOwner.flushCompositingBits();
    // 绘制
    pipelineOwner.flushPaint();
    if (sendFramesToEngine) {
      // 产生这一帧的数据Scene,由window.render交给Engine,最终显示到屏幕(发送数据到GPU)。
      renderView.compositeFrame();
      // 将语义树发送到操作系统
      pipelineOwner.flushSemantics();
      _firstFrameSent = true;
    }
  }
// WidgetsBinding
void drawFrame() {
   ...
   
   try {
    if (renderViewElement != null)
      //调用BuildOwner.buildScope开始构建
      buildOwner.buildScope(renderViewElement);
      
    //调用RendererBinding.drawFrame,开始布局、绘制阶段。
    super.drawFrame();
    
    //从element tree中移除不需要的element,unmount
    buildOwner.finalizeTree();
  } finally {
     ...
  }
}

绘制流程结束后会调用renderView.compositeFrame()产生这一帧的数据 Scene,由 window.render 交给Engine,最终显示到屏幕。整个热身帧绘制流程:

SchedulerBinding.scheduleWarmUpFrame
-> SchedulerBinding.handleBeginFrame 处理动画
-> SchedulerBinding.handleDrawFrame
-----> WidgetBinding.drawFrame 通过 buildOwner 构建组件
-----> RendererBinding.drawFrame 通过 pipelineOwner 完成组件布局和绘制
-----> renderView.compositeFrame 发送 Scene 到GPU

总结

mixin机制在FlutterApp启动过程带来的优势:

  1. 高内聚低耦合:适合应用于需要多个功能模块配合完成的场景,将功能模块通过mixin解耦,各模块职责单一,相互之间不直接引用。
  2. 代码复用:多个类可通过混入 mixin 类来复用 mixin 类的代码
  3. 保证调用顺序:mixin配合super调用,可以实现同名方法的“继承链”式调用,保证串行执行顺序。

Flutter App的启动过程总结:

  1. ensureInitialized 通过7个 mixin 类 按顺序完成相关初始化工作
  2. scheduleAttachRootWidget 绑定app 应用启动的 Widget 到 render tree 的根节点RenderView上并生成widget tree 的根节点 RenderObjectToWidgetAdapter,RenderView又关联了widget tree 的根节点和 element tree 的根节点
  3. scheduleWarmUpFrame 完成热身帧绘制

参考文章:
从mixin机制理解Flutter App启动

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容