闲鱼是怎么面向Flutter做技术体验升级的?

简介: 蛇打七寸,拨云见日——更快的flutter,更好的闲鱼

作者:闲鱼技术——非歌


背景

目前闲鱼的主要业务(搜索、详情等)都由Flutter来承载,虽然Flutter是高性能的跨端技术方案,但是随着业务快速迭代和业务复杂度不断提升,应用的性能表现却不尽如人意,主要体现在启动慢、页面加载慢,页面滑动操作不流畅、卡顿等,非常影响用户使用体验。
为此我们启动了闲鱼体验升级的项目,从用户使用闲鱼App的操作路径的关键节点出发,系统性的对应用做了优化工作,包括安装包瘦身、加快启动速度、页面加载时长优化、流畅度优化,最终提升了用户使用体验。
本文主要介绍闲鱼在Flutter侧的性能体验优化工作,主要分为三个部分:

  • 流畅度优化
  • 搜索结果页加载优化
  • 详情页加载优化

优化方案如下图:
优化方案综述.png


流畅度优化

先从最棘手的Flutter流畅度问题介绍。从线上数据和实际使用体感来看,搜索页、商品详情页的滑动流畅度都不尽如意,舆情中也有很多关于卡顿的反馈。
接下来分3个方面来介绍流畅度优化:

  • Flutter卡顿定位工具建设
  • 长列表loadmore优化
  • 滚动加载小图

Flutter卡顿定位工具建设

Flutter采用数据驱动的方式构建UI,官方的Flutter性能最佳实践指出

  • 避免在 build() 方法中进行重复且耗时的工作,因为当父 Widget 重建时,子 Wdiget 的 build() 方法会被频繁地调用
  • 避免在一个超长的 build() 方法中返回一个过于庞大的 Widget。把他们分拆成不同的 Widget,并进行封装

但是随着业务快速迭代,开发者往往会疏忽大意,写出低性能的代码。理想情况是,通过性能分析工具自动定位到问题代码,及时修改。
Flutter官方提供了Devtools性能分析工具,它的timeline界面可以逐帧分析应用的UI性能,cpu profiler界面来抓取卡顿过程中的耗时操作。
在研发阶段,我们可以使用这个工具来分析本地可以复现的卡顿问题。
但是,Devtools的局限性在于:

  • 只能用于在出现卡顿问题后去分析,无法做到监控卡顿问题。
  • 只能用于线下排查,对于线上反馈卡顿问题,因为没有足够的场景,也无能为力。

为此我们需要建设一个Flutter卡顿工具,用于在线上线下监控定位造成卡顿的耗时函数。
实现思路:我们知道release模式下的Dart代码是基于AOT编译的,业务代码和sdk都会编译成平台相关的机器码,所以我们是可以通过信号机制抓取native的堆栈,再通过符号化还原的方式来获取flutter堆栈。
卡顿工具实现图.png

我们通过卡顿工具定位排查出两类造成流畅度低的问题:耗时函数和过度渲染问题。

定位耗时函数

对于耗时函数导致的卡顿问题,可以通过查看堆栈定位到耗时函数。
卡顿工具-定位耗时函数.png
从调用栈可以看出channel的调用时数据序列化 和 反序列化比较耗时。
通过查看FxImage的代码,了解到resumeImage对应的native实现已经为空,flutter侧其实可以省去这一次channel调用。

定位过度渲染

在自动化阶段上报的数据有大量是如下所示渲染阶段的堆栈,无法定位到业务代码。
渲染阶段的堆栈.png

这是由于Flutter的刷新机制是中心化、异步的渲染机制,
业务层需要刷新界面元素,framework层会先把需要更新的元素标脏,然后等下一次引擎侧的渲染回调,在这次渲染回调中,对之前收集到的脏元素统一做更新。
这样的机制导致在抓取渲染阶段的堆栈有大量的Element.rebuild、update方法,都是flutter framework的函数调用栈,而缺失了业务侧代码,只看这些堆栈并不能帮我们定位到卡顿问题。

我们的思路是:通过增加渲染过程中的脏element信息,
根据标脏的element的复杂程度(我们根据元素的深度、长度、子节点数来计算复杂度)
可以帮助我们检测出是否有代码不规范导致刷新范围过大的问题。
计算脏element.png

通过这个方案,我们定位到详情页快速提问组件过度渲染问题:
卡顿工具-过度渲染.png

查看脏element信息可以看到,打脏的元素复杂度较高。
优化方案:
提高build效率,setState刷新数据下沉到叶子节点,将每个标签抽离成LabelStatefulWidget,只做局部刷新。

长列表loadmore优化

通过卡顿工具,我们发现搜索结果页列表滑动加载更多过程中会对整个列表容器打脏重建,造成比较严重的流畅度问题。
经过分析发现,因为加载更多数据返回后,追加列表数据后会调用setState,打脏容器widget。
并且由于我们有做预加载,在滑到底部前几屏时就去触发加载更多的逻辑,导致打脏重建widget的频率更高。
一期我们从业务层入手,预加载更多的数据回来并不去调用setState,而先是保存到内存,等滑到底部再去调用setState,重建列表容器。后面我们从列表容器底层去优化,loadmore时不会打脏重建整个列表容器,容器加载和滑动过程有较大的优化,提升体验比较明显。

优化前如下图,(红色区域表示打脏的widget)可以看到刷新了整个容器。
PowerScrollView优化前.gif

优化后,仅对新增的卡片做刷新
PowerScrollView优化后.gif

滚动加载小图

feeds流卡片包含大量的图片,在快速滑动过程中,加载大量图片对于内存和IO都是比较大的考验,影响流畅度,在低端机上尤其明显。
优化思路:在快速滚动过程中,只加载尺寸较小的模糊图,等到滚动停止后再渐进式的展示原图,并且在超出屏幕区域不加载原图,优化上屏体验。
效果如图:(为了演示效果,这里的用的是缩小5倍的小图)
滚动加载小图,停止加载大图.gif

小结

在经过优化之后,闲鱼详情页和搜索页流畅度 FPS 提升了 3 个点,低端机大卡顿次数降低一半,中高端机型上流畅度提升到 57 或以上,大卡顿次数接近 0。

详情页线上高可用 fps 数据如下:
详情页-线上低端机 fps

线上低端机 fps 曲线,横轴表示帧率区间,绿色为优化版本。曲线分布越靠右,流畅度越好。

详情页-线上高端机 fps

线上高端机 fps 曲线。绿色为优化版本

搜索页线上高可用 fps 数据如下:

搜索页-线上低端机 fps

线上低端机 fps 曲线。绿色为优化版本

搜索页-线上高端机 fps

线上高端机 fps 曲线。绿色为优化版本


搜索结果页加载优化

在优化流畅度问题之后,再来看下对于页面加载需要做哪些优化。
在优化之前,从搜索关键词到搜索结果展示过程中有较长loading。对于页面的加载速度优化,我们更多的从业务流程开始去找突破口,搜索结果页的打开过程如下:
搜索结果页打开流程.png

搜索结果页由Flutter实现,但它是从Native页面点击打开,在混合栈的背景下导致路由拦截到打开容器这一步有一定耗时。
我们可以通过 URL 携带预取信息,在 Native 进行跳转导航时同时进行异步并行的数据预取,可以减少页面打开的耗时。
同时因为搜索页面的请求RT相对比较高,一般页面进来了,还仍然在等待网络请求回来,所以如果在网络请求回来的时候再去做模板的预加载,大概率会命中。

优化之后的流程如下:
搜索结果页优化之后的打开流程.png

通过一定的并行手段,采用数据预取、模板预加载的方案,我们在Android低端机上将搜索结果页加载时长优化300ms。
同时在数据请求时展示骨架屏动画(lottie实现)代替小黄鱼loading,带给用户更好的使用体验。

优化前:
搜索页-优化前.gif

优化后:
搜索结果页加载-优化后.gif


详情页加载优化

对于详情页的加载优化,我们主要通过下面3个手段做优化:

  • FlutterBoost优化
  • 数据透传
  • 转场动画

FlutterBoost优化

闲鱼目前还是Native+Flutter的混合开发模式,通过FlutterBoost来处理混合栈页面的映射和跳转。
在FlutterBoost的open处理中,会通过startActivity()打开一个新的容器,而详情页的跳转场景中,大部分都是从Flutter页面跳转过来的,其实可以复用之前打开过的容器。
针对这样的应用场景,我们在FlutterBoost增加了一个新的特性,在Flutter页面打开一个新的Flutter页面时,可以选择两个Flutter页面共用一个Flutter容器(Activity、ViewController), 以加快页面打开速度、减少内存消耗,并且支持了Flutter实现页面切换的动画。

数据透传

在搜索结果页跳详情和猜你喜欢跳详情的场景中,详情的部分数据已经可以通过上一个接口拿到,我们可以把这部分数据透传带到详情页,在请求详细数据的过程中,先展示简单的内容,比如主图、标题、价格,等详细数据回来后再更新详情页,带来直出的体验效果。

转场动画

在前面数据透传的基础上,我们又通过转场动画,实现沉浸式页面的切换的效果,进一步地提升用户使用体验。
转场动画.png

实现思路:在路由导航过程中通过继承ModalRoute接管buildPage过程,对简化版详情页AnimateDetailPage做动画处理,监听动画状态,当动画完成后,再把详情页DetailPage挂到widget树上。

优化前:
详情页-优化前.gif

优化后:
详情页-优化后.gif


总结展望

总结下来,闲鱼Flutter的性能体验升级中,在技术细节优化方面,我们对主要页面加载时长、流畅度做了优化,除了针对业务流程上的优化之外,我们也沉淀了一些通用优化能力,比如数据预取、widget分帧上屏、长列表加载更多优化、长列表局部刷新能力、长列表滚动差值器算法优化。在视觉优化方面,突出年轻化的主题,设计使用了大量的微动画元素,Flutter Lottie动画使用在各个页面随处可见,例如:发布页、搜索结果加载页、以及各个加载刷新动画。在优化之后,闲鱼App操作变得更加流畅如丝般顺滑。

未来,我们还需要对Flutter引擎启动有更多的探索和思考,尤其是在首页切换到Flutter之后,对Flutter启动优化的工作是必须的。
另外,由于业务的快速迭代,前期的优化工作到后面很容易恶化。如何在业务快速变化的同时,既满足效率又保证性能,是接下来需要着手解决的问题,比如在代码集成合入前通过性能卡口,将性能不达标的代码打回并给出优化建议;将性能优化的手段内置到容器框架这一层。

相关文章
|
1月前
|
缓存 监控 前端开发
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
【4月更文挑战第30天】本文探讨了Flutter应用启动优化策略,包括理解启动过程、资源加载优化、减少初始化工作、界面布局简化、异步初始化、预加载关键数据、性能监控分析以及案例和未来优化方向。通过这些方法,可以缩短启动时间,提升用户体验。使用Flutter DevTools等工具可助于识别和解决性能瓶颈,实现持续优化。
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
|
1月前
|
开发框架 Dart 前端开发
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
【4月更文挑战第30天】对比 Flutter(Dart,强类型,Google支持,快速热重载,高性能渲染)与 React Native(JavaScript,庞大生态,热重载,依赖原生渲染),文章讨论了开发语言、生态系统、性能、开发体验、学习曲线、社区支持及项目选择因素。两者各有优势,选择取决于项目需求、团队技能和长期维护考虑。参考文献包括官方文档和性能比较文章。
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
|
1月前
|
前端开发 Android开发 iOS开发
【Flutter前端技术开发专栏】Flutter在Android与iOS上的性能对比
【4月更文挑战第30天】Flutter 框架实现跨平台移动应用,通过一致的 UI 渲染(Skia 引擎)、热重载功能和响应式框架提高开发效率和用户体验。然而,Android 和 iOS 的系统差异、渲染机制及编译过程影响性能。性能对比显示,iOS 可能因硬件优化提供更流畅体验,而 Android 更具灵活性和广泛硬件支持。开发者可采用代码、资源优化和特定平台优化策略,利用性能分析工具提升应用性能。
【Flutter前端技术开发专栏】Flutter在Android与iOS上的性能对比
|
1月前
|
Dart 前端开发 安全
【Flutter前端技术开发专栏】Flutter中的线程与并发编程实践
【4月更文挑战第30天】本文探讨了Flutter中线程管理和并发编程的关键性,强调其对应用性能和用户体验的影响。Dart语言提供了`async`、`await`、`Stream`和`Future`等原生异步支持。Flutter采用事件驱动的单线程模型,通过`Isolate`实现线程隔离。实践中,可利用`async/await`、`StreamBuilder`和`Isolate`处理异步任务,同时注意线程安全和性能调优。参考文献包括Dart异步编程、Flutter线程模型和DevTools文档。
【Flutter前端技术开发专栏】Flutter中的线程与并发编程实践
|
1月前
|
Dart 前端开发 测试技术
【Flutter前端技术开发专栏】Flutter开发中的代码质量与重构实践
【4月更文挑战第30天】随着Flutter在跨平台开发的普及,保证代码质量成为开发者关注的重点。优质代码能确保应用性能与稳定性,提高开发效率。关键策略包括遵循最佳实践,编写可读性强的代码,实施代码审查和自动化测试。重构实践在项目扩展时尤为重要,适时重构能优化结构,降低维护成本。开发者应重视代码质量和重构,以促进项目成功。
【Flutter前端技术开发专栏】Flutter开发中的代码质量与重构实践
|
1月前
|
存储 缓存 监控
【Flutter前端技术开发专栏】Flutter中的列表滚动性能优化
【4月更文挑战第30天】本文探讨了Flutter中优化列表滚动性能的策略。建议使用`ListView.builder`以节省内存,避免一次性渲染所有列表项。为防止列表项重建,可使用`UniqueKey`或`ObjectKey`。缓存已渲染项、减少不必要的重绘和异步加载大数据集也是关键。此外,选择轻量级组件,如`StatelessWidget`,并利用Flutter DevTools监控性能以识别和解决瓶颈。持续测试和调整以提升用户体验。
【Flutter前端技术开发专栏】Flutter中的列表滚动性能优化
|
1月前
|
Dart 前端开发 开发者
【Flutter前端技术开发专栏】Flutter中的性能分析工具Profiler
【4月更文挑战第30天】Flutter Profiler是用于性能优化的关键工具,提供CPU、GPU、内存和网络分析。它帮助开发者识别性能瓶颈,如CPU过度使用、渲染延迟、内存泄漏和网络效率低。通过实时监控和分析,开发者能优化代码、减少内存占用、改善渲染速度和网络请求,从而提升应用性能和用户体验。定期使用并结合实际场景与其它工具进行综合分析,是实现最佳实践的关键。
【Flutter前端技术开发专栏】Flutter中的性能分析工具Profiler
|
1月前
|
前端开发 数据处理 Android开发
【Flutter 前端技术开发专栏】Flutter 中的调试技巧与工具使用
【4月更文挑战第30天】本文探讨了Flutter开发中的调试技巧和工具,强调其在及时发现问题和提高效率上的重要性。介绍了基本的调试方法如打印日志和断点调试,以及Android Studio/VS Code的调试器和Flutter Inspector的使用。文章还涉及调试常见问题的解决、性能和内存分析等高级技巧,并通过实际案例演示调试过程。在团队协作中,有效调试能提升整体开发效率,而随着技术发展,调试工具也将持续进化。
【Flutter 前端技术开发专栏】Flutter 中的调试技巧与工具使用
|
1月前
|
Dart 前端开发 Java
【Flutter前端技术开发专栏】Flutter中的内存泄漏检测与解决
【4月更文挑战第30天】本文探讨了Flutter应用中的内存泄漏检测与解决方法。内存泄漏影响性能和用户体验,常见原因包括全局变量、不恰当的闭包使用等。开发者可借助`observatory`工具或`dart_inspector`插件监测内存使用。解决内存泄漏的策略包括避免长期持有的全局变量、正确管理闭包、及时清理资源、妥善处理Stream和RxDart订阅、正确 disposal 动画和控制器,以及管理原生插件资源。通过这些方法,开发者能有效防止内存泄漏,优化应用性能。
【Flutter前端技术开发专栏】Flutter中的内存泄漏检测与解决
|
1月前
|
缓存 监控 前端开发
【Flutter前端技术开发专栏】Flutter应用的性能调优与测试
【4月更文挑战第30天】本文探讨了Flutter应用的性能调优策略和测试方法。性能调优对提升用户体验、降低能耗和增强稳定性至关重要。优化布局(避免复杂嵌套,使用`const`构造函数)、管理内存、优化动画、实现懒加载和按需加载,以及利用Flutter的性能工具(如DevTools)都是有效的调优手段。性能测试包括基准测试、性能分析、压力测试和电池效率测试。文中还以ListView为例,展示了如何实践这些优化技巧。持续的性能调优是提升Flutter应用质量的关键。
【Flutter前端技术开发专栏】Flutter应用的性能调优与测试