Flutter性能优化技巧总结

154 阅读13分钟

原文:xuanhu.info/projects/it…

Flutter性能优化技巧总结

1. 减少Widget重建优化

1.1 使用const构造函数

在Flutter中,使用const关键字声明widget可以让Flutter只创建该widget的一个实例,并在widget树中重复使用它,从而减少内存消耗和提高渲染时间。


// 使用const关键字声明不变widget

const Text('Hello, Flutter!');

const Icon(Icons.star, color: Colors.yellow);

为什么重要const关键字减少了Flutter构建widget树时需要完成的工作,提高了整体性能。

1.2 最小化不必要的widget重建

Flutter基于widget的架构鼓励可重用性,但过多的重建会造成性能负担。使用以下策略最小化不必要的重建:

  • 避免传递未改变的变量:在StatelessWidget中使用const,并限制StatefulWidget的使用

  • 提取widget:将大型widget分解为更小、更易管理的部分,防止widget树因微小变化而整体重建

  • 使用AutomaticKeepAliveClientMixin:对于具有标签视图的屏幕,此mixin可在切换标签时保留widget状态


class MyWidget extends StatefulWidget {

@override

_MyWidgetState createState() => _MyWidgetState();

}

  


class _MyWidgetState extends State<MyWidget> with AutomaticKeepAliveClientMixin {

@override

bool get wantKeepAlive => true; // 保持状态活跃

  


@override

Widget build(BuildContext context) {

super.build(context); // 调用keep-alive功能

return Text('Tab View Content');

}

}

1.3 使用shouldRebuild方法优化

对于自定义widget,可以重写shouldRebuild方法来确定widget是否应该重建,实现对重建的细粒度控制。

2. 布局和渲染优化

2.1 限制Opacity使用,改用Visibility

Flutter的Opacity widget在透明度值之间过渡时会持续渲染widget,可能成为昂贵操作。考虑使用Visibility widget根据条件隐藏或显示widget而不影响性能。


// 使用Visibility替代Opacity进行条件渲染

Visibility(

visible: isVisible,

child: Text('I am visible!'),

);

2.2 使用RepaintBoundary提高滚动性能

对于具有复杂动画或图形的widget,使用RepaintBoundary widget。它告诉Flutter将被包裹的widget视为单独图层,减少widget树重建时所需的工作量。


RepaintBoundary(

child: ComplexWidget(), // 复杂widget

);

2.3 减少昂贵布局widget的使用

Column和Row等widget是基础,但过度嵌套会增加成本。避免深度widget嵌套,考虑使用Stack来叠加widget。如果复杂布局不可避免,使用CustomMultiChildLayout进行自定义精细控制。

2.4 创建轻量级布局

高效布局对保持Flutter应用流畅性能至关重要:

  • 避免深层widget树:深层widget树会导致复杂性增加和性能降低

  • 使用轻量widget:尽可能使用Container、Padding和Align等轻量widget,而不是Stack和ListView等较重widget

  • 使用LayoutBuilder优化布局:使用LayoutBuilder根据可用空间调整布局,创建响应式设计

3. 列表性能优化

3.1 对长列表实现懒加载

对于包含许多项目的列表,使用ListView.builder或ListView.separated仅加载可见项目。此技术通过仅渲染当前视口中的列表项来节省内存和CPU周期。


ListView.builder(

itemCount: items.length,

itemBuilder: (context, index) {

return ListTile(title: Text(items[index]));

},

);

3.2 使用itemExtent确定列表元素尺寸

对于许多列表,我们可以根据UI设计稿预先知道滚动方向上的尺寸。如果能够知道,使用itemExtent属性设置列表元素在滚动方向上的大小可以提高性能。这是因为如果不指定,在滚动过程中需要计算每个元素在滚动方向上的大小,消耗计算资源。


ListView.builder(

itemBuilder: (context, index) => ListItem(index: index),

itemCount: 1000,

itemExtent: 120.0, // 指定列表项高度

);

3.3 使用prototypeItem属性

prototypeItem属性允许提供一个原型项,帮助Flutter更有效地确定所有子项的尺寸,特别当子项有不同大小时非常有用。


ListView.builder(

prototypeItem: const CardWidget(index: "", maxfontSize: 35),

itemCount: 1000,

itemBuilder: (BuildContext context, int index) {

return CardWidget(index: index.toString());

},

);

3.4 禁用addAutomaticKeepAlives和addRepaintBoundaries功能

这两个属性旨在优化滚动过程中的用户体验,但在某些情况下可以禁用以提高性能:

  • addAutomaticKeepAlives:默认true,意味着列表元素在不可见后可以保持其状态,以便再次出现在屏幕上时快速构建。这是一种以空间换时间的方式,会导致一定程度的内存开销。可以设置为false关闭此功能。

  • addRepaintBoundaries:用重绘边界(Repaint Boundary)包装列表元素,以便在滚动时避免重绘。如果列表易于绘制(列表元素的布局相对简单),可以关闭此功能以提高滚动流畅度。


ListView.builder(

itemBuilder: (context, index) => ListItem(index: index),

itemCount: 1000,

addAutomaticKeepAlives: false, // 禁用自动保持活跃

addRepaintBoundaries: false, // 禁用重绘边界

itemExtent: 120.0,

);

4. 图像和资源优化

4.1 选择正确的图像格式

选择适当的图像格式对Flutter应用性能至关重要,直接影响性能、内存使用和用户体验:

  • WebP:对于照片和复杂图像,使用WebP可以获得更小的文件大小和更快的渲染速度。它支持有损和无损压缩,提供比JPEG和PNG更小的文件尺寸同时保持高质量。

  • PNG:对于需要透明度的图像(如徽标),使用PNG。它使用无损压缩,保持图像质量但通常导致较大的文件大小。

  • SVG:对于简单图形或图标,优先使用SVG以实现可扩展性。SVG文件是分辨率无关的,可以缩放而不损失质量。

  • JPEG:适用于照片和复杂图像,使用有损压缩减小文件大小但可能导致一些质量损失。渲染速度较慢。

性能比较示例

考虑一个包含10个高分辨率图像的应用:

  • 使用JPEG:总大小=15MB,渲染速度=60fps

  • 使用PNG:总大小=25MB,渲染速度=70fps

  • 使用WebP:总大小=8MB,渲染速度=120fps

4.2 图像压缩和缓存

大图像会因高内存使用和加载时间而减慢应用速度。有效压缩和缓存图像可以显著提升性能。


// 使用CachedNetworkImage进行网络图像缓存

CachedNetworkImage(

imageUrl: "https://example.com/image.jpg",

placeholder: (context, url) => CircularProgressIndicator(),

errorWidget: (context, url, error) => Icon(Icons.error),

);

4.3 调整图像大小

加载所需分辨率的图像,而不是在应用中缩小大图像。使用Image.asset()或Image.network()指定大小,或使用ResizeImage。


// 调整图像大小以减少内存使用

Image(

image: ResizeImage(AssetImage('assets/large_image.png'), width: 300),

);

4.4 使用分辨率感知图像

Flutter支持分辨率感知图像,允许为不同设备像素比提供多个版本的图像。


assets/

my_icon.png

1.5x/

my_icon.png

2.0x/

my_icon.png

3.0x/

my_icon.png

在这种结构中,Flutter根据设备的像素比自动选择适当的图像,确保最佳图像质量而不需要不必要的内存使用。

5. 状态管理优化

5.1 状态管理框架比较

选择合适的state management解决方案对应用性能至关重要。以下是流行状态管理框架的比较:

| 框架/模式 | 学习成本 | 代码复杂度 | 性能 | 适用场景 |

|------------------|----------|------------|------|--------------------------|

| setState(原生) | 低 | 低 | 高 | 小项目、Demo、简单状态 |

| Provider | 中 | 中 | 高 | 中小型项目 |

| Riverpod | 中偏高 | 中偏低 | 高 | 中大型项目、复杂依赖 |

| Bloc/Cubit | 中偏高 | 中偏高 | 高 | 大型团队、多人协作 |

| GetX | 低 | 低 | 高 | 快速开发、小团队 |

| Signals | 中 | 低 | 高 | 简单响应式场景、官方实验 |

5.2 Provider使用

Provider是Flutter团队推荐的状态管理解决方案,基于InheritedWidget构建,提供简单的方式来管理和访问应用状态。


// 添加依赖

dependencies:

provider: ^6.0.0

  


// 创建模型

class Counter with ChangeNotifier {

int _count = 0;

int get count => _count;

void increment() {

_count++;

notifyListeners(); // 通知监听器

}

}

  


// 提供状态

void main() {

runApp(

ChangeNotifierProvider(

create: (context) => Counter(),

child: MyApp(),

),

);

}

  


// 使用状态 - 方式1: 使用Consumer

Consumer<Counter>(

builder: (context, counter, child) {

return Text('Count: ${counter.count}');

},

)

  


// 使用状态 - 方式2: 使用Provider.of

var counter = Provider.of<Counter>(context);

  


// 使用状态 - 方式3: 使用Selector

Selector<Counter, int>(

selector: (_, counter) => counter.count,

builder: (_, count, __) {

return Text('Count: $count');

},

)

5.3 Riverpod使用

Riverpod是Provider的改进版,解决了Provider的许多痛点,提供更好的灵活性和安全性。


// 添加依赖

dependencies:

flutter_riverpod: ^2.0.0

  


// 创建Provider

final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {

return CounterNotifier();

});

  


class CounterNotifier extends StateNotifier<int> {

CounterNotifier() : super(0);

void increment() {

state++;

}

}

  


// 使用Provider

void main() {

runApp(ProviderScope(child: MyApp()));

}

  


// 使用ConsumerWidget

class CounterDisplay extends ConsumerWidget {

@override

Widget build(BuildContext context, WidgetRef ref) {

final count = ref.watch(counterProvider);

return Text('Count: $count');

}

}

  


// 使用Consumer

Consumer(

builder: (context, ref, child) {

return ElevatedButton(

onPressed: () => ref.read(counterProvider.notifier).increment(),

child: Text('Increment'),

);

},

)

5.4 Bloc使用

Bloc(Business Logic Component)是一种基于事件和状态的状态管理模式,特别适合复杂业务逻辑的应用。


// 添加依赖

dependencies:

flutter_bloc: ^8.0.0

bloc: ^8.0.0

  


// 定义事件

abstract class CounterEvent {}

class Increment extends CounterEvent {}

  


// 定义状态

class CounterState {

final int count;

CounterState(this.count);

}

  


// 创建Bloc

class CounterBloc extends Bloc<CounterEvent, CounterState> {

CounterBloc() : super(CounterState(0)) {

on<Increment>((event, emit) {

emit(CounterState(state.count + 1));

});

}

}

  


// 使用Bloc

void main() {

runApp(

BlocProvider(

create: (context) => CounterBloc(),

child: MyApp(),

),

);

}

  


// 使用BlocBuilder

BlocBuilder<CounterBloc, CounterState>(

builder: (context, state) {

return Text('Count: ${state.count}');

},

)

  


// 发送事件

context.read<CounterBloc>().add(Increment());

5.5 状态管理选择建议

根据项目需求选择合适的状态管理方案:

  • 小型项目/Demo:setState或GetX

  • 中小型项目:Provider或Riverpod

  • 大型项目:Bloc或Riverpod

  • 多人协作、严格架构:Bloc或Redux

  • 数据依赖复杂:MobX或Riverpod

  • 快速MVP开发:GetX

6. 内存管理优化

6.1 减少Widget创建和销毁

重用现有widget,使用Key避免重复构建,避免状态管理器泄漏。

6.2 优化图像资源

移除不必要的图像,避免图像重复加载,使用MemoryImage对图像进行缓存。

6.3 避免资源泄漏

及时释放内存,避免"闭包"泄漏。

6.4 合理管理内存

使用Navigator管理页面历史,采用异步方式加载数据。


// 使用pushReplacement导航确保旧路由被清除

Navigator.pushReplacement(

context,

MaterialPageRoute(builder: (context) => NewPage()),

);

6.5 使用缓存策略

合理使用缓存可以减少重复计算,但要注意缓存的大小,避免内存溢出。

7. 网络和异步优化

7.1 优化网络调用

网络调用是常见的性能瓶颈来源。通过以下方式优化网络调用:

  • 使用http包进行HTTP请求,使用dio进行高级功能如重试和缓存

  • 实现分页和缓存以减少网络使用并改善数据加载时间

  • 使用后台处理和isolates进行CPU密集型任务,防止UI冻结

7.2 使用FutureBuilder和StreamBuilder

使用FutureBuilder和StreamBuilder异步加载数据,确保UI渲染不被繁重任务阻塞。

7.3 隔离繁重计算

对于CPU密集型任务,使用Isolate在单独线程中运行任务,保持UI线程响应。

7.4 防抖和节流用户交互

对于交互式应用(如具有搜索或表单提交的应用),对用户交互进行防抖和节流处理以防止过多重建。这在管理由用户操作触发的网络请求时特别有用。


void _onSearchChanged(String query) {

_debounce(() {

// 执行搜索

}, Duration(milliseconds: 500));

}

8. 动画优化

8.1 使用AnimatedBuilder

对于复杂动画,使用AnimatedBuilder仅重建widget树的必要部分。

8.2 减少动画开销

简化动画以减少对性能的影响。使用性能覆盖等工具测量和优化动画平滑度。

9. 应用大小优化

9.1 摇树优化(Tree Shaking)

Flutter在编译期间自动移除未使用的代码。为进一步优化,确保没有导入不必要的包或依赖。

9.2 拆分APK/IPA

对于Android,可以按架构拆分APK以减少下载大小。对于iOS,使用bitcode和应用瘦身实现类似结果。


flutter build apk --split-per-abi

9.3 移除未使用的资源

移除未使用的图像、字体和其他资源以减少应用大小。

9.4 减少自定义字体使用

虽然自定义字体可以增强UI设计,但也会增加应用大小和渲染时间。尽可能使用系统字体,仅加载需要的字重和样式。

10. 分析和调试工具

10.1 使用Flutter DevTools进行性能分析

要真正了解瓶颈所在,必须使用Flutter的DevTools。

  • 时间轴(Timeline):实时显示CPU和GPU使用情况

  • Widget检查器(Widget Inspector):分析widget树并突出显示过度嵌套的区域

  • 内存分析器(Memory Profiler):帮助监控内存使用并捕获内存泄漏,这是长生命周期widget中的常见问题

10.2 启用性能覆盖

启用性能覆盖以可视化应用的帧渲染。查找卡顿(帧丢失)并相应优化。


// 在开发模式下启用性能覆盖

MaterialApp(

debugShowCheckedModeBanner: false,

showPerformanceOverlay: true,

home: MyHomePage(),

);

10.3 使用flutter analyze

使用flutter analyze和dart:developer工具识别内存泄漏和性能瓶颈。

10.4 实现性能测试

使用flutter_driver实现性能测试,识别回归和瓶颈。

11. 平台特定优化

11.1 明智使用平台通道

通过与平台通道与原生代码交互时,确保通信尽可能高效。尽可能减少调用次数和批量操作。

11.2 平台特定优化

考虑平台特定优化以在不同设备上最大化性能:

  • Android:使用ProGuard、AOT编译等进行优化

  • iOS:确保使用Metal进行渲染,因为它提供更好的性能。使用App Thinning、Metal API等优化

12. 代码和架构优化

12.1 编写高效代码

编写高效代码可以显著影响应用性能:

  • 避免嵌套setState调用和不必要的widget重建

  • 使用const构造函数和final字段最小化对象创建

  • 移除未使用的导入和依赖以减少应用大小和启动时间

12.2 分解复杂widget

将复杂widget分解为更小、可重用的组件。这不仅提高性能,还增强代码可读性和可维护性。

12.3 尽可能使用无状态widget

当widget不依赖动态数据时,优先使用无状态widget而不是有状态widget。无状态widget本质上更高效,因为它们不需要状态管理。

12.4 实现记忆化(Memoization)

记忆化是一种用于缓存昂贵函数调用结果并在相同输入再次出现时返回缓存结果的技术。在Flutter中,记忆化可以应用于widget构建以避免冗余计算。

13. 性能基准和结果

通过实施上述优化技术,您可以期望在以下方面获得显著改进:

13.1 列表性能改进

  • 25% FPS增加:通过使用ListView.builder与itemExtent/prototypeItem,列表滚动性能可以提高高达25%

  • 内存使用减少:懒加载和适当缓存可以减少列表内存使用达40%

13.2 应用大小减少

  • 66% APK大小减少:通过图像优化(使用WebP)、摇树优化和移除未使用资源,APK大小可以减少高达66%

  • 更快启动时间:优化代码和资源可以将启动时间减少达50%

13.3 渲染性能改进

  • 更平滑动画:优化动画和减少重绘可以提高动画性能达30%,实现更平滑的60fps体验

  • 减少UI卡顿:通过减少不必要的widget重建和优化布局,UI卡顿可以减少达60%

14. 持续优化策略

性能优化不是一次性的任务,而是一个持续的过程。建立以下实践以确保应用的长期性能:

14.1 定期性能分析

定期使用Flutter DevTools分析应用性能,设立性能基准并监控回归。

14.2 代码审查和重构

定期进行代码审查会议,专注于性能优化。重构代码以实施最佳实践和新优化技术。

14.3 测试不同设备

在不同设备和平台版本上测试应用性能,确保优化在所有目标设备上有效。

14.4 监控生产性能

使用应用性能监控工具监控生产环境中的实际性能,识别用户实际遇到的瓶颈。

15. 总结

优化Flutter应用性能是一个多方面的过程,涉及最小化不必要的widget重建、创建高效布局、使用记忆化技术、优化图像和资源、选择适当的状态管理解决方案,以及有效使用性能分析工具。

原文:xuanhu.info/projects/it…