美文网首页Flutter
Flutter APP 启动过程源码分析

Flutter APP 启动过程源码分析

作者: Joker_Wan | 来源:发表于2020-11-20 14:31 被阅读0次

    提示:本文设计到的 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启动

    相关文章

      网友评论

        本文标题:Flutter APP 启动过程源码分析

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