简介
APP匆忙上线之后,就提出了性能优化的需求。
一路走来,还是有了一点点进步。
图片
性能优化,图片是绕不开的话题。
图片分为本地图片和网络图片两种。
一般本地图片不存在性能问题,而网络图片存在性能和体验等方面的制约。
图片组件
image.png-
以上构造方法,我们用得多的是
Image.asset
,本地资源图片,没什么好选的,就用这个。并且本地图片除了占包大小,在性能上也是无可挑剔,不需要考虑。 -
Image.memory
在很少的场合用,比如登录注册过程的验证码图片。这个也不存在性能问题。 -
参考文章: Flutter中的 Image图片组件的详解
网络图片
使用历史
- 一般网络图片的显示,有下面三种选择:
参考文章: Flutter - 加载网络图片的几种方式
-
一开始我们用的是
Image.network
; -
后来,需要占位图,用过一段时间的
FadeInImage
; -
再后来,要加强体验,想到图片缓存,找到了插件
CachedNetworkImage
;现在基本上用这个。
缩略图
阿里CDN服务器提供缩略图服务,网络图片尺寸变小,流量节省,性能也会提升。当然,相应的,图片清晰度会下降,体验会降低。这个就需要一个平衡。
我们的首页图片非常多,就用到了这个服务。用起来也非常简单,按照规则,修改图片的url就可以了。
/// 拼接参数,进行图片缩放
/// w和h要一样大
/// w和h要整数
/// w和h只能整10和整100
/// w和h不能根据屏幕自适应(不能保证整10整100)
/// 目前只有alicdn才能这么做;比如微店的geilicdn就不行
String url = item['goodsImg'] ?? '';
if (url.contains('alicdn')) {
int w = 400;
int h = 400;
url = '${url}_${w}x$h.jpg';
}
缓存管理
-
Flutter遇到
null
或者数组越界等一般不会崩溃,而是直接白屏;这个很好,白屏比崩溃好多了。 -
Flutter的内存目前大约2G左右,一旦内存占用过大,程序退出,现象和崩溃差不多。
-
一开始
CachedNetworkImage
没有提供缓存管理,导致内存占用过大,程序退出。 -
引入
flutter_cache_manager
之后就好多了。 -
再后来,发现
flutter_cache_manager
到达临界值的时候,切换非常慢。所以,根据不同的页面和使用场景,提供不同的配置。
class CustomCacheManager {
static const key = 'image_cache_key';
static CacheManager instance = CacheManager(
Config(
key,
stalePeriod: const Duration(days: 7),
maxNrOfCacheObjects: 100,
),
);
/// 首页购物时报采用单独的缓存
static const homeRealTimeKey = 'homeRealTimeKey';
static CacheManager homeRealTimeInstance = CacheManager(
Config(
homeRealTimeKey,
stalePeriod: const Duration(hours: 2),
maxNrOfCacheObjects: 200,
),
);
/// 首页购物时报采用单独的缓存
static const goodsDetailKey = 'goodsDetailKey';
static CacheManager goodsDetailInstance = CacheManager(
Config(
goodsDetailKey,
stalePeriod: const Duration(hours: 2),
maxNrOfCacheObjects: 200,
),
);
}
sliver家族
-
原先我们很多页面是通过
SingleChildScrollView
或者ListView
来实现的;后来反馈说滑动时卡顿明显。
` -
后来,这几个页面都换成了
CustomScrollView
, 使用了sliver
家族组件,卡顿问题好了很多。 -
sliver
家族也成了性能优化的第一选择。现在,但凡需要解决卡顿问题的页面,先用上CustomScrollView
试试再说。
ListView嵌套
-
表格套表格的时候怎么办?比如我们的购物车,第一层是订单列表,可滑动,这个理所当然。
-
每个订单还包含若干个商品。这个我们定义为不可滑动。一般的代码如下:
ListView(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: pis.map((e) => _piCell(get, e)).toList(),
)
-
优化的时候,第一时间将最外层的
ListView
替换成了CustomScrollView
,但是以往屡试不爽的绝招不管用了,仍然卡顿。 -
后来,看到了上面不可滑动的表格。有了个想法:“表格不可滑动,并且也没有用到构建函数,这和
Column
有什么区别?” -
抱着试试看的想法,把上面的不可滑动表格用
Column
来替换,还真解决了困扰已久的卡顿问题。
Column(
children: pis.map((e) => _piCell(get, e)).toList(),
)
- 原因不清楚,并且这样的例子只有一个,所以不好下结论。估计
shrinkWrap: true, physics: const NeverScrollableScrollPhysics(),
这个组合的ListView
确实性能不好。
Raster
- 翻译是光栅,据说跟GPU有关,也就是画图。就是flutter engine层的画图操作。如果这个耗时过大,和原生的离屏渲染有点像。这里是调用了saveLayer()函数,比较耗性能。
这方面考虑的因素是圆角,透明度,阴影。对应的组件是Clip, Opacity,shadows属性
这里的意思是默认三个钩都打上,也就是3个方面都检测。然后一个一个去掉,看性能是否有改善。这样就能反推出影响性能的到底是Clip, Opacity,shadows中的哪一个。
-
下面这个视频解释说
shrinkWrap: true, physics: const NeverScrollableScrollPhysics(),
确实性能不好。并且解决的方案不是替换为Column
,而是替换为Sliver
家族
ShrinkWrap 对比 Slivers | 解码 Flutter -
这里已经到了
Flutter
三棵树之外的Layer Tree
,是Flutter Engine
提交给GPU
的时候,大多数时候跟大图展示有关。圆角Clip
,透明度Opacity
,阴影Shadows
这些用在大图上,容易引起性能问题。
耗时函数
-
耗时函数有时候也会引起性能问题。比如
build
方法前面先来一些耗时计算,然后再返回Widget
,那么就会引起Performance
中build
阶段过长。 -
打开
DevTools
- 切换到
CPU Profiler
选项卡
-
三步操作,等待
企业微信截图_98031bf4-8f10-4b1c-a3e2-a26c31a887fe.pngDevTools
记录函数操作时间
-
过滤系统调用
- 观察列表
- 查看代码
函数名称,文件名称,所在行数都有了,到对应的代码文件看看
@override
Map<String, Color?> get lightInfo => {
PandaColorSring.coltheme: const Color(0xff2865ff),
PandaColorSring.colffffff: const Color(0xffffffff),
PandaColorSring.col000000: const Color(0xff000000),
// ... ...
}
这只是一个数组,存放了所有
light
模式下的颜色。对应的,还有darkInfo
,存放了所有dark
模式下的颜色定义。
-
把这个数组情况,再连接上
Performance
,卡顿情况立马好转,说明卡顿的原因确实是这里。 -
如何修改?
上面的两个Map
都用了get
关键字。这个其实是函数。每次访问到的时候,都会执行一遍。好处是每次都刷新,坏处当然是耗性能。去掉get
关键字,作为静态的Map
,性能就好了,卡顿明显减少。
@override
Map<String, Color?> lightInfo = {
PandaColorSring.coltheme: const Color(0xff2865ff),
PandaColorSring.colffffff: const Color(0xffffffff),
PandaColorSring.col000000: const Color(0xff000000),
// ... ...
}
这样改会有警告,集成重写,需要
get
关键字。所以可以考虑去掉继承,重新整理一下。
暗黑模式检查
-
GetX
中,检查是否暗黑模式的函数耗时严重
Get.isDarkMode
-
一开始,每次取颜色就检查一次是否暗黑模式,导致性能影响较大
-
为了提升性能,将状态保存在一个静态变量中,只有在切换模式的时候,才修改静态变量。
这样做,功能不变,但是性能提升明显。
/// 记忆状态,默认不是暗黑模式
static bool isDarkMode = false;
/// 在需要的时候检查状态,更新静态变量
static checkDarkMode() {
String type = LocalStorageUtil.getThemeType();
if (type == 'auto') {
isDarkMode =
(MediaQuery.of(Get.context!).platformBrightness == Brightness.dark);
} else {
isDarkMode = Get.isDarkMode;
}
}
/// 对外接口,直接返回静态变量,提升性能
static bool isdark() {
return isDarkMode;
}
-
checkDarkMode()
只要跟随模式变化函数就好了。模式改变,更新一下静态变量
static updateTheme() {
/// 这里需要delay,不能直接调用Get.forceAppUpdate();
Future.delayed(const Duration(milliseconds: 200), (() {
Get.forceAppUpdate();
/// 状态更新后,检查模式,更新静态变量
checkDarkMode();
}));
}
性能分析
-
有好几次,产品或者视觉说某某页面卡顿,但是我自己试着滑动了一下,感觉还好啊。到底卡不卡呢?该怎么判断?
-
Flutter
的Performance
选项卡,就是干这个的。简单理解,可以认为,蓝色柱子没事,一旦出现红色柱子,那就是有性能问题,那就是卡。 -
检查性能的时候,需要讲模式设置为profile。将那几个我自己认为还可以的页面跑了一下,确实有红色柱子时不时冒出来,看来确实“卡”,只是我没有感觉到而已。
- 性能分析工具很强大,下面的参考文章很好,先保存起来,有时间慢慢看
Using the Performance view
Flutter性能优化—UI
2022 Flutter Performance 性能调试工具 devTools
网友评论