列表流畅度优化

这是一个通用的流畅度优化方案,通过分帧渲染优化由构建导致的卡顿,例如页面切换或者复杂列表快速滚动的场景。

代码中 example 运行在 VIVO X23(骁龙 660),在相同的滚动操作下优化前后 200 帧采集数据指标对比(录屏在文章最后):

优化前 优化后

监控工具来自:fps_monitor,指标详细信息:页面流畅度不再是谜!调试神器开箱即用,Flutter FPS检测工具

  • 流畅:一帧耗时低于 18ms
  • 良好:一帧耗时在 18ms-33ms 之间
  • 轻微卡顿:一帧耗时在 33ms-67ms 之间
  • 卡顿:一帧耗时大于 66.7ms

采用分帧优化后,卡顿次数从 平均 33.3 帧出现了一帧,降低到 200 帧中仅出现了一帧,峰值也从 188ms 降低到 90ms。卡顿现象大幅减轻,流畅帧占比显著提升,整体表现更流畅。下方是详细数据。

优化前 优化后
平均多少帧出现一帧卡顿 33.3 200
平均多少帧出现一帧轻微卡顿 8.6 66.7
最大耗时 188.0ms 90.0ms
平均耗时 27.0ms 19.4ms
流畅帧占比 40% 64.5%

页面切换流畅度提升

在打开一个页面或者 Tab 切换时,系统会渲染整个页面并结合动画完成页面切换。对于复杂页面,同样会出现卡顿掉帧。借助分帧组件,将页面的构建逐帧拆解,通过 DevTools 中的性能工具查看。切换过程的峰值由 112.5ms 降低到 30.2 ms,整体切换过程更加流畅。


如何使用?

项目依赖:

pubspec.yaml 中添加 keframe 依赖

dependencies:keframe: version

组件仅区分非空安全与空安全版本

非空安全使用: 1.0.1

空安全版本使用: 2.0.1

github 地址:https://github.com/LianjiaTech/keframe

pub 查看:https://pub.dev/packages/keframe

Dont forget star ~

快速上手:

如下图所示

假如现在页面由 A、B、C、D 四部分组成,每部分耗时 10ms,在页面时构建为 40ms。使用分帧组件 FrameSeparateWidget 嵌套每一个部分。页面构建时会在第一帧渲染简单的占位,在后续四帧内分别渲染 A、B、C、D。

对于列表,在每一个 item 中嵌套 FrameSeparateWidget,并将 ListView 嵌套在 SizeCacheWidget 内即可。


构造函数说明

FrameSeparateWidget :分帧组件,将嵌套的 widget 单独一帧渲染

类型 参数名 是否必填 含义
Key key
int index 分帧组件 id,使用 SizeCacheWidget 的场景必传,SizeCacheWidget 中维护了 index 对应的 Size 信息
Widget child 实际需要渲染的 widget
Widget placeHolder 占位 widget,尽量设置简单的占位,不传默认是 Container()

SizeCacheWidget:缓存子节点中,分帧组件嵌套的实际 widget 的尺寸信息

类型 参数名 是否必填 含义
Key key
Widget child 子节点中如果包含分帧组件,则缓存实际的 widget 尺寸
int estimateCount 预估屏幕上子节点的数量,提高快速滚动时的响应速度

方案设计与分析:

卡顿的本质,就是 单帧的绘制时间过长。基于此自然衍生出两种思路解决:

1、减少一帧的绘制耗时,因为导致耗时过长的原因有很多,比如不合理的刷新,或者绘制时间过长,都有可能,需要具体问题具体分析,后面我会分享一些我的优化经验。

2、在不对耗时优化下,将一帧的任务拆分到多帧内,保证每一帧都不超时。这也是本组件的设计思路,分帧渲染。

如下图所示:

原理并不复杂,问题在于如何在 Flutter 中实践这一机制。

因为涉及到帧与系统的调度,自然联想到看 SchedulerBinding 中有无现成的 API。

发现了 scheduleTask 方法,这是系统提供的一个执行任务的方法,但这个方法存在两个问题:

  • 1、其中的渲染任务是优先级进行堆排序,而堆排序是不稳定排序,这会导致任务的执行顺序并非 FIFO。从效果上来看,就是列表不会按照顺序渲染,而是会出现跳动渲染的情况

  • 2、这个方法本身存在调度问题,我已经提交 issue 与 pr,不过一直卡在单元测试上,如果感兴趣可以以在这里交流谈论。

fix: Tasks scheduled through ‘SchedulerBinding.instance.scheduleTask’… #82781

最终,参考这个设计结合 endOfFrame 方法的使用,完成了分帧队列。整个渲染流程变为下图所示:

对于列表构建场景来说,假设屏幕上能显示五个 item。首先在第一帧的时候,列表会渲染 5 个占位的 Widget,同时添加 5 个高优先级任务到队列中,这里的任务可以是简单的将占位 Widget 和实际 item进行替换,也可通过渐变等动画提升体验。在后续的五帧中占位 Widget 依次被替换成实际的列表 item。

在 ListView流畅度翻倍!!Flutter卡顿分析和通用优化方案 这篇文章中有更加详细的分析。


一些展示效果(Example 说明请查看 Github)

卡顿的页面往往都是由多个复杂 widget 同时渲染导致。通过为复杂的 widget 嵌套分帧组件 FrameSeparateWidget。渲染时,分帧组件会在第一帧同时渲染多个 palceHolder,之后连续的多帧内依次渲染复杂子项,以此提升页面流畅度。

例如 example 中的优化前示例:

ListView.builder(itemCount: childCount,itemBuilder: (c, i) => CellWidget(color: i % 2 == 0 ? Colors.red : Colors.blue,index: i,),)

其中 CellWidget 高度为 60,内部嵌套了三个 TextField 的组件(整体构建耗时在 9ms 左右)。

优化仅需为每一个 item 嵌套分帧组件,并为其设置 placeHolder(placeHolder 尽量简单,样式与实际 item 接近即可)。

在列表情况下,给 ListView 嵌套 SizeCacheWidget,同时建议将预加载范围 cacheExtent 设置大一点,例如 500(该属性默认为 250),提升慢速滑动时候的体验。

(占位与实际列表项不一致时,首次渲染抖动,二次渲染正常)

此外,也可以给 item 嵌套透明度/位移等动画,优化视觉上的效果。

效果如下图:


分帧的成本

当然分帧方案也非十全十美,在我看来主要有两点成本:

1、额外的构建开销:整个构建过程的构建消耗由「n * widget消耗 」变成了「n *( widget + 占位)消耗 + 系统调度 n 帧消耗」。可以看出,额外的开销主要由占位的复杂度决定。如果占位只是简单的 Container,测试后发现整体构建耗时大概提升在 15 % 左右。这种额外开销对于当下的移动设备而言,成本几乎可以不计。

2、视觉上的变化:如同上面的演示,组件会将 item 分帧渲染,页面在视觉上出现占位变成实际 widget 的过程。但其实由于列表存在缓存区域(建议将缓存区调大),在高端机或正常滑动情况下用户并无感知。而在中低端设备上快速滑动能感觉到切换的过程,但比严重顿挫要好。


优化前后对比演示

注:gif 帧率只有20

优化前 优化后

最后:一点点思考

列表优化篇到此告一段落,在整个开源实践过程中,有两点感触较深:

「点」与「面」的关系

我们在思考技术方案的时候可以由「点」到「面」,站在一个较高视野去想问题的本质。

而在执行的时候则需要由「面」到「点」的进行逐级拆分,抓住问题的关键节点,并且拟定进度计划,逐步破解。

很多时候,这种向上和向下的逻辑思维才是我们的核心竞争力

以不变应万变

对于未知的东西,我们往往会过度的将它想复杂。在一开始分析列表构建原理的时候,我也苦于无从下手,走了很多弯路。但其实对于 Flutter 这套 「UI」 框架而言,核心仍然在于三棵树的构建机制

在这套体系内,抓住不变的东西,无论是生命周期、路由等等问题都可以从里面找到答案。我之前也有过总结:Flutter 核心渲染机制 与 Flutter路由设计与源码解析 。

下一阶段,我会聚焦于 Dart 中的 I/O 部分,结合计算机网络原理由浅入深地进行分析与实践。从底层原理出发,与大家一起学习 「不变的原理」,一起进步。如果你有任何疑问可以通过公众号与联系我,如果文章对你有所启发,希望能得到你的点赞、关注和收藏,这是我持续写作的最大动力。Thanks~

如果遇到了任何问题与建议,欢迎在评论区或者公众号联系我,或者 issue 和 pr。

公众号:进击的Flutter或者 runflutter 里面整理收集了最详细的Flutter进阶与优化指南,欢迎关注。

已开源!Flutter 流畅度优化组件 keframe相关推荐

  1. 已开源!Flutter 流畅度优化组件 Keframe | 开发者说·DTalk

    本文原作者: Nayuta,原文发布于: 进击的 Flutter 列表流畅度优化 这是一个通用的流畅度优化方案,通过分帧渲染优化由构建导致的卡顿,例如页面切换或者复杂列表快速滚动的场景. 代码中 ex ...

  2. 已开源!Flutter 基于分帧渲染的流畅度优化组件 Keframe

    大家好,这里是承香墨影! 今天给大家推荐一个,Flutter 中利用分帧渲染优化流程度的开源库,刚开源,还热乎着.这次开源可真波折,看着 @Nayuta 前前后后在公司内部流程走了一个多月吧,太艰难了 ...

  3. 淘特 Flutter 流畅度优化实践

    作者:谢伟(韦圣) 不同的业务背景引出不同的技术诉求,"用户体验特爽"是淘特的不懈追求,本文将介绍笔者加入淘特以来在Flutter流畅度方面的诸多优化实践,这些优化不涉及Engin ...

  4. Flutter 流畅度优化实践总结

    本篇内容来自ArchSummit会议分享 作者介绍: 张云龙(云从),闲鱼客户端专家.先后在网易.字节.阿里任职移动端研发.目前在阿里巴巴闲鱼技术部,目前负责闲鱼 app 包大小.流畅度.启动等端体验 ...

  5. 淘特 Flutter 流畅度优化实践 · 二期

    作者:谢伟(韦圣) "在上一篇<淘特 Flutter 流畅度优化实践>中说到,虽然一期效果较为明显,但距离极致的用户体验仍有不小的差距.去年,淘特端架构联合业务团队共同发起&qu ...

  6. Android应用优化之流畅度优化实操

    上一篇流畅度概念向大家详细地描述了VSync机制和Choreographer编舞者的用法.可能所讲解的内容偏向理论概念,因此这篇是流畅度优化实操,整篇主要分三层,UI层.代码逻辑层.IO层来讲述各个优 ...

  7. Android性能优化——界面流畅度优化

    Android性能优化--界面流畅度优化 序言 首先流畅度不仅仅是受到代码的影响.也会跟机器的硬件配置有关系.所以第一点需要明确的是,流畅度最低保证在哪个硬件配置之上.这样有了一个基点之后,才能比较好 ...

  8. Android App界面和流畅度优化

    Android App界面和流畅度优化 所谓界面和流畅度优化,就是尽可能多地消除用户可直接感知的.影响用户操作体验的bug 1.人为在UI线程中做轻微耗时操作,导致UI线程卡顿 人为避免一切耗时操作 ...

  9. iOS性能优化系列篇之“列表流畅度优化”工具篇

    这一篇文章是iOS性能优化系列文章的的第二篇,主要内容是关于列表流畅度的优化.在具体内容的阐述过程中会结合性能优化的总体原则进行分析,所以建议大家在阅读这篇文章前先阅读一下上一篇文章:iOS性能优化系 ...

最新文章

  1. [转]run for a girl
  2. Linux常用内建命令笔记
  3. java arraylist排序_最全Java集合笔记
  4. java usecompressedclasspointers_聊聊jvm的CompressedClassSpace
  5. 手把手教你制作AppPreview视频并上传到appStore进行审核
  6. layui table数据渲染页面+筛选医生+在筛选日期一条龙2
  7. 小米11 Pro概念图曝光:曲面挖孔屏+后置五摄相机模组
  8. 微信dat文件用什么软件打开方式_2020微信dat文件解密工具怎么获取软件
  9. Java泛型对象的实例化
  10. Excel怎么隐藏指定文本单元格整行
  11. [树状数组模板] 洛谷P3368
  12. 万能python,画个滑稽来玩玩
  13. [Java实验 5] 异常处理
  14. CSS制作的32种图形效果 梯形 | 三角 | 椭圆 | 平行四边形 | 菱形 | 四分之一圆 | 旗帜
  15. 微信小程序使用nginx跳转第三方url
  16. 各位对IE放尊重点没有他,你怎么下载其他浏览器,你们等黑丝和白丝她来了!
  17. xlsx VLOOKUP 怎么用
  18. 自制英语翻译(调用有道翻译接口)
  19. 拆解IncServer网络库
  20. 与Java的初吻_ The First Kiss On Java

热门文章

  1. CRM哪家好?这5个CRM管理系统很好用!
  2. ATAx=0与Ax=0同解
  3. 图像数据增强2_albumentation 标注框同时修改(VOC、YOLO)
  4. 下载360图片(一)
  5. 集群运维:All datanodes DatanodeInfoWithStorage[10.21.131.179:50010,DS-6fca3fba-7b13-4855-b483-342df8432e
  6. 网友感到担忧!iOS 17支持第三方应用商店:这下跟安卓没区别了
  7. Mockito 框架用于单元测试
  8. 移动硬盘弹出时总是显示被占用,解决方案
  9. 桥梁通服务器物理连接成功,ZStack 实践汇|OSPF搭建与物理网络通信的“桥梁”
  10. VSFTPD FTP服务器搭建手册