Flutter性能优化实践 —— UI篇

本文主要探讨了Flutter应用中的UI性能优化,从复用、RepaintBoundary、加载策略、耗时计算、GPU优化等方面展开,提供了具体的优化方法,如预构建Widget、避免不必要的构建、按需加载和错峰加载策略、减少GPU压力等,旨在提高应用流畅度和减少卡顿现象。
摘要由CSDN通过智能技术生成

动画的使用在实际开发中很常见,但是一旦使用不当也会造成不必要的刷新,甚至会带来卡顿。

举一个deer中的例子,商品列表页中有一个商品操作菜单的呼入呼出动画(这里就不谈具体的实现效果了,有兴趣的可以去看源码)。一开始的写法如下:

AnimatedBuilder(
animation: animation,
builder:(_, __) {
return MenuReveal(
revealPercent: animation.value,
child: _buildGoodsMenu(context),
);
}
)

效果如下: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 这个动画看起来还是比较流畅的。顶部的性能图表(Performance Overlay)中,UI花费的时间平均在7.2ms/frame。比起16ms的安全标准来说已经非常好了。

但是我们来看看构建次数(呼入呼出各一次):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 这里仔细看就有点问题,动画执行时我们只希望可变的部分刷新(MenuReveal),但实际上连菜单中的按钮也一起刷新构建了。

那么优化的方法就是预构建菜单中的按钮,将_buildGoodsMenu(context)方法放在AnimatedBuilder之前执行再传入或是放在AnimatedBuilderchild中。

AnimatedBuilder(
animation: animation,
child: buildGoodsMenuContent(context), // <-----放在这里
builder:(
, child) {
return MenuReveal(
revealPercent: animation.value,
child: child // <----这里使用
);
}
)

效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 可以看到UI线程花费的时间在6ms/frame左右。这个提升还是比较大的(16%左右),虽然对于用户来说是无感知的。

再次看一下构建次数:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 那么提升的原因也就找到了,因为避免了不必要的构建。所以针对这类不依赖于动画的子Widget,预构建它可以显著提高性能。

类似这种builder/child的模式还有不少,你可以多多留意一下。

复用

  • 尽量使用const来定义一些不变的Widget,这相当于缓存一个Widget并复用它。

我之前看到过一篇博客,作者测试一个页面上构建1000个重复图标,结果使用const构造函数的,FPS大约高8.4%,内存使用量降低约20%。

当然作者也说了,实际一个页面上有1000个Widget也不现实。其实说这个点的原因也是希望大家能养成一个好习惯。

  • 添加GlobalKey也能复用widget。这个使用场景相对较少,可以了解一下。相关内容链接:说说Flutter中的Key

RepaintBoundary

这个我之前有详细介绍过,可以直接查看:说说Flutter中的RepaintBoundary,这里我就不重复说了。合理的使用RepaintBoundary可以减少不必要的刷新提升性能。

4.加载策略

按需加载

推荐使用ListView.builder来动态实现列表,而不是直接使用ListView静态创建。注意这里在使用ListView.builderitemBuilder来构建item时,可不要预构建Widget了。类似的Widget还有PageView.builderGridView.builder

PS:按需加载是一种策略,并不是仅仅依靠这几个类型的Widget。比如之前阿里AliFlutter的分享中,就有提到列表中加载图片的优化。通过判断图片的在屏和离屏,来合理回收图片,这样减小了内存的波动,同样也可以带来性能的提升。

错峰加载

错峰加载的目的是为了避免因同一时间的大量构建,而产生卡顿现象。这里我举一个例子:

在使用PageView.builder这个Widget时,我发现在左右滑动切换页面时会有卡顿的现象。使用timeline来分析发现两个问题,一是切换的页面比较复杂,比较耗时。二是页面构建的时间点在滑动中。

页面复杂的问题我进行了一定的优化,虽然有效果,但还是有卡顿发生。那么只能针对第二点再进行优化,我们先看一下PageView.相关源码:

return NotificationListener(
onNotification: (ScrollNotification notification) {
if (notification.depth == 0 && widget.onPageChanged != null && notification is ScrollUpdateNotification) {
final PageMetrics metrics = notification.metrics;
final int currentPage = metrics.page.round();
if (currentPage != _lastReportedPage) {
_lastReportedPage = currentPage;
widget.onPageChanged(currentPage);
}
}
return false;
},
child: Scrollable(),
);

代码很简单,如果我们设置了onPageChanged的监听,那么在滑动中(ScrollUpdateNotification)计算当前页的页码并返回(round方法,四舍五入)。所以在滑动到一半的时候,onPageChanged就会回调结果,我因为在这里触发了页面的刷新代码,导致了卡顿的发生。

其实在我熟知的安卓中,默认行为都是在滑动结束后才去加载页面数据。所以按照这个思路处理,调整一下加载策略。

修改代码如下:

NotificationListener(
onNotification: (ScrollNotification notification) {
if (notification.depth == 0 && notification is ScrollEndNotification) {
final PageMetrics metrics = notification.metrics;
final int currentPage = metrics.page.round();
if (currentPage != _lastReportedPage) {
_lastReportedPage = currentPage;
_onPageChange(currentPage);
}
}
return false;
},
child: PageView.builder(),
)

我们在PageView.builder上添加一个NotificationListener,同时修改ScrollUpdateNotificationScrollEndNotification。这样就自定义了我们的滑动监听事件,通过错峰加载保证了UI的流畅。

PS:在Flutter 1.17的重要改动中就有一条:在高速滚动时推迟图像解码。这也是运用了错峰加载的策略。

5.耗时计算

避免将一些耗时计算放在UI线程,我们可以把耗时计算放到Isolate去执行(多线程)。

举一个Flutter源码中的例子:

Future loadString(String key, { bool cache = true }) async {
final ByteData data = await load(key);
if (data == null)
throw FlutterError(‘Unable to load asset: k e y ′ ) ; i f ( d a t a . l e n g t h I n B y t e s < 10 ∗ 1024 ) / /

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值