Flutter内存泄漏检测插件leak_detector的使用
Flutter内存泄漏检测插件leak_detector的使用
leak_detector
是一个用于检测Flutter应用程序中内存泄漏的工具。它可以帮助开发者识别和修复潜在的内存泄漏问题,从而提高应用的性能和稳定性。以下是关于如何使用 leak_detector
的详细说明,并附带一个完整的示例demo。
1. 初始化
为了防止底层库 vm service
崩溃,在添加内存泄漏检测对象之前,请调用以下代码进行初始化:
LeakDetector().init(maxRetainingPath: 300); // maxRetainingPath 默认值为 300
启用内存泄漏检测会降低性能,并且可能会导致页面出现帧丢失(Full GC)。插件通过 assert
进行初始化,因此在 release
模式下不需要关闭它。
2. 检测
将 LeakNavigatorObserver
添加到 MaterialApp
的 navigatorObservers
中,它会自动检测页面中的 Widget
及其对应的 Element
对象是否存在内存泄漏。如果页面的 Widget
是 StatefulWidget
,它还会自动检查其对应的 State
。
import 'package:leak_detector/leak_detector.dart';
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
navigatorObservers: [
// 使用 LeakNavigatorObserver
LeakNavigatorObserver(
shouldCheck: (route) {
return route.settings.name != null && route.settings.name != '/';
},
),
],
);
}
3. 获取泄漏信息
LeakDetector().onLeakedStream
可以注册监听器,在检测到内存泄漏后通知对象的引用链。LeakDetector().onEventStream
可以监控内部时间通知,例如 start Gc
、end Gc
等。
你可以通过以下代码显示引用链的预览页面。注意,BuildContext
必须能够获取 NavigatorState
:
import 'package:leak_detector/leak_detector.dart';
// 显示预览页面
LeakDetector().onLeakedStream.listen((LeakedInfo info) {
// 打印到控制台
info.retainingPath.forEach((node) => print(node));
// 显示预览页面
showLeakedInfoPage(navigatorKey.currentContext, info);
});
预览页面显示如下:
预览页面包含引用链节点的类信息、引用的属性信息、属性声明的源代码以及源代码的位置(行号:列号)。
4. 获取内存泄漏记录
你可以通过以下代码获取内存泄漏记录,并显示历史记录页面:
import 'package:leak_detector/leak_detector.dart';
getLeakedRecording().then((List<LeakedInfo> infoList) {
showLeakedInfoListPage(navigatorKey.currentContext, infoList);
});
历史记录页面显示如下:
5. 在真实设备上无法连接到 vm_service
当我们在连接电脑运行时,电脑上的 DDS
会首先连接到手机端的 vm_service
,导致 leak_detector
插件无法再次连接到 vm_service
。有以下两种解决方案:
-
断开连接并重启应用:在
run
完成后,断开与电脑的连接,然后最好重启应用。如果测试包已经安装在手机上,上述问题不会存在,因此这种方法适用于测试人员。 -
禁用
DDS
:在flutter run
后面添加--disable-dds
参数来禁用DDS
。测试完成后,这不会对调试产生任何影响。可以在Android Studio
中进行配置。
6. 完整示例Demo
以下是一个完整的示例代码,展示了如何使用 leak_detector
插件来检测内存泄漏:
import 'package:flutter/material.dart';
import 'package:leak_detector/leak_detector.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
[@override](/user/override)
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
GlobalKey<NavigatorState> navigatorKey = GlobalKey();
bool _checking = false;
[@override](/user/override)
void initState() {
super.initState();
// 初始化 leak_detector
LeakDetector().init(maxRetainingPath: 300);
// 监听内存泄漏信息
LeakDetector().onLeakedStream.listen((LeakedInfo info) {
// 打印到控制台
info.retainingPath.forEach((node) => print(node));
// 显示预览页面
showLeakedInfoPage(navigatorKey.currentContext!, info);
});
// 监听事件
LeakDetector().onEventStream.listen((DetectorEvent event) {
print(event);
if (event.type == DetectorEventType.startAnalyze) {
setState(() {
_checking = true;
});
} else if (event.type == DetectorEventType.endAnalyze) {
setState(() {
_checking = false;
});
}
});
}
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
routes: {
'/p1': (_) => LeakPage1(),
'/p2': (_) => LeakPage2(),
'/p3': (_) => LeakPage3(),
'/p4': (_) => LeakPage4(),
},
navigatorObservers: [
// 使用 LeakNavigatorObserver
LeakNavigatorObserver(
checkLeakDelay: 0,
shouldCheck: (route) {
// 你可以自定义哪些 `route` 可以被检测
return route.settings.name != null && route.settings.name != '/';
},
),
],
home: Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.adjust,
color: _checking ? Colors.white : null,
),
backgroundColor: _checking ? Colors.red : null,
onPressed: () {},
),
body: Container(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () {
Navigator.of(navigatorKey.currentContext!).pushNamed('/p1');
},
style: ButtonStyle(
side: MaterialStateProperty.resolveWith(
(states) => BorderSide(width: 1, color: Colors.blue),
),
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: Text('跳转 (无状态, widget 泄漏)'),
),
),
SizedBox(height: 20),
TextButton(
onPressed: () {
Navigator.of(navigatorKey.currentContext!).pushNamed('/p2');
},
style: ButtonStyle(
side: MaterialStateProperty.resolveWith(
(states) => BorderSide(width: 1, color: Colors.blue),
),
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: Text('跳转 (有状态, widget 泄漏)'),
),
),
SizedBox(height: 20),
TextButton(
onPressed: () {
Navigator.of(navigatorKey.currentContext!).pushNamed('/p3');
},
style: ButtonStyle(
side: MaterialStateProperty.resolveWith(
(states) => BorderSide(width: 1, color: Colors.blue),
),
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: Text('跳转 (有状态, state 泄漏)'),
),
),
SizedBox(height: 20),
TextButton(
onPressed: () {
Navigator.of(navigatorKey.currentContext!).pushNamed('/p4');
},
style: ButtonStyle(
side: MaterialStateProperty.resolveWith(
(states) => BorderSide(width: 1, color: Colors.blue),
),
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: Text('跳转 (有状态, element 泄漏)'),
),
),
SizedBox(height: 20),
TextButton(
onPressed: () {
getLeakedRecording().then((List<LeakedInfo> infoList) {
showLeakedInfoListPage(
navigatorKey.currentContext!, infoList);
});
},
style: ButtonStyle(
side: MaterialStateProperty.resolveWith(
(states) => BorderSide(width: 1, color: Colors.blue),
),
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: Text('读取历史'),
),
),
],
),
),
),
),
);
}
}
class LeakPage1 extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return Material(
child: Container(
child: Center(
child: TextButton(
onPressed: () {
Navigator.of(context).pop(this);
},
style: ButtonStyle(
side: MaterialStateProperty.resolveWith(
(states) => BorderSide(width: 1, color: Colors.blue),
),
),
child: Text('返回'),
),
),
),
);
}
}
class LeakPage2 extends StatefulWidget {
[@override](/user/override)
State<StatefulWidget> createState() {
return LeakPageState2();
}
}
class LeakPageState2 extends State<LeakPage2> {
[@override](/user/override)
Widget build(BuildContext context) {
return Material(
child: Container(
child: Center(
child: TextButton(
onPressed: () {
Navigator.of(context).pop(widget);
},
style: ButtonStyle(
side: MaterialStateProperty.resolveWith(
(states) => BorderSide(width: 1, color: Colors.blue),
),
),
child: Text('返回'),
),
),
),
);
}
}
class LeakPage3 extends StatefulWidget {
[@override](/user/override)
State<StatefulWidget> createState() {
return LeakPageState3();
}
}
class LeakPageState3 extends State<LeakPage3> {
[@override](/user/override)
Widget build(BuildContext context) {
return Material(
child: Container(
child: Center(
child: TextButton(
onPressed: () {
Navigator.of(context).pop(this);
},
style: ButtonStyle(
side: MaterialStateProperty.resolveWith(
(states) => BorderSide(width: 1, color: Colors.blue),
),
),
child: Text('返回'),
),
),
),
);
}
}
class LeakPage4 extends StatefulWidget {
[@override](/user/override)
State<StatefulWidget> createState() {
return LeakPageState4();
}
}
class LeakPageState4 extends State<LeakPage4> {
TextEditingController _controller = TextEditingController();
[@override](/user/override)
Widget build(BuildContext context) {
return Material(
child: Container(
child: Column(
children: [
TextField(
controller: _controller,
),
TextButton(
onPressed: () {
Navigator.of(context).pop(context);
},
style: ButtonStyle(
side: MaterialStateProperty.resolveWith(
(states) => BorderSide(width: 1, color: Colors.blue),
),
),
child: Text('返回'),
)
],
),
),
);
}
}
更多关于Flutter内存泄漏检测插件leak_detector的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter内存泄漏检测插件leak_detector的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
在Flutter开发中,内存泄漏是一个常见的问题,它会导致应用性能下降,甚至崩溃。leak_detector
是一个用于检测 Flutter 应用内存泄漏的插件。以下是如何在 Flutter 项目中使用 leak_detector
的详细步骤和代码示例。
步骤 1: 添加依赖
首先,你需要在 pubspec.yaml
文件中添加 leak_detector
依赖:
dependencies:
flutter:
sdk: flutter
leak_detector: ^x.y.z # 请替换为最新版本号
然后运行 flutter pub get
来获取依赖。
步骤 2: 初始化 LeakDetector
在你的应用入口文件(通常是 main.dart
)中,初始化 LeakDetector
。
import 'package:flutter/material.dart';
import 'package:leak_detector/leak_detector.dart';
void main() {
// 初始化 LeakDetector
LeakDetector.initialize();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Leak Detector Demo'),
),
body: Center(
child: Text('Check console for memory leaks!'),
),
);
}
}
步骤 3: 使用 LeakDetector 进行内存泄漏检测
leak_detector
插件提供了检测内存泄漏的便捷方法。你可以通过调用 LeakDetector.detect
方法来手动触发内存泄漏检测。通常,你会在应用的某些关键路径或生命周期回调中进行检测,例如页面销毁时。
以下是一个在页面销毁时检测内存泄漏的示例:
import 'package:flutter/material.dart';
import 'package:leak_detector/leak_detector.dart';
class MemoryLeakPage extends StatefulWidget {
@override
_MemoryLeakPageState createState() => _MemoryLeakPageState();
}
class _MemoryLeakPageState extends State<MemoryLeakPage> {
@override
void dispose() {
// 在页面销毁时检测内存泄漏
LeakDetector.detect(tag: 'MemoryLeakPage disposed');
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Memory Leak Page'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// 导航到另一个页面以模拟内存泄漏场景
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AnotherPage()),
);
},
child: Text('Go to Another Page'),
),
),
);
}
}
class AnotherPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Another Page'),
),
body: Center(
child: Text('This is another page'),
),
);
}
}
步骤 4: 查看检测结果
当你运行应用并触发内存泄漏检测时,leak_detector
会在控制台输出检测结果。你可以通过查看控制台日志来识别潜在的内存泄漏问题。
注意事项
leak_detector
插件主要用于开发阶段帮助开发者识别内存泄漏问题。- 在生产环境中,建议移除或禁用内存泄漏检测代码,以避免性能影响。
- 内存泄漏检测可能受到多种因素的影响,包括 Flutter 引擎版本、Dart VM 版本以及具体的应用代码实现。因此,在使用
leak_detector
时,建议结合其他内存分析工具(如 DevTools)进行综合分析。
通过以上步骤,你可以在 Flutter 项目中有效地使用 leak_detector
插件来检测内存泄漏问题。