开发者

Flutter runApp到渲染上屏分析详解

目录
  • 起源
  • 分析准备
    • ensureInitialized
    • scheduleAttachRootWidget
    • scheduleWarmUpFrame
  • 总结

    起源

    flutter作为一个跨平台的框架,在绘制上体现出了它跨平台的良好性能.那么,它是如何从runApp()后 绘制上屏的呢?本文将与你一起去探索这一过程.

    ps: 为了思维不中断, 本文仅对整体流程作分析,不会深入分析具体实现

    我们运行一个flutter app ,入口一定是从runApp() 中进行的. 那么flutter 在runApp() 中做了哪些处理呢? 首先,我们从runApp() 这个函数聊起.它是一个需要传入Widget 的函数.而传入的Widget ,即首屏渲染所需的Widget.

    在此我们应该知道这个概念, 即widget 是flutter 中用来描述ui如何绘制的配置文件,去形容一个组件在整体中的位置、大小.

    那么不难推断出.在runApp() 的过程中,如果Widget是绘制的配置文件. 那么手势注册、桢调度等都应该是在此时注册的. 带着这样的推断我们去源码中找答案.

    分析准备

    void runApp(Widget app) {
      WidgetsFlutterBinding.ensureInitialized()
      // 这里解释下:
      // ..是flutter中的级联运算符
      // 可以同一个对象上连续调用多个对象的变量或方法
        ..scheduleAttachRootWidget(app)
        ..scheduleWarmUpFrame();
    }
    

    在runApp() 中可以看到, 这里实际上也就是调用了三个方法,以下我们对每个方法进行刨析.

    ensureInitialized

    从字面意思看,这是为了确保已经初始化而调用的方法.它的作用是为了返回WidgetsBinding的对象.如果了解单列模式的话,会发现这么写实际上就是一个单列模式.

      static WidgetsBinding ensureInitialized() {
        if (WidgetsBinding._instance == null)
          WidgetsFlutterBinding();
        return WidgetsBinding.instance;
      }编程客栈
    

    这里我们去挖掘一下WidgetsFlutterBinding内部的构造函数在初始化时做了什么? 它是继承BingingBase的, 我们进入BingingBase中浅看一下大概的实现, Timeline 和assert这部分代码我们可以忽略.

    ps: assert 在release代码中不会执行

    也就是实际上的结构是这样的

     BindingBase() {
        initInstances();
        initServiceExtensions();
      }
    
    • initInstances() 方法是为了绑定初始化实例和其他的一些状态.
    • initServiceExtensions() 方法是为了绑定初始化服务

    这里我们回过头来看WidgetsFlutterBinding它的一些实现接口, 顺序依次是:

    接口解释
    GestureBinding实现点击命中测试
    SchedulerBinding引入了帧的概念
    ServicesBinding提供对插件的访问
    PaintingBinding解码图像
    SemanticsBinding语义树
    RendererBinding处理render tree
    WidgetsBinding处理widget tree

    内部的具体实现这里不再赘述,后续会逐章对这些进行分解、解释.这里只是去分析整体的流程. 也就是说, 在这里我们完成了对app系统的初始化、推动界面的绘制,获取手势等等.

    scheduleAttachRootWidget

    在一系列服务注册完之后,我们需要把当前的root widget 挂载到树上,

      void scheduleAttachRootWidget(Widget rootWidget) {
        Timer.run(() {
          attachRootWidget(rootWidget);
        });
      }
    

    在attachRootWidget 的方法中, 我们构建了RenderObjectToWidgetAdapter 的对象. 通过RenderObjecwww.devze.comtToWidgetAdapt开发者_Go开发er 当作Element 和RenderObject 之间的桥梁,

    {
        final bool isBootstrapFrame = renderViewElement == nujavascriptll;
        _readyToProduceFrames = true;
        _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
          container: renderView,
          debugShortDescription: '[root]',
          child: rootWidget,
        ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
        if (isBootstrapFrame) {
          SchedulerBinding.instance.ensureVisualUpdate();
        }
    }
    

    同时,这里 根据renderViewElement 有没有赋值来判断是否是第一次加载.如果是第一次加载页面,会通知界面去刷新ui

    scheduleWarmUpFrame

    这个方法从字面意思来看 应该是在界面启动时去执行的一些方法. 首先,我们看一下它的一些引用路径.发现一共有三个地方的代码都引用了这个方法

    Flutter runApp到渲染上屏分析详解

    可以看到调用的三个地方分别是:

    • allowFirstFrame()
    • performReassemble()
    • runApp()

    好家伙,这不都是类似于绘制的入口函数? 因此,我们可以推断这里就是一些绘制时初始化时候必须执行的一些代码.

    {
        // 这里通过伪代码简要了解一下大致实现
        // 
        if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle) rpythoneturn;
        _warmUpFrame = true;
        // 这里把源代码提前了,由于handle*()的代码都是通过Timer.run执行的,实际上是
        // 一种异步执行,会在下一帧去调用
        lockEvents(() async {
          await endOfFrame;
          timelineTask.finish();
        });
        // 开始帧回调
        handljseBeginFrame(null);
        // 新帧回调处理
        handleDrawFrame();
        // 这里和热重载相关
        resetEpoch();
        _warmUpFrame = false;
        // 
        if (hadScheduledFrame) scheduleFrame();
    }
    

    总结

    总结一下, runApp() 通过

    • 注册各种服务
    • 注册ui
    • 绘制上屏

    最终在屏幕上呈现出ui,其中还有如PipelineOwner、BuildOwner等等非常重要的api,这里暂且不表. 后续我们单章详细介绍,希望这一次与你一起阅读的思路可以帮你一起思考启动的流程.

    以上就是Flutter runApp到渲染上屏分析详解的详细内容,更多关于Flutter runApp渲染上屏的资料请关注我们其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜