Flutter DevTool问题:页面销毁后类实例未被移除
内存泄漏排查:页面销毁后ProductService实例未被GC回收
我的应用存在严重内存泄漏,涉及多类实例、静态类及get_it依赖注入器。为验证问题,我制作了小型测试示例,通过Flutter DevTool的Memory面板Heap Snapshot分析发现:退出初始化ProductService类的页面后,该类实例仍存在于堆内存中,未被垃圾回收(GC)移除。即使返回主页面,ProductService类实例依旧留存于堆内存中。
复现代码
main.dart
import 'package:flutter/material.dart'; import 'package:memory_leak_example/second.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( home: MyHomePage(), ); } } class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('First Route'), ), body: Center( child: ElevatedButton( child: const Text('Open route'), onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => const SecondRoute()), ); }, ), ), ); } }
second.dart
import 'package:flutter/material.dart'; import 'package:memory_leak_example/product_service.dart'; class SecondRoute extends StatefulWidget { const SecondRoute({super.key}); @override State<SecondRoute> createState() => _SecondRouteState(); } class _SecondRouteState extends State<SecondRoute> { late final ProductService productService = ProductService(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Second Route'), ), body: Center( child: ElevatedButton( onPressed: () { Navigator.pop(context); }, child: StreamBuilder<Object>( stream: productService.timer$, builder: (context, snapshot) { return Text('Go back (${snapshot.data})!'); }, ), ), ), ); } @override void dispose() { productService.onDispose(); super.dispose(); } }
product_service.dart
import 'dart:async'; import 'bridge.dart'; class ProductService { ProductService() { sub = timer$.listen((int tick) { print(tick); }); } final Stream<int> timer$ = Bridge.timer$; late final StreamSubscription<int> sub; void onDispose() { sub.cancel(); } }
bridge.dart
import 'dart:async'; class Bridge { Bridge._(); static Stream<int> timer$ = Stream<int>.periodic(const Duration(seconds: 1), (x) => x).asBroadcastStream(); }
问题原因及解决方案
核心原因
问题出在**StreamBuilder仍持有对ProductService.timer$的引用**,而timer$指向Bridge类的静态广播流。虽然你在dispose中取消了ProductService内部的StreamSubscription,但StreamBuilder在页面销毁时不会自动取消对该流的订阅,导致ProductService实例被StreamBuilder的订阅链间接引用,无法被GC回收。
另外,Bridge.timer$是静态流,生命周期与应用一致。只要流本身存在,未取消的订阅就会持续持有相关对象引用,造成内存泄漏。
解决方案
方案1:手动管理StreamBuilder的订阅
在页面State类中保存StreamBuilder对应的订阅对象,在dispose时主动取消:
class _SecondRouteState extends State<SecondRoute> { late final ProductService productService = ProductService(); StreamSubscription? _streamSubscription; int _currentTick = 0; @override void initState() { super.initState(); // 手动订阅流并更新UI状态 _streamSubscription = productService.timer$.listen((tick) { setState(() => _currentTick = tick); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Second Route')), body: Center( child: ElevatedButton( onPressed: () => Navigator.pop(context), child: Text('Go back ($_currentTick)!'), ), ), ); } @override void dispose() { productService.onDispose(); _streamSubscription?.cancel(); // 取消StreamBuilder对应的订阅 super.dispose(); } }
方案2:绑定流与ProductService生命周期
将静态流改为ProductService的实例流,让流的生命周期与服务实例绑定,避免全局引用:
class ProductService { late final Stream<int> timer$; late final StreamSubscription<int> sub; ProductService() { // 创建实例级别的流,随服务销毁而停止 timer$ = Stream<int>.periodic(const Duration(seconds: 1), (x) => x).asBroadcastStream(); sub = timer$.listen((int tick) { print(tick); }); } void onDispose() { sub.cancel(); // 如果是单订阅流,可通过controller关闭;广播流无需额外关闭,但需确保所有订阅都取消 } }
关键注意事项
- 任何订阅了流的对象,必须在生命周期结束时手动取消订阅,尤其是静态流或全局流。
- 避免让全局静态流持有页面/组件级对象的引用,否则极易引发内存泄漏。
内容的提问来源于stack exchange,提问作者matox
相关产品推荐
相关产品推荐

