Flutter 内存泄漏是一个常见问题,通常由于不当的资源管理导致。以下是常见的内存泄漏场景、检测方法和解决方案:
🔍 常见内存泄漏场景
- Controller 未释放
dart
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final TextEditingController _controller = TextEditingController();
final ScrollController _scrollController = ScrollController();
final AnimationController _animationController = AnimationController(
duration: Duration(seconds: 1),
vsync: this, // 注意这里的 vsync
);
@override
void dispose() {
_controller.dispose(); // ✅ 必须调用
_scrollController.dispose(); // ✅ 必须调用
_animationController.dispose(); // ✅ 必须调用
super.dispose();
}
}
- Stream 订阅未取消
dart
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
StreamSubscription? _subscription;
@override
void initState() {
super.initState();
_subscription = someStream.listen((data) {
// 处理数据
});
}
@override
void dispose() {
_subscription?.cancel(); // ✅ 必须取消订阅
super.dispose();
}
}
// 更好的方式:使用 Riverpod、Bloc 等状态管理库
- 定时器未取消
dart
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Timer? _timer;
@override
void initState() {
super.initState();
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
// 定期执行
});
}
@override
void dispose() {
_timer?.cancel(); // ✅ 必须取消
super.dispose();
}
}
- 全局/静态变量持有引用
dart
// ❌ 错误做法
List<MyWidget> globalWidgets = [];
// ✅ 正确做法:使用弱引用或清理机制
import 'package:flutter/foundation.dart';
List<WeakReference<MyWidget>> weakWidgets = [];
- 闭包捕获上下文
dart
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
void doSomething() {
// ❌ 闭包捕获了 this,可能导致泄漏
Future.delayed(Duration(seconds: 10), () {
if (mounted) { // ✅ 检查 mounted
setState(() {});
}
});
// ✅ 更好的做法:使用局部变量
final currentContext = context;
Future.delayed(Duration(seconds: 10), () {
if (mounted && currentContext.mounted) {
setState(() {});
}
});
}
}
🛠️ 检测工具
- DevTools Memory 面板
bash
# 启动应用时开启性能监控
flutter run --profile
· 查看内存快照
· 分析内存分配
· 检测泄漏对象
- 使用 Observatory/VmService
dart
// 在 main.dart 中添加
void main() {
WidgetsFlutterBinding.ensureInitialized();
// 内存监控
if (kDebugMode) {
MemoryAllocations.instance.addListener((event) {
if (event.type == MemoryEventType.newObject) {
debugPrint('New object: ${event.object}');
}
});
}
runApp(MyApp());
}
- Leak Tracking Package
yaml
dependencies:
leak_tracker: ^10.0.0
leak_tracker_flutter_testing: ^10.0.0
🚀 最佳实践
- 使用 StatefulWidget 的 dispose 方法
dart
@override
void dispose() {
// 按创建顺序反向清理
_animationController.dispose();
_focusNode.dispose();
_scrollController.dispose();
_textController.dispose();
_streamSubscription?.cancel();
_timer?.cancel();
super.dispose(); // 最后调用 super.dispose()
}
- 使用 Provider/Consumer
dart
// ❌ 避免在 build 方法中创建新对象
Widget build(BuildContext context) {
return MyWidget(
controller: TextEditingController(), // 每次重建都创建新实例
);
}
// ✅ 使用 State 或 Provider
final myControllerProvider = Provider<TextEditingController>((ref) {
return TextEditingController();
}, dispose: (controller) => controller.dispose());
- 使用 WeakReference
dart
import 'dart:ffi' as ffi;
import 'package:ffi/ffi.dart';
class WeakReference<T extends NativeType> {
ffi.Pointer<ffi.Void> _ptr;
T? get value {
// 检查引用是否有效
}
}
- 图片资源管理
dart
// 使用 Image.network 时注意缓存
Image.network(
url,
cacheWidth: 100, // 限制缓存大小
cacheHeight: 100,
filterQuality: FilterQuality.low,
)
// 清除图片缓存
imageCache.clear();
imageCache.clearLiveImages();
- ListView/GridView 优化
dart
ListView.builder(
itemCount: 1000,
itemBuilder: (context, index) {
return MyListItem(
key: ValueKey(index), // 使用 Key 帮助 Flutter 复用 Widget
);
},
)
📊 内存监控代码
dart
class MemoryMonitor {
static void startMonitoring() {
if (kDebugMode) {
// 定期检查内存
Timer.periodic(Duration(seconds: 5), (timer) {
debugPrint('Memory usage: ${_getMemoryUsage()}');
});
}
}
static String _getMemoryUsage() {
// 获取内存使用情况
return '${(WidgetsBinding.instance.window.memoryUsage / 1024 / 1024).toStringAsFixed(2)} MB';
}
}
🧪 测试内存泄漏
dart
testWidgets('Memory leak test', (WidgetTester tester) async {
// 创建并销毁 Widget 多次
for (int i = 0; i < 100; i++) {
await tester.pumpWidget(MyApp());
await tester.pumpWidget(Container()); // 替换为空 Widget
}
// 检查是否有未释放的资源
expect(debugDidScheduleFrame, isFalse);
});
💡 关键要点
- 总是实现 dispose() 方法清理资源
- 使用 mounted 检查避免访问已销毁的 Widget
- 避免在 build() 中创建新对象
- 使用合适的状态管理(Provider、Riverpod、Bloc)
- 定期进行内存分析(DevTools)
- 注意第三方库可能的内存泄漏
- 测试中模拟内存警告以验证清理逻辑
通过遵循这些最佳实践,可以显著减少 Flutter 应用中的内存泄漏问题。