简介
渲染性能直接影响应用的流畅度。本文介绍如何优化 Flutter 应用的渲染性能。
基本概念
渲染流程
- Layout - 计算大小和位置
- Paint - 绘制界面
- Composite - 合成图层
性能指标
- 帧率(FPS)
- 帧时间
- 掉帧数量
优化方法
1. 减少重建
// 使用 const 构造函数(和下面定义的 MyWidget 对应)
const MyWidget(
key: Key('my_widget'),
);
// 使用 StatelessWidget
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: Text('Hello'),
);
}
}
2. 按测量结果使用 RepaintBoundary
SliverChildBuilderDelegate 的 addRepaintBoundaries 默认是 true,会为列表子项添加重绘边界。ListView.builder 也通过对应的 sliver delegate 构建子项,所以通常不需要在 builder 中再手动给每个列表项套一层。
当 DevTools 或重绘可视化确认某个子树与周围内容的重绘时机明显不同时,再考虑增加边界。RepaintBoundary 会创建独立 Layer;它可能减少重复绘制,也会增加图层和内存开销,效果要在目标场景中测量。
3. 图层优化
// Opacity 会作用于整个 child 子树。
Opacity(
opacity: 0.5,
child: ExpensiveWidget(),
);
// 如果只需要半透明背景,只给背景颜色设置透明度;child 本身不会变透明。
Container(
color: const Color.fromRGBO(0, 0, 0, 0.5),
child: ExpensiveWidget(),
);
// 使用 Transform 代替 Positioned
// 不推荐
Stack(
children: [
Positioned(
left: _animation.value,
child: ExpensiveWidget(),
),
],
);
// 推荐
Transform.translate(
offset: Offset(_animation.value, 0),
child: ExpensiveWidget(),
)
完整示例
class RenderOptimizedApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: OptimizedList(),
),
);
}
}
class OptimizedList extends StatefulWidget {
@override
_OptimizedListState createState() => _OptimizedListState();
}
class _OptimizedListState extends State<OptimizedList> {
final ScrollController _controller = ScrollController();
final List<String> _items = List.generate(1000, (i) => 'Item $i');
@override
Widget build(BuildContext context) {
return CustomScrollView(
controller: _controller,
cacheExtent: 100, // 缓存范围
physics: const BouncingScrollPhysics(),
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => _buildItem(index),
childCount: _items.length,
),
),
],
);
}
Widget _buildItem(int index) {
// 使用 const 构造函数
return const ListItemWidget(
height: 100,
margin: EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
class ListItemWidget extends StatelessWidget {
final double height;
final EdgeInsets margin;
const ListItemWidget({
Key? key,
required this.height,
required this.margin,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: height,
margin: margin,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: const [
BoxShadow(
color: Colors.black12,
offset: Offset(0, 2),
blurRadius: 4,
),
],
),
child: const _ItemContent(),
);
}
}
class _ItemContent extends StatelessWidget {
const _ItemContent({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: const [
_ItemImage(),
SizedBox(width: 16),
Expanded(child: _ItemText()),
],
);
}
}
// 注意:原稿引用了 _ItemImage/_ItemText 却没定义,已补上
class _ItemImage extends StatelessWidget {
const _ItemImage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Icon(Icons.image, size: 48);
}
}
class _ItemText extends StatelessWidget {
const _ItemText({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Text('Item');
}
}
性能监控
1. 性能叠加层
void main() {
runApp(
Directionality(
textDirection: TextDirection.ltr,
child: Stack(
children: [
MyApp(),
const PerformanceOverlay.allEnabled(),
],
),
),
);
}
2. 自定义性能监控
import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';
class FrameTimingLogger {
bool _started = false;
void start() {
if (_started) return;
SchedulerBinding.instance.addTimingsCallback(_onTimings);
_started = true;
}
void stop() {
if (!_started) return;
SchedulerBinding.instance.removeTimingsCallback(_onTimings);
_started = false;
}
void _onTimings(List<FrameTiming> timings) {
for (final timing in timings) {
debugPrint(
'build=${timing.buildDuration.inMilliseconds}ms, '
'raster=${timing.rasterDuration.inMilliseconds}ms, '
'total=${timing.totalSpan.inMilliseconds}ms',
);
}
}
}
在页面或服务销毁时调用 stop() 移除回调。这个示例记录引擎上报的帧耗时,不用递归的 post-frame 回调近似计算 FPS;完整的采集与生命周期说明见Flutter 性能监控。
最佳实践
- 记住帧预算:60fps 下 UI 线程和 Raster 线程单帧各 16.6ms(120Hz 设备是 8.3ms)。DevTools Performance 视图里标红的帧就是要修的——先看标红再动手,别凭感觉优化。
- 固定高度的列表给 itemExtent:省掉滚动时每一项的布局测量,长列表滚动提升明显。高度不固定的千万别硬给,错位比卡顿更难查。
- build 方法只做”拼 widget”:排序、正则、耗时计算全挪到
initState里算好存起来,或做 memoize。build 每帧都可能跑,在里面干重活等于每帧交税。 - RepaintBoundary 按需添加:列表 delegate 默认会添加子项边界;额外边界只用于重绘时机不同且已通过 profile 观察到收益的子树。
注意事项
- 避免重复包列表项:
SliverChildBuilderDelegate默认addRepaintBoundaries: true。手动再包一层会形成嵌套边界;关闭默认边界后再自行管理时,也应通过 DevTools 验证,而不是预设图层越多越快。 - 别过度优化:没测出瓶颈就加缓存、加隔离层,等于给代码加债。流程永远是:DevTools 定位标红帧 → 修 → 再测。
- 验收用低端机:开发机上 60fps 不代表千元机也 60fps。渲染优化的验收机至少要有一台 3 年前的中低端 Android,否则你优化的全是幻觉。
- 检查列表图片尺寸:过大的源图会增加解码与纹理内存开销。优先提供接近显示尺寸的资源;无法调整资源时,可按实际布局设置
cacheWidth/cacheHeight,再用 Flutter Inspector 检查 oversized images。不要笼统地把图片解码归因于 UI 线程,也不要把它当作可以直接挪进应用 isolate 的普通 Dart 工作。
实战案例
旅行类应用 是个列表密集型 App(旅行目的地、失物招领都是长列表),渲染优化是刚需。我的三板斧:第一,列表图片全用 cached_network_image,占位图+淡入,避免滑动白闪;第二,pull_to_refresh(^2.0.0)做下拉刷新,它的 footer/header 是按需构建的,不会一次性 build 全量;第三,轮播图用 carousel_slider(^5.1.1)而不是自己写 PageView——自己写的容易忘记 viewportFraction 和预加载,carousel 把坑都踩完了。还有个反直觉的:flutter_screenutil 的 .w/.h 适配在 build 里频繁调用没问题,别在循环里做 heavy 计算就行。渲染优化的核心就一句:别在 build 方法里干重活。
总结
渲染优化需要从多个层面入手,包括减少重建、优化图层、合理使用缓存等。通过合理的优化措施,可以显著提升应用的渲染性能和流畅度。
