Flutter在iOS上崩溃并显示EXC_BAD_ACCESS错误

11

我正在使用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 Builder方面是否有做错的地方。这是我的代码缩短后的版本:

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)。看起来,内存使用量随时间增加。为什么用户离开手机屏幕后内存没有被释放呢?(我怀疑流即使在用户移动到另一个屏幕后仍然占用内存...)


1
调查Flutter v2.16.1、iOS v15.1上的类似问题。你是否使用Firebase数据库?我注意到在错误发生前不久有一个Firebase查询? - Pip
@kuhnroyal 我怎样才能从设备中获取完整的崩溃日志? - edn
1
@edn https://developer.apple.com/documentation/xcode/acquiring-crash-reports-and-diagnostic-logs - kuhnroyal
@kuhnroyal,我在上面的问题中添加了更多的信息。您有机会看一下吗? - edn
@RémiRousselet 你有时间看一下这个问题吗? - edn
显示剩余7条评论
5个回答

1
我知道我的问题并没有真正被组织好,我也无法提供一个能够复制该问题的适当代码。经过多次尝试,我似乎解决了这个问题。但是,由于它只是偶尔发生,图片对我来说仍然很模糊,但最起码我知道了如何解决我的问题。
虽然仍然很难提供完整的代码,但我会在此提供一些伪代码来解释我是如何解决这个问题的,希望对其他可能遇到类似问题的人有所帮助。
无论我对其有多不满意,在应用程序的某些部分中,我都有一些不可避免的嵌套流。在一个页面上,我有多个嵌套的流构建器,但我将在下面仅说明两个流的情况:
StreamBuilder1
   --> Read data A from database
   StreamBuilder2
      ---> Read data B from database (by using data from data A)

从数据库读取数据B时,需要使用StreamBuilder1的一些输入。存在依赖关系。
这两个流有时会导致页面刷新。导致EXC_BAD_ACCESSS的原因之一似乎是尝试读取已经释放的内存部分。我“怀疑”数据A偶尔不在内存中,当StreamBuilder2需要使用它来从数据库中读取自己的数据时。
我进行了以下更改...
StreamBuilder1
   --> Read data A from database
   FutureBuilder2
      ---> Read data B from database (by using data from data A)

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

我本质上有多个嵌套的流构建器。为所有流都设置流构建器当然很好,但这并不是必须的。只需要使用一个数据源来实现流构建器,我就这样做了。然后,我将所有其他流都替换为 FutureBuilder。自那以后,我就没有遇到过 EXC_BAD_ACCESS 崩溃。

也许我误解了事物的工作原理或者这种改变如何解决我的问题。但至少这就是我所做的,也是最终解决我的问题的方法。如果有人有进一步的评论,他/她可以分享,因为我非常想知道在使用嵌套流时需要考虑哪些事项,因为很难在网络上找到好的例子。

附注:我甚至学习了一点 BLoC,希望它能帮助解决这个问题。我猜对于已经知道如何使用它的人来说,这很容易。但是,特别是当您想要在从 Firebase 读取数据时使用它来处理多个流时,这可能会令人生畏。我无法找到任何关于如何在 BLoC 中实现它的示例。


补充(2022年6月24日): 我仍然遇到这个问题,但是它偶尔发生。在 Android 上还没有发生过,但应用程序会因未知原因关闭。经过我上述的更改后,它并不经常发生,但问题尚未完全消失。


1
在我的情况下,这是由于connectivity_plus引起的。
  • 我刚刚将其更新到5.xx
  • pod deintegrate
  • pod install

是的,这是线程的链接:https://github.com/fluttercommunity/plus_plugins/issues/2162 - undefined

1
关于内存使用量持续增加的问题,我有几个想法,但是没有一个完整的示例,实际上很难回答你的问题。
1. Firebase会缓存对象,这就是为什么第二次更快。这不应该对内存使用造成足以破坏应用程序的影响。 2. 我怀疑流在用户切换到另一个屏幕后仍然占用内存,所以我提出了这个问题...... 3. 可能你正在使用Navigator.push*,并且从未弹出任何先前的路由,所有先前路由中的列表都被保留在内存中。
跟踪dispose调用,查看小部件是否被清除。可以使用日志记录、断点或更好的方法是使用DevTools内存视图来跟踪小部件计数:https://docs.flutter.dev/development/tools/devtools/memory 内存快照可以告诉您确切地使用了哪些内存。
通常最好将排序移出build函数。但这更像是性能提示而不是问题。
您的内存问题可能与EXC_BAD_ACCESS崩溃无关,但您应该先尝试解决它们。如果有任何根本原因,这将使查找更容易。

1
非常感谢您的评论。我已经采纳了其中的一些建议,对我帮助很大。我的原始问题EXC_BAD_ACCESS似乎与另一个问题有关,我将在另一个答案中进行解释。 - edn

0
在我的情况下,由于 Firebase 推送通知而导致程序崩溃。我从通知负载中移除了通知,现在它可以正常工作:
{
    // "notification":{"title": "title","body": "body" }, DON'T USE THIS LINE EVER!
    "to": "token",
    "priority": "high",
    "data": {
      "click_action": "FLUTTER_NOTIFICATION_CLICK",
      "content": {
        "payload":{
          "type": "Message",
          "userId": "123",
          "postId": "postId"
        },
        "id": 100,
        "channelKey": "basic_channel",
        "body": "body",
        "title": "title"
      }
    },
    "mutable_content" : true,
    "content_available": true
  }

谢谢您的回复。在我的情况下,它与推送通知无关,因为即使没有接收到推送通知,它也会崩溃。 - edn

0
我遇到了一个类似的错误(EXC_BAD_ACCESS)。 在我的情况下,是因为对late关键字的错误使用(不确定是否正确)。 (截图) 可能是我从其他地方复制粘贴过来的...分析器和编译器都没有警告这个错误。只有平台级别的崩溃日志。 此外,flutter clean有所帮助,但过一段时间后错误又出现了。

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接