Sunglasses,太阳镜,希望能让你在如火如荼的移动端开发过程中变的更 cool !
Sunglasses是京东主站的黄金流程购物车团队孵化出的用于在开发、测试阶段使用的提效工具集,解决了由于组件化所导致的各团队提效工具分散、共享难度大的痛点,目前已经融合了京东主站各开发团队贡献的包括代码调试、UI调试、性能调优、数据调试等多种类型的提效工具,拥有接入成本低、代码无侵入、使用简便等优点,以不参与上线的组件化模块形式帮助各开发团队提升效率,目前已经参与到京东商城、京喜、京东极速版等App的版本迭代过程中。未来Sunglasses将持续优化共建方式,完善工具覆盖场景,打磨工具使用体验,并扩大支持App的范围,帮助各团队持续提效,成为研发、测试环节中更 cool 的一环!
Flutter帧率监控工具是京东主站在进行Flutter改造时所驱动出的页面性能调优工具,目前已经接入到Sunglasses中,本文就Sunglasses中的Flutter帧率监控工具的原理进行了分析,如有错漏,敬请指正。
界面渲染过程分为三个阶段:布局(Layout)、绘制(Paint)、合成(Composite)。
1)布局:确定渲染对象树中的每个节点的尺寸和位置
布局过程采用深度优先算法遍历渲染对象树。渲染对象树中的每个对象都会在布局过程中接受父对象的布局约束参数,决定自己的大小,然后父对象决定各个子对象的位置,完成布局过程。
为了防止因子节点发生变化而导致整个控件树重新布局,Flutter可以在某些节点设置布局边界(Relayout boundary),即当边界内的任何对象发生重新布局时,不会影响边界外的对象,反之亦然。例如,控件被设置了固定大小(tight constraint),控件大小不会影响其他控件,无论子树发生什么样的变化,处理范围都只在子树上。
2)绘制:确定控件外观
绘制节点过程同样采用深度优先遍历渲染对象树,把不同的RenderObject绘制到不同的图层上。
在绘制流程中,可能出现如下图所示情况:节点 2 在绘制子节点 4 时,由于其节点4需要单独绘制到一个图层上(如 video),因此绿色图层上面多了个黄色的图层。之后再需要绘制其他内容(标记 5)就需要再增加一个图层(红色)。再接下来要绘制节点 1 的右子树(标记 6),也会被绘制到红色图层上。所以如果 2 号节点发生改变就会改变红色图层上的内容,因此也影响到了毫不相干的 6 号节点。
为解决这种问题,Flutter中有一个与布局边界对应的机制——重绘边界(Repaint Boundary)。在绘制页面时如果遇到重绘边界就会强制切换图层。
3)合成
Flutter 的渲染树层级通常很多,直接给到渲染引擎进行多图层渲染,可能会出现大量渲染内容的重复绘制,所以还需要进行一次图层合成,即将所有的图层根据大小、层级、透明度等规则计算出最终的显示效果,将相同的图层归类合并,简化渲染树,提高渲染效率。合并完成后,Flutter 会将几何图层数据交由 Skia 引擎加工成二维图像数据,最终交由 GPU 进行渲染,完成界面的展示。
结合Flutter分层架构介绍界面渲染具体流程
1)三种树状层级结构
Flutter中涉及UI布局的有三种数据结构:
Widget:控件实现的基本逻辑单位,里面存储的是有关视图渲染的配置信息,包括布局、渲染属性、事件响应信息等.
Element:Widget 的一个实例化对象,它承载了视图构建的上下文数据,是连接结构化的配置信息到完成最终渲染的桥梁。
RenderObject:主要负责实现视图渲染的对象。
三者关系如下:
首先,通过 Widget 树生成对应的 Element 树;然后,创建相应的 RenderObject 并关联到 Element.renderObject 属性上;最后,构建成 RenderObject 树。
2)对渲染树进行布局和绘制操作
Flutter引擎层不直接创建和管理线程,而是由嵌入层负责线程管理以及对应的消息循环。嵌入层为引擎提供task runners。
Flutter引擎需要4个task runner的引用,即
Platform Task Runner、UI Task Runner、GPU Task Runner、IO Task Runner。
其中UI Task Runner和GPU Task Runner与渲染相关。
”UI Task Runner”
执行主隔离区(Root Isolate)中的所有Dart代码。Root Isolate绑定了必要的Flutter函数,用来进行渲染相关的操作。
每一帧的渲染过程如下:
Root Isolate通知Flutter Engine有帧需要渲染。
Flutter Engine通知平台,需要在下一个Vsync到来的时候得到通知。
平台等待下一个Vsync到来。
Vsync到达后:
1、更新动画。
2、在构建阶段重建widgets。
3、遍历widgets树生成渲染树,并将它们绘制成一个图层树,这些图层将立即提交给引擎(当前阶段没有进行任何光栅化,这个步骤仅是生成了对需要绘制内容的描述)。
4、创建或者更新Tree,这个Tree包含了用于屏幕上显示Widgets的语义信息。这个东西主要用于平台相关的辅助Accessibility元素的配置和渲染。
“GPU Task Runner”
负责将图层树提供的信息转化为平台可执行的GPU指令,同时负责绘制所需要的GPU资源的管理。资源主要包括平台Framebuffer,Surface,Texture和Buffers等。
综合以上,Flutter 的绘制整体流程如下:
UI Task Runner构建视图结构数据,生成渲染树,这些数据会在 GPU 线程进行图层合成,随后交给 Skia 引擎加工成 GPU 数据,这些数据通过 OpenGL 提供给 GPU 渲染。视频控制器根据垂直同步信号(VSync)以每秒 60 次的速度,从帧缓冲区读取帧数据交由显示器完成图像显示。
Flutter framework中的Window类提供了与操作系统交互的基本接口,比如图形绘制API等。Engine在收到VSync信号时,会通过window.onBeginFrame和window.onDrawFrame回调给Framework来驱动Flutter渲染管线生成最新的图层树。图层树通过window.render发送到Engine端,交给GPU Task Runner做光栅化与上屏。同时还提供了onReportTimings回调(v1.12.13以后建议使用SchedulerBinding.instance.addTimingsCallback)方法提供帧绘制时间。
Flutter帧率监控工具监听SchedulerBinding.instance.addTimingsCallback,根据CPU绘制时间和GPU绘制时间,做了分段,小于50ms的认为正常,50到80ms的 疑似卡顿,80到240的是验证卡顿,大于240的是bigjank,这样区分完了以后,在页面onPause的时候,会进行抓取采样。将所有的帧数据累计以后,取平均值,拿到平均帧率,然后通过channel工具实时传递给原生模块(Android),然后原生模块通过反射调用sunglasses(原生工具平台)的方法,将数据实时传递给sunglasses进行展示。
后续会增加堆栈数据的获取,以便卡顿发生时定位具体的函数调用。
The Engine architecture
https://github.com/flutter/flutter/wiki/The-Engine-architecture
Flutter’s Rendering Pipeline
https://www.youtube.com/watch?v=UUfXWzp0-DU
How Flutter renders Widgets
https://www.youtube.com/watch?v=996ZgFRENMs
https://flutter.dev/