MST

星途 面试题库

面试题:Flutter DevTools 性能分析下的内存泄露排查

在 Flutter 应用开发过程中,使用 Flutter DevTools 发现内存不断增长,疑似出现内存泄露。阐述使用 DevTools 进行内存泄露排查的流程,如何借助其功能找出内存泄露的对象以及相关的代码位置,并举例说明常见的内存泄露场景及解决方案。
13.2万 热度难度
前端开发Flutter

知识考点

AI 面试

面试题答案

一键面试

1. 使用 DevTools 进行内存泄露排查流程

  1. 打开 DevTools:在 Flutter 项目运行时,通过命令行 flutter pub global run devtools 或者 IDE 中的相关插件打开 DevTools。
  2. 连接到应用:DevTools 启动后,选择正在运行的 Flutter 应用进行连接。
  3. 启用性能监控:在 DevTools 界面中,切换到“Memory”标签页,开始记录内存数据。可以先进行一次完整的内存快照,作为初始状态。
  4. 模拟内存增长操作:在应用中执行可能导致内存增长的操作,例如多次打开关闭页面、反复加载数据等。
  5. 多次记录内存快照:在执行操作过程中,多次点击“Take Snapshot”记录内存快照,以便对比不同状态下的内存变化。
  6. 对比内存快照:使用“Compare to Baseline”或与之前的快照对比,查看内存增长部分。关注“Retained Size”(保留大小)增长明显的对象。

2. 借助其功能找出内存泄露对象及代码位置

  1. 分析内存快照:在内存快照对比结果中,查找“Retained Size”持续增长的对象。点击该对象可以查看其引用关系。
  2. 追溯引用链:沿着引用链向上查找,找到持有该对象的根对象。根对象可能是全局变量、单例等。
  3. 定位代码位置:在引用链中,结合代码逻辑可以定位到创建和持有泄露对象的代码位置。通常,在 Dart 代码中,通过查看对象的声明和使用处,以及相关的类和方法来确定具体位置。例如,如果是一个自定义的 Widget 导致内存泄露,查看该 Widget 的 build 方法以及相关生命周期方法,看是否存在不合理的对象持有。

3. 常见内存泄露场景及解决方案

场景一:未取消的 Stream 订阅

  • 描述:如果在 Widget 中订阅了 Stream,但在 Widget 销毁时未取消订阅,Stream 会继续持有 Widget,导致 Widget 及其相关资源无法释放,造成内存泄露。
  • 解决方案:在 StatefulWidget 的 dispose 方法中取消 Stream 订阅。例如:
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();
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

场景二:静态成员持有大量数据

  • 描述:类中的静态成员(如静态变量、静态方法中的局部变量等)会一直存在于内存中,如果这些成员持有大量数据并且没有及时清理,就会导致内存泄露。
  • 解决方案:尽量避免在静态成员中持有大量的可变数据。如果必须使用,提供方法在合适的时候清理这些数据。例如:
class DataHolder {
  static List<BigDataObject> _dataList = [];

  static void addData(BigDataObject data) {
    _dataList.add(data);
  }

  static void clearData() {
    _dataList.clear();
  }
}

在不需要这些数据时,调用 DataHolder.clearData() 方法清理数据。

场景三:循环引用

  • 描述:两个或多个对象相互引用,形成循环,导致垃圾回收器无法回收这些对象。例如,A 对象持有 B 对象的引用,B 对象又持有 A 对象的引用,且它们都没有其他外部引用。
  • 解决方案:打破循环引用。可以通过使用弱引用(WeakReference)来替代强引用,或者在合适的时机解除其中一方的引用。例如:
class A {
  B b;
  A() {
    b = B(this);
  }
}

class B {
  A a;
  B(this.a);
}

可以修改为:

import 'dart:weak';

class A {
  WeakReference<B> b;
  A() {
    B newB = B(this);
    b = WeakReference(newB);
  }
}

class B {
  A a;
  B(this.a);
}

这样 A 对 B 的引用为弱引用,不会阻止 B 被垃圾回收器回收。