Flutter内存泄漏检测插件leak_detector的使用

发布于 1周前 作者 yibo5220 来自 Flutter

Flutter内存泄漏检测插件leak_detector的使用

leak_detector 是一个用于检测Flutter应用程序中内存泄漏的工具。它可以帮助开发者识别和修复潜在的内存泄漏问题,从而提高应用的性能和稳定性。以下是关于如何使用 leak_detector 的详细说明,并附带一个完整的示例demo。

1. 初始化

为了防止底层库 vm service 崩溃,在添加内存泄漏检测对象之前,请调用以下代码进行初始化:

LeakDetector().init(maxRetainingPath: 300); // maxRetainingPath 默认值为 300

启用内存泄漏检测会降低性能,并且可能会导致页面出现帧丢失(Full GC)。插件通过 assert 进行初始化,因此在 release 模式下不需要关闭它。

2. 检测

LeakNavigatorObserver 添加到 MaterialAppnavigatorObservers 中,它会自动检测页面中的 Widget 及其对应的 Element 对象是否存在内存泄漏。如果页面的 WidgetStatefulWidget,它还会自动检查其对应的 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 Gcend 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);
});

预览页面显示如下:

预览页面1 预览页面2 预览页面3

预览页面包含引用链节点的类信息、引用的属性信息、属性声明的源代码以及源代码的位置(行号:列号)。

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

1 回复

更多关于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 插件来检测内存泄漏问题。

回到顶部