Flutter从启动到显示

Posted 小源子2016

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter从启动到显示相关的知识,希望对你有一定的参考价值。

目录

前言

一、入口代码

二、runApp

三、WidgetsFlutterBinding

 四、BindingBase

 五、Window

 六、scheduleAttachRootWidget

 七.RendererBinding

 

八.scheduleWarmUpFrame

总结


前言

 研究启动过程,目前是理解和加深Flutter框架原理,为后续开发高性能的Flutter应用提供理论思想


一、入口代码

这句代码就是我们启动Flutter程序的入口代码

 runApp(MyApp());

二、runApp

   a.源码注释:Inflate the given widget and attach it to the screen.

   b.我的翻译:让给定的widget膨胀并让其附加到屏幕上


   c.加深理解:何为膨胀,若是是安卓开发,你会inflate一个xml,将其膨胀为view,flutter中一个widget就相当于这个xml配置,flutter通过runApp将其膨胀成element tree和renderObject tree

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

三、WidgetsFlutterBinding

      混入了各种Binding,如同胶水一样,使得Flutter Widget 框架 和 Flutter Engin连接在一起

那具体是如何粘接的呢?请往下看

/// A concrete binding for applications based on the Widgets framework.
///
/// This is the glue that binds the framework to the Flutter engine.
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

  /// Returns an instance of the [WidgetsBinding]
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}

 四、BindingBase

   binding中都持有了全局对象window

 ui.Window get window => ui.window;

 五、Window

 window就是FlutterFramework连接宿主操作系统的接口,具体来说

  你看看window方法里有些方法是不是有native关键字

  正如在android JNI开发的时候,也有native关键字,就是用来表明这个是和一个native

 函数(c/c++)对应的,这样,我们就在window中建立了和engine(skia,Dart VM)的连接工作

class Window {
    
  // ,一个逻辑像素显示多少物理像素,数字越大,显示效果就越精细保真。
  // DPI是设备屏幕的固件属性,如Nexus 6的屏幕DPI为3.5 
  double get devicePixelRatio => _devicePixelRatio;
  
  // Flutter UI绘制区域的大小
  Size get physicalSize => _physicalSize;

  // 当前系统默认的语言Locale
  Locale get locale;
    
  // 当前系统字体缩放比例。  
  double get textScaleFactor => _textScaleFactor;  
    
  // 当绘制区域大小改变回调
  VoidCallback get onMetricsChanged => _onMetricsChanged;  
  // Locale发生变化回调
  VoidCallback get onLocaleChanged => _onLocaleChanged;
  // 系统字体缩放变化回调
  VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
  // 绘制前回调,一般会受显示器的垂直同步信号VSync驱动,当屏幕刷新时就会被调用
  FrameCallback get onBeginFrame => _onBeginFrame;
  // 绘制回调  
  VoidCallback get onDrawFrame => _onDrawFrame;
  // 点击或指针事件回调
  PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
  // 调度Frame,该方法执行后,onBeginFrame和onDrawFrame将紧接着会在合适时机被调用,
  // 此方法会直接调用Flutter engine的Window_scheduleFrame方法
  void scheduleFrame() native 'Window_scheduleFrame';
  // 更新应用在GPU上的渲染,此方法会直接调用Flutter engine的Window_render方法
  void render(Scene scene) native 'Window_render';

  // 发送平台消息
  void sendPlatformMessage(String name,
                           ByteData data,
                           PlatformMessageResponseCallback callback) ;
  // 平台通道消息处理回调  
  PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
  
  ... //其它属性及回调

  void updateSemantics(SemanticsUpdate update) native 'Window_updateSemantics';

  void setIsolateDebugName(String name) native 'Window_setIsolateDebugName';

   
}

 六、scheduleAttachRootWidget

  总之,这一步就是根据widget inflate三颗树

 /// Schedules a [Timer] for attaching the root widget.
  ///
  /// This is called by [runApp] to configure the widget tree. Consider using
  /// [attachRootWidget] if you want to build the widget tree synchronously.
  @protected
  void scheduleAttachRootWidget(Widget rootWidget) {
    Timer.run(() {
      attachRootWidget(rootWidget);
    });
  }
  void attachRootWidget(Widget rootWidget) {
    _readyToProduceFrames = true;
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
  }

// Inflate this widget and actually set the resulting [RenderObject] as the
  /// child of [container].
  ///
  /// If `element` is null, this function will create a new element. Otherwise,
  /// the given element will have an update scheduled to switch to this widget.
  ///
  /// Used by [runApp] to bootstrap applications.
  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;
  }

 七.RendererBinding

在binding初始化的时候我们这里特别介绍下RenderBingd,addPersistentFrameCallback,它注册了一个持续帧回调处理函数,它本质上通过native 函数调用绘制图形,调用window.render强制 GPU刷新当前应用显示,但是它什么时候被调用呢,请继续看

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;
    initRenderView();
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    initMouseTracker();
  }

  flushLayout:renderObject布局(明确大小、位置等)

  flushPaint:通过canvas api调用engin skia绘制数据

  compositerFrame:通过window.render通知GPU进行光栅化

  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;
    }
  }

八.scheduleWarmUpFrame

 热身帧的过程是

  1.触发 handleBeginFrame,其实这个是window.onBeginFrame回调,接收vsync信号,为了

加快首帧速度,我们主动触发,正常应该是engin触发

2.触发handleDrawFrame(非首帧应该是engin触发),其实这个是window.onDrawFrame回调,engine告知Flutter Frame 要

绘制帧,内部则调用了帧会调监听,就包括上一步RendererBinding注册的监听

3.然后调用scheduleFrame, 实际调到了window.scheduleFrame,触发native 的Window_scheduleFrame,然后在适合的时候,engine将会调用onBeginFrame和onDrawFrame

就是通知Engin重新调度Frame(估计有变化的时候才需要通知engin重新调度帧,所以帧回调在界面不怎么变化的时候调用次数少的)

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

    _warmUpFrame = true;
    Timeline.startSync('Warm-up frame');
    final bool hadScheduledFrame = _hasScheduledFrame;
    // We use timers here to ensure that microtasks flush in between.
    Timer.run(() {
      assert(_warmUpFrame);
      handleBeginFrame(null);
    });
    Timer.run(() {
      handleDrawFrame();

      resetEpoch();
      _warmUpFrame = false;
      if (hadScheduledFrame)
        scheduleFrame();
    });

    // Lock events so touch events etc don't insert themselves until the
    // scheduled frame has finished.
    lockEvents(() async {
      await endOfFrame;
      Timeline.finishSync();
    });
  }

总结

1.首帧处理上Flutter主动触发绘制,减少等待时间

2.在分析过程中运用了Flutter四颗树知识加深了理解

参考资料:

1.Flutter 框架实现原理

https://blog.csdn.net/yingshukun/article/details/107813592

2.Flutter运行机制-从启动到显示

以上是关于Flutter从启动到显示的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Firebase 获取数据到 Recyclerview 中的片段?

如何从用 Swift 编写的 Flutter 平台特定代码启动 ViewController?

从其他片段启动视频视图片段时显示问题?

从mysql的片段中加载ListView

14-1 启动白屏问题解决与启动屏开发-1

ActivityNotFoundException,启动活动时出错