官方文档中比较浅显的一些
1.谨慎使用 saveLayer()
- ShaderMask
- ColorFilter
- Chip— 当 disabledColorAlpha != 0xff 的时候,会调用 saveLayer()
- Text— 当有 overflowShader 时,会调用saveLayer()
2.Opacity 别用 方案 :
Image.network(
'https://raw.githubusercontent.com/flutter/assets-for-api-docs/main/packages/diagrams/assets/blend_mode_destination.jpeg',
color: const Color.fromRGBO(255, 255, 255, 0.5),
colorBlendMode: BlendMode.modulate
)
要在图像中实现淡入淡出,请考虑使用 FadeInImage widget
- Clipping 不会调用 saveLayer() (除非明确使用 Clip.antiAliasWithSaveLayer),因此这些操作没有 Opacity 那么耗时,但仍然很耗时,所以请谨慎使用。
- 加载动画使用骨骼动画? shadermask
https://docs.flutter.cn/cookbook/effects/shimmer-loading
3.animator 中千万别在vsync值监听中去setstate
4.如果大多数 children widget 在屏幕上不可见,请避免使用返回具体列表的构造函数(例如 Column() 或 ListView()),以避免构建成本。
避免在 Widget 对象上重写 operator ==。虽然这看起来有助于避免不必要的重建,但在实践中,它实际上损害了性能,因为这是 O(N²) 的行为。只有 leaf widget(没有子的 widget)是个例外,在这种特殊的情况下,比较 widget 的属性可能比重建 widget 更加有效,也能更少改变 widget 的配置。即使在这种情况下,最好还要缓存 widget,因为哪怕有一次对 operator == 进行覆盖也会导致全面性能的下降,编译器也会因此不再认为调用总是静态的。
5.监听的指标 Raster Build UI线程 jank 包含
要避免 Flutter 中的 Scroll Jank(滚动卡顿),可以从以下几个方面入手,结合具体场景进行优化:
1. 理解 Scroll Jank 的根源
帧率不稳定: 滚动流畅需要稳定的 60 FPS(或更高的刷新率)。任何导致帧率下降的操作都可能引起卡顿[参考资料缺失]。
复杂的构建/渲染: 在滚动过程中,如果 Flutter 需要进行大量的 Widget 构建或复杂的渲染计算,就会占用主线程时间,导致掉帧[参考资料缺失]。
资源加载延迟: 滚动时加载图片、网络数据等资源,如果加载速度慢,会阻塞 UI 线程[参考资料缺失]。
2. 优化策略
a. 减少 Widget 重建
使用
const
关键字: 对于静态的、不变的 Widget,使用const
关键字可以避免不必要的重建[参考资料缺失]。
const Text('This is a static text');
使用
const
Widget 作为子节点: 如果 Widget 的子节点是const
的,那么当父 Widget rebuild 时,子节点可以跳过 rebuild[参考资料缺失]。使用
Key
: 当列表中的 Widget 顺序发生变化时,Flutter 默认会尝试复用现有的 Widget。但如果 Widget 的内容也发生了变化,就会导致不必要的 rebuild。使用Key
可以帮助 Flutter 正确地识别和复用 Widget[参考资料缺失]。使用
ListView.builder
或SliverList
: 这两种方式只构建可见区域的 Widget,避免一次性构建大量 Widget[参考资料缺失]。使用
AutomaticKeepAliveClientMixin
: 对于PageView
或TabBarView
中的页面,可以使用AutomaticKeepAliveClientMixin
来保持页面的状态,避免每次切换时都重新构建[参考资料缺失]。b. 优化图片加载
使用
CachedNetworkImage
:CachedNetworkImage
库可以缓存网络图片,避免重复加载[参考资料缺失]。使用占位符: 在图片加载完成之前,显示一个占位符,避免 UI 闪烁[参考资料缺失]。
预加载图片: 在滚动开始之前,预先加载一部分图片,减少滚动过程中的加载延迟[参考资料缺失]。
调整图片大小: 加载适合屏幕尺寸的图片,避免加载过大的图片[参考资料缺失]。
c. 异步处理耗时操作
使用
FutureBuilder
或StreamBuilder
: 将网络请求、数据库查询等耗时操作放在异步任务中执行,避免阻塞 UI 线程[参考资料缺失]。使用
compute
函数: 对于 CPU 密集型任务(例如复杂的数学计算),可以使用compute
函数在后台线程中执行[参考资料缺失]。d. 避免在 Build 方法中执行耗时操作
Build 方法应该只负责构建 UI,避免在其中执行任何耗时操作。耗时操作应该放在其他地方执行,例如
initState
、didChangeDependencies
或事件处理函数中[参考资料缺失]。e. 减少 Overdraw
Overdraw 指的是在同一像素上绘制多次。过多的 Overdraw 会浪费 GPU 资源,导致性能下降。可以使用 Flutter DevTools 中的 Overdraw 可视化工具来检测 Overdraw 情况,并进行优化[参考资料缺失]。
f. 优化自定义绘制
如果使用了
CustomPaint
进行自定义绘制,需要确保绘制逻辑是高效的。避免在paint
方法中进行复杂的计算或资源加载[参考资料缺失]。可以使用
shouldRepaint
方法来控制是否需要重新绘制。只有当绘制内容发生变化时,才需要重新绘制[参考资料缺失]。g. 使用 Profiler 进行性能分析
Flutter DevTools 提供了强大的 Profiler 工具,可以帮助你分析应用的性能瓶颈。通过 Profiler,你可以找到导致卡顿的具体原因,并进行针对性的优化[参考资料缺失]。
h. 启用 Impeller 渲染引擎
Impeller 是 Flutter 新的渲染引擎,它通过预编译着色器等技术,可以提高渲染性能,减少卡顿。目前 Impeller 在 iOS 上默认启用,Android 上还在开发中[参考资料缺失]。
3. 代码示例
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
class MyList extends StatelessWidget {
final List<String> imageUrls;
MyList({Key? key, required this.imageUrls}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: imageUrls.length,
itemBuilder: (context, index) {
return Card(
child: Row(
children: [
CachedNetworkImage(
imageUrl: imageUrls[index],
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
width: 100,
height: 100,
fit: BoxFit.cover,
),
SizedBox(width: 16),
Expanded(
child: Text('Image ${index + 1}'),
),
],
),
);
},
);
}
}
总结
避免 Scroll Jank 需要综合考虑多个因素,包括减少 Widget 重建、优化图片加载、异步处理耗时操作、减少 Overdraw 等。通过使用 Flutter DevTools 进行性能分析,可以帮助你找到具体的瓶颈,并进行针对性的优化。 记住,针对不同的场景,优化的侧重点也会有所不同。
一、构建优化(Build 阶段优化)
核心原则:减少 Widget 树重建范围和频次
-
const
使用原则:// ✅ 正确:使用 const 构造函数 const ListTile(title: const Text('Title')); // ❌ 错误:所有元素都不带 const ListTile(title: Text('Title'));
原理:
const
组件会被编译为编译期常量,有效减少重建时的对象创建开销 -
Widget 拆分标准:
- 业务逻辑分离:逻辑操作与布局代码隔离
- 频繁更新的部分独立成子Widget(如动画元素)
- 示例:
// 拆分前:整个列表项在父组件中构建 itemBuilder: (ctx, i) => ListTile( leading: Image.network(data[i].url), title: Text(data[i].title), subtitle: _buildComplexContent(), ) // 拆分后:独立组件优化 itemBuilder: (ctx, i) => ListItem(data[i]) class ListItem extends StatelessWidget { final Data data; const ListItem(this.data); @override Widget build(BuildContext context) { return ListTile( leading: OptimizedImage(data.url), title: Text(data.title), subtitle: ContentWidget(data.content), ); } }
二、内存管理
核心原则:合理控制对象生命周期
-
ListView 优化铁律:
- 禁用
addAutomaticKeepAlives
(分页加载场景) - 动态保持关键状态:
// 自定义条件保留状态组件 class ConditionalKeepAlive extends StatefulWidget { final bool keepAlive; final Widget child; const ConditionalKeepAlive({ required this.keepAlive, required this.child }); @override _ConditionalKeepAliveState createState() => _ConditionalKeepAliveState(); } class _ConditionalKeepAliveState extends State<ConditionalKeepAlive> with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => widget.keepAlive; @override Widget build(BuildContext context) { super.build(context); return widget.child; } }
- 禁用
-
图片缓存策略:
CachedNetworkImage( imageUrl: url, memCacheWidth: (MediaQuery.of(context).size.width * 2).toInt(), // 物理像素适配 maxWidthDiskCache: 1024, // 服务器端大图时限制本地存储尺寸 filterQuality: FilterQuality.low, // 降低渲染质量节省GPU资源 )
三、滚动性能优化(List/Grid 核心优化点)
核心指标:滚动帧率稳定在 60fps
-
基准优化方案:
ListView.builder( itemCount: 1000, itemExtent: 80, // ✅ 显式设定行高(避免动态计算) cacheExtent: 500, // ✅ 预加载区域调整 addAutomaticKeepAlives: false, // ❗️禁用自动保持 addRepaintBoundaries: true, // ✅ 智能添加重绘隔离 physics: const BouncingScrollPhysics(), // ❗️低端机用ClampingScrollPhysics )
-
分页加载优化公式:
加载公式:触发阈值 = 最大滚动距离 - 当前偏移 公式实现: if (scrollOffset >= maxScrollExtent - triggerThreshold) { loadNextPage(); }
代码实现:
void _scrollListener() { final max = _controller.position.maxScrollExtent; final current = _controller.position.pixels; if (current >= max - 200) { // 提前200像素加载 _loadData(); } }
四、渲染管线优化
核心原理:减少 GPU 合成层数量
-
重绘隔离技巧:
- 动态判断是否添加隔离层:
Widget build(BuildContext context) { final needsBoundary = hasAnimations || hasFrequentUpdates; return needsBoundary ? RepaintBoundary(child: _realContent()) : _realContent(); }
-
图层合并优化:
// ✅ 正确:层级扁平化 Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), boxShadow: [/*...*/], ), ) // ❌ 错误:过度嵌套 Container( color: Colors.white, child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Container( decoration: BoxShadow(...), ), ), )
五、关键性能指标及调优工具
诊断指标对照表:
指标 | 健康范围 | 危险信号 | 优化措施 |
---|---|---|---|
FPS (帧率) | ≥58 fps | <45 fps | 检查渲染管线 |
UI 线程耗时 | <16ms | >30ms | 优化build方法 |
GPU 线程耗时 | <8ms | >12ms | 减少图层合成 |
对象创建数/帧 | <1000 | >2000 | 检查对象复用 |
Scroll jank | 0-2次/滚动 | >5次/滚动 | 列表项优化 |
工具链使用指南:
# 性能分析专用构建命令
flutter run --profile --purge-persistent-cache
# 调试包分析
flutter build apk --analyze-size
flutter build appbundle --target-platform android-arm64 --analyze-size
六、进阶性能模式(针对复杂场景)
-
按需渲染模式:
class SmartListView extends StatefulWidget { @override _SmartListViewState createState() => _SmartListViewState(); } class _SmartListViewState extends State<SmartListView> { final _visibleIndexes = <int>{}; void _handleScroll(ScrollMetrics metrics) { final first = metrics.minScrollExtent; final last = metrics.maxScrollExtent; final newIndexes = calculateVisibleItems(first, last); if (newIndexes != _visibleIndexes) { setState(() => _visibleIndexes = newIndexes); } } @override Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: DataModel.itemsNotifier, builder: (ctx, items, _) { return ListView.builder( itemCount: items.length, itemBuilder: (ctx, index) { return VisibilityTracker( visible: _visibleIndexes.contains(index), child: ListItem(item: items[index]), ); }, ); }, ); } }
-
内存压缩方案:
// 图片内存优化组件 class CompressedImage extends StatelessWidget { final String url; const CompressedImage(this.url); @override Widget build(BuildContext context) { return Image.network( url, cacheWidth: MediaQuery.of(context).size.width.toInt() * 2, filterQuality: FilterQuality.low, loadingBuilder: (ctx, child, progress) { return progress == null ? child : Shimmer.fromColors(...); // 内存友好的加载动画 }, ); } }
结语:性能优化四步法则
-
测:每次修改后用
devtools
性能面板验证 - 断:通过时间线定位具体问题(构建、布局、绘制、合成)
- 优:针对性应用上述优化方案
- 衡:权衡优化效果与复杂度,避免过度优化
记住:用真机测不要用假机测原因是模拟器有些很大的差距 ❗️