【问题标题】:Flutter iOS crash with EXC_BAD_ACCESS errorFlutter iOS 崩溃并出现 EXC_BAD_ACCESS 错误
【发布时间】:2022-06-24 18:38:39
【问题描述】:

我正在开发一个带有 Flutter 的应用程序,并在物理 iOS 设备(Iphone 7)上测试该应用程序。 iOS版本为:15.3.1 而flutter版本是:2.10.3

当我测试我的应用程序时,我偶尔会遇到崩溃。崩溃给出了以下错误。它并不总是在同一个地方崩溃,所以我不知道在这里分享什么代码。错误消息本身对我来说并没有太多意义,我在网上找不到任何有用的信息。

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x443b8000443b8000)
    frame #0: 0x0000000198405048 libobjc.A.dylib`objc_msgSend + 8
libobjc.A.dylib`objc_msgSend:
->  0x198405048 <+8>:  ldr    x13, [x0]
    0x19840504c <+12>: and    x16, x13, #0xffffffff8
    0x198405050 <+16>: mov    x15, x16
    0x198405054 <+20>: ldr    x11, [x16, #0x10]
Target 0: (Runner) stopped.
Lost connection to device.
Exited (sigterm)

经过几天的多次尝试,我完全不知道要检查什么。有没有这方面经验丰富的人可以帮帮我?


附录:

我想知道我的 Stream Builders 是否做错了什么。这是我的代码的缩短版本:

class PrepareList extends StatefulWidget {
  final String place;
  const PrepareList({
    Key? key,
    required this.place,
  }) : super(key: key);

  @override
  State<PrepareList> createState() =>
      _PrepareListState();
}

class _PrepareListState
    extends State<PrepareList> {
  late final Stream? _listStream;

  @override
  void initState() {
    super.initState();

    String dbChild = "events/" + widget.place + "/";

    final db = FirebaseDatabase.instance
        .ref()
        .child(dbChild)
        .orderByKey()
        .limitToLast(1500);

    _listStream = db.onValue;
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
        stream: _listStream,
        builder: (context, AsyncSnapshot<dynamic> dbEvent) {
          if (dbEvent.hasError) {
            return CircularProgressIndicator();
          }
          else if (dbEvent.hasData) {
            DataSnapshot dataSnapshot = dbEvent.data!.snapshot;

            List<EventDetails> placeEventList = [];
            if (dataSnapshot.value != null) {
              (dataSnapshot.value as Map<dynamic, dynamic>)
                  .forEach((key, value) {
                placeEventList.add(EventDetails.fromRTDB(value));
              });
              placeEventList
                  .sort((a, b) => a.dateEST.compareTo(b.dateEST));

              return AttendeeList(placeEventList: placeEventList, place: place);
            } else {
              return PlaceDataNotAvailable(place: widget.place);
            }
          } else {
            return CircularProgressIndicator();
          }
        });
  }
}

class AttendeeList extends StatefulWidget {
  final List<EventDetails> placeEventList;
  final String place;
  const AttendeeList({
    Key? key,
    required this.placeEventList,
    required this.place,
  }) : super(key: key);

  @override
  State<AttendeeList> createState() =>
      _AttendeeListState();
}

class _AttendeeListState
    extends State<AttendeeList> {
  late final Stream? _attendeeListStream;

  @override
  void initState() {
    super.initState();

    String dbChild = "attendees/" + widget.place + "/";

    final db = FirebaseDatabase.instance
        .ref()
        .child(dbChild)
        .orderByKey()
        .limitToLast(1500);

    _listStream = db.onValue;
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
        stream: _attendeeListStream,
        builder: (context, AsyncSnapshot<dynamic> dbEvent) {
          if (dbEvent.hasError) {
            return CircularProgressIndicator();
          }
          else if (dbEvent.hasData) {
            DataSnapshot dataSnapshot = dbEvent.data!.snapshot;

            List<AttendeeDetails> attendeeList = [];
            if (dataSnapshot.value != null) {
              (dataSnapshot.value as Map<dynamic, dynamic>)
                  .forEach((key, value) {
                attendeeList.add(EventDetails.fromRTDB(value));
              });
              attendeeList
                  .sort((a, b) => a.dateEST.compareTo(b.dateEST));

              return Scaffold(
                body: ShowLists(placeEventList, attendeeList);
            } else {
              return PlaceDataNotAvailable(place: widget.place);
            }
          } else {
            return CircularProgressIndicator();
          }
        });
  }
}

上面的小部件可以在应用的生命周期中被多次调用。用户通过在初始屏幕上选择place 进入此屏幕,它执行PrepareList 有状态小部件中的代码,而后者又调用AttendeeList 有状态小部件。

我想强调PrepareListAttendeeList 都使用流。并且每次执行这些小部件中的代码时,都会从数据库中下载大量节点(每个小部件 1500 个)。

一个执行可以如下所示:

PrepareList("London");

另一个执行可能如下所示,在同一屏幕上显示一个新的项目列表:

PrepareList("Manhattan");

我观察到的是:

当我第一次运行PrepareList("London"); 时,需要一些时间(3 到 4 秒)才能看到屏幕上的内容。然后我运行PrepareList("Manhattan");,它也需要大约 3 到 4 秒来显示内容。但是当我再次运行PrepareList("London"); 时,内容会很快出现在屏幕上,大约 1 秒。

为了能够拨打PrepareList(),我需要转到另一个屏幕,这意味着 - 在我的理解中 - 我的流订阅 每次离开屏幕关联到时都会取消上面有 2 个小部件。但是是不是流本身没有被取消,数据仍然在内存中?

我的怀疑: 使用该应用程序时,当我在多个地方(多次)调用PrepareList(...) 时,它会在内存中加载越来越多的数据,并且从不清理它。过了一会儿,应用程序消耗了所有可用内存并通过给出上述错误而崩溃,这告诉我没有任何意义。 并且随着PrepareList(...)在使用应用程序时被执行的次数越来越多,Iphone 7 会发热,我很容易感觉到。我什至用 Iphone 12 进行了测试,它不会像 Iphone 7 那样发热,但也会崩溃。

我什至尝试在两个类中添加如下处理:

 @override
  void dispose() {
    super.dispose();
  }

...但它仍然没有帮助。

我的流实现是真的吗? 我怀疑这可能是这次崩溃的根本问题吗?

任何帮助将不胜感激!

附录 2:

我一直在努力... 我以某种方式使用该应用程序,以便多次触发PrepareList("...");。我也观察了 devtools 中的内存使用情况。我可以观察到内存使用量随着时间的推移而增加。这次我遇到了一个新错误,说的是更具体的内容:

[ServicesDaemonManager] interruptionHandler is called. -[FontServicesDaemonManager connection]_block_invoke
[tcp] tcp_input [C17.1.1:3] flags=[R] seq=3749683210, ack=0, win=0 state=LAST_ACK rcv_nxt=3749683210, snd_una=3584722489
[tcp] tcp_input [C17.1.1:3] flags=[R] seq=3749683210, ack=0, win=0 state=CLOSED rcv_nxt=3749683210, snd_una=3584722489
* thread #46, name = 'DartWorker', stop reason = EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=1450 MB, unused=0x0)
    frame #0: 0x0000000108ef4d0c Flutter`dart::CompilerPass_TypePropagation::DoBody(dart::CompilerPassState*) const + 1644
Flutter`dart::CompilerPass_TypePropagation::DoBody:
->  0x108ef4d0c <+1644>: str    xzr, [x20, x28, lsl #3]
    0x108ef4d10 <+1648>: ldr    x8, [x22, #0x48]
    0x108ef4d14 <+1652>: cmp    x24, x8
    0x108ef4d18 <+1656>: b.ge   0x108ef4d84               ; <+1764>
Target 0: (Runner) stopped.
Lost connection to device.

这次是EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=1450 MB, unused=0x0)。看起来,内存使用量随着时间的推移而增加。为什么用户离开手机屏幕时内存没有释放? (我通过怀疑即使用户移动到另一个屏幕后流仍然占用内存来问这个问题......)

【问题讨论】:

  • 正在调查 Flutter v2.16.1、ios v15.1 上的类似问题。您在使用 Firebase 数据库吗?我在错误发生前不久就注意到了一个 Firebase 查询?
  • @Pip 感谢您分享您的评论。是的,我确实使用 Firebase。虽然不可能找到它发生的位置,因为它可能会在应用程序的完全不同的不相关位置崩溃。理解这个错误的含义会很好(例如,这是否意味着内存不足?),这样我就会知道要看什么。
  • 你不会得到这样的答案。您需要提供更多关于插件的信息,可能还需要提供设备的完整崩溃日志。
  • @kuhnroyal 如何从设备获取完整的崩溃日志?

标签: firebase flutter dart firebase-realtime-database stream-builder


【解决方案1】:

关于内存使用量永久增加的一些想法,因为如果没有完整的示例,几乎不可能回答您的问题。

  1. Firebase 会缓存对象,这就是它第二次更快的原因。这应该不会影响您的内存使用量,从而破坏应用程序。

  2. 我通过怀疑即使用户移动到另一个屏幕后流仍然占用内存来问这个问题......

也许您正在使用Navigator.push* 并且从不弹出任何以前的 路线,之前路线中的所有列表都保存在内存中。

  1. 跟踪 dispose 调用,查看小部件是否被清理。通过使用日志记录、断点,甚至使用 DevTools 内存视图更好地跟踪小部件计数:https://docs.flutter.dev/development/tools/devtools/memory 内存快照可以准确地告诉您内存的使用位置。

  2. 一般来说,最好将排序移出build 函数。但这更像是一个性能提示而不是一个问题。

  3. 您的内存问题可能与EXC_BAD_ACCESS 崩溃无关,但您应该先尝试解决它们。如果有的话,它将更容易找到根本原因。

【讨论】:

  • 非常感谢您的 cmets。我做了其中的一些,这真的很有帮助。我最初的 EXC_BAD_ACCESS 问题似乎与另一个问题有关,我将在单独的答案中解释。
【解决方案2】:

EXC_BAD_ACCESS 错误确实令人沮丧,iOS 设备崩溃在“调试模式”下随机发生。当我在 iOS 设备上测试我的应用程序时,我正在使用“发布模式”。崩溃报告无济于事,它并没有准确地指出问题所在。我正在使用 Android 设备开发我的应用程序,当我完成应用程序的某些部分/模块时,我会在 iOS 设备上的“发布模式”中看到输出。如果您使用的是 iOS 设备,请在终端上使用此命令以“发布模式”构建您的应用:

flutter 运行 --release

【讨论】:

    【解决方案3】:

    我知道我的问题没有真正组织好,我无法提供复制问题的正确代码。经过多次尝试,我似乎解决了这个问题。因为它只是有时会发生,所以图片对我来说仍然模糊,但我至少知道是什么解决了我的问题。

    仍然很难提供完整的代码,但我会在这里尝试用一些伪代码来解释是什么解决了我的问题,希望对可能遇到类似问题的其他人有所帮助。

    无论我对它有多不满意,我在应用程序的某些部分都有一些不可避免的嵌套流。在一页上,我有多个嵌套流构建器,但我将仅在下面说明 2 个流的情况:

    StreamBuilder1
       --> Read data A from database
       StreamBuilder2
          ---> Read data B from database (by using data from data A)
    

    从数据库中读取data B时,需要使用StreamBuilder1的一些输入。存在依赖关系。

    这 2 个流偶尔会导致页面刷新。可能导致 EXC_BAD_ACCESSS 的原因之一似乎是试图读取已释放的部分内存。我“怀疑”的是,当 StreamBuilder2 需要使用它从数据库中读取自己的数据时,data A 有时并不真正存在于内存中。

    我做了以下更改...

    StreamBuilder1
       --> Read data A from database
       FutureBuilder2
          ---> Read data B from database (by using data from data A)
    

    我用 FutureBuilder 替换了 StreamBuilder2,意外崩溃消失了。

    我基本上有多个嵌套流构建器。为所有人提供流会很棒,但这并不是必须的。只有一个数据馈送需要使用流构建器来实现,我这样做了。我用 future builder 替换了所有其他流。从那以后,我没有遇到过 EXC_BAD_ACCESS 崩溃。

    我可能误解了事情的运作方式或此更改如何解决了我的问题。但这至少是我所做的,并且最终真正解决了我的问题。是否有人有任何进一步的 cmets,非常欢迎他/她分享它们,因为我非常有兴趣了解使用嵌套流时需要考虑的事项,因为在那里找到好的例子有点困难。

    旁注:我什至研究了一点 BLoC,希望它可以帮助解决这个问题。我想对于已经知道如何使用它的人来说这很容易。但这非常令人生畏,特别是如果您想在从 firebase 读取数据时将其用于多个流,而我找不到任何使用 BLoC 的示例。


    附录(2022 年 6 月 24 日): 我仍然遇到偶尔发生的这个问题。它尚未在 Android 中发生,但该应用程序因未知原因而关闭。在我上面提到的更改之后不会经常发生这种情况,但问题还没有完全消失。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-05-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-30
      相关资源
      最近更新 更多