- UI Thread RenderObject 树 Layout、Paint 生成 Scene 对象,最后传递给 Raster Thread 进行绘制上屏;
上述流程,必须要 16.6 ms 内完成,才能保证不掉帧。大部分情况,不需要构建新的卡片,但当新卡片进入列表区域时,整个计算量就会变得巨大,尤其是在复杂的业务场景下,如何保证在一帧 16.6ms 内完成全部计算,是一个不小的挑战。
上图是一次滑动 devtool 样例,卡顿阶段都是新卡片上屏时发生,其他阶段均很流畅,因为滚动速度在衰减,所以卡顿间隔也在变大。因为大部分时候都很流畅,所以平均 FPS 不低。但新卡片构建时的产生画面停顿,给我们的卡顿体感却很明显。
▐ 动态能力的挑战 - Flutter DynamicX
闲鱼 App 卡片使用自研 Flutter DynamicX 来支持我们的动态能力。基本原理:在线编辑布局 DSL,生成 dx 文件并下发。端侧通过解析 dx 文件,并结合后台卡片数据,生成 DXComponentWidget,最后生成 Widget Tree。Flutter DynamicX 技术给闲鱼带来动态更新的能力,统一监控能力(如在 DXComponentWidget 监控卡片创建),良好研发体感(在线 DSL 和 Android Layout 基本一致,对 Android 开发优化),在线编辑能力;
但在性能上,我们也付出了一定的代价:DX 卡片相比增加了模板装载和数据绑定开销,Widget 要通过 WidgetNode 递归遍历动态创建,视图嵌套层级会更得更深(后续讲述)。
说明:Flutter DynamicX 参考阿里集团 DSL 规则实现
▐ 用户体感的挑战
前面已经讲述过,相同 FPS 下,Flutter 列表的卡顿体感更明显;
在 Android RecycleView 发生小卡顿(16.6*2ms)时,体感并不明显,而 Flutter 列表在发生卡顿时,不仅时间上停顿,滑动 Offset 上也发生了跳变,为此小卡顿的体感也变得明显了;
假设列表内容足够简单,滚动不会发生卡顿,我们也发现 Flutter 列表和 Android RecycleView 也不太一样:
-
使用 ClampingScrollPhysics,在列表快停止的时候,会感受到类似磁铁吸住的感觉。
-
使用 BouncingScrollPhysics,列表滚动开始时,速度衰减的更快;
在 90hz 机器上,早期 Flutter 列表并不流畅,原因是部分机器上,触控采样率是 120hz,屏幕刷新率是 90hz,导致部分画面是 2 次触控事件,部分是 1 次触控事件,最后导致滚动 offset 发生跳变。在 Flutter 1.22 版本时,可以使用 resamplingEnabled 对触控事件进行重采样。
列表容器和FlutterDx组件优化
讲述了 Flutter 流畅度优化的挑战,现在来分享闲鱼如何优化流畅度,并沉淀进 PowerScrollView 和 Flutter Dynamic 组件。
▐ Power****ScrollView 设计和性能优化
PowerScrollView 是闲鱼团队自研 Flutter 列表组件,在 Sliver 协议上有了更好的封装和补充:数据增删改方面,补充了局部刷新;布局方面,补充了瀑布流;事件方面,补充了卡片上屏、离屏、滚动事件;控制方面,补充了滚动到 index 的能力。
在性能方面,补充了瀑布流布局优化、局部刷新优化、卡片分帧优化和滑动曲线优化。
▐ Powe****rScrollView 瀑布流布局
PowerScrollView 瀑布流布局提供了纵向布局、横向布局、混排布局(横向卡片和普通卡片混排)。现在闲鱼大部分列表页面均采用 PowerScrollView 的瀑布流布局,如首页同城页、搜索结果页等。
▐ PowerS****crollView 瀑布流布局优化
首先通过常规的缓存优化,缓存每个卡片左上角 x 值和属于哪一列。
相比 SliverGrid 卡片是并排进入列表区域,而瀑布流布局