Mar*_*oso 10 android ios flutter google-cloud-firestore
我的 flutter 应用程序在所有平台上运行良好几个月,但随机开始崩溃,我无法找到问题或弄清楚为什么它会突然发生。崩溃会关闭应用程序,并且应用程序无法再次打开。
\n我设置了 Crashlytics 和 Sentry 来检查崩溃日志,但都没有显示问题所在。我只能通过使用连接到 VSCode 的真实设备重现崩溃来解决以下错误。我提供了两个,错误几乎总是发生在特定屏幕上或该屏幕之前的屏幕上。该错误发生在某些 Android 设备上,但在我亲自测试过的 Samsung Galaxy S21 或 S8 上从未出现过。在 iPhone 6 上,应用程序在我到达有问题的屏幕之前就崩溃了。该错误不会发生在模拟器上。一旦它在 iPhone 12 上崩溃并停止,如果我尝试打开该应用程序,它甚至不会在手机上启动。
\n我尝试使用 CachedNetworkImage 而不是仅使用 NetworkImage 来更新 Flutter 和 XCode,并且确保if(mounted)在任何调用之前进行调用,以最大程度地减少内存泄漏的可能性,并且通过重写该方法来setState正确处置任何内存泄漏。我什至不知道从哪里开始寻找这个问题。StreamSubscriptionsdispose()
请帮我确定这里发生了什么。我可以使用什么方法来查找导致此崩溃的原因?
\n在 iPhone 12 上运行:
\nError 1:\n* thread #15, name = 'io.worker.3', stop reason = EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=2098 MB, unused=0x0)\n frame #0: 0x000000010f011538 Flutter`ycc_rgb_convert + 140\nFlutter`ycc_rgb_convert:\n-> 0x10f011538 <+140>: strb w21, [x5]\n 0x10f01153c <+144>: ldr x21, [x12, x19, lsl #3]\n 0x10f011540 <+148>: ldr x20, [x11, x20, lsl #3]\n 0x10f011544 <+152>: add x20, x20, x21\nTarget 0: (Runner) stopped.\nLost connection to device.\n\nError 2: \n[Process] 0x11f000d30 - NetworkProcessProxy::didClose (Network Process 0 crash)\n[ServicesDaemonManager] interruptionHandler is called. -[FontServicesDaemonManager connection]_block_invoke\n* thread #15, name = 'io.worker.3', stop reason = EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=2098 MB, unused=0x0)\n frame #0: 0x000000011336f7ac Flutter`ycc_rgb_convert + 140\nFlutter`ycc_rgb_convert:\n-> 0x11336f7ac <+140>: strb w21, [x5]\n 0x11336f7b0 <+144>: ldr x21, [x12, x19, lsl #3]\n 0x11336f7b4 <+148>: ldr x20, [x11, x20, lsl #3]\n 0x11336f7b8 <+152>: add x20, x20, x21\nTarget 0: (Runner) stopped.\nLost connection to device.\n\nFlutter Version:\nFlutter 3.7.0 \xe2\x80\xa2 channel stable \xe2\x80\xa2 https://github.com/flutter/flutter.git\nFramework \xe2\x80\xa2 revision b06b8b2710 (2 days ago) \xe2\x80\xa2 2023-01-23 16:55:55 -0800\nEngine \xe2\x80\xa2 revision b24591ed32\nTools \xe2\x80\xa2 Dart 2.19.0 \xe2\x80\xa2 DevTools 2.20.1\n\nRunning flutter doctor...\nDoctor summary (to see all details, run flutter doctor -v):\n[\xe2\x9c\x93] Flutter (Channel stable, 3.7.0, on macOS 12.6.3 21G419 darwin-x64, locale en-GB)\n[\xe2\x9c\x93] Android toolchain - develop for Android devices (Android SDK version 30.0.3)\n[\xe2\x9c\x93] Xcode - develop for iOS and macOS (Xcode 14.2)\n[\xe2\x9c\x93] Chrome - develop for the web\n[\xe2\x9c\x93] Android Studio (version 2021.2)\n[\xe2\x9c\x93] VS Code (version 1.74.3)\n[\xe2\x9c\x93] Connected device (3 available)\n[\xe2\x9c\x93] HTTP Host Availability\n\n\xe2\x80\xa2 No issues found!\nRun Code Online (Sandbox Code Playgroud)\n编辑:
\n感谢您的回复。崩溃主要发生在应用程序的一个特定部分,即我在一个屏幕(iPhone 6)中加载图像,或者在加载图像屏幕后(iPhone12)加载一两个屏幕。我升级到 Flutter 3.7,试图解决仍然存在的问题。
\n我认为这是一个图像问题,因为 ChatGPT 说:“函数名称“ycc_rgb_convert”表明它可能与图像处理有关,因为 YCC (YUV) 是图像和视频压缩中使用的颜色空间,但它并没有证实这一点它与加载图像有关。” 当然,这只是一个线索,并不能确定。
\n它也不会发生在所有设备上。它发生在 iPhone 6 上,在 iPhone 12 上发生需要更长的时间,而在三星 Galaxy S21 或 S8 上根本不会发生。该错误大约 2 周前开始发生,我只是在尝试解决此问题时才开始使用 CacheNetworkImage,之前我不需要它,而且说实话,现在它并没有真正的帮助。
\n崩溃发生在地点屏幕(或在该屏幕进入堆栈后不久),我在其中加载了“地点项目”的条子网格。位置项是一个网格图块,它具有主图像和头像图像,以及放置在网格图块页眉和页脚中的 like_button 包中的一些文本和 2 个图标按钮。单击某个地点项目会将您带到地点详细信息屏幕,iPhone12 有时会在该屏幕上发生崩溃。如果我完全避开 iPhone 6 上的地点屏幕,就不会发生崩溃。但如果我在地点屏幕中加载区区 4 个地点项目,iPhone 6 上就会发生崩溃。
\n我首先加载 6 个位置项,然后在用户滚动时加载另外 4 个位置项来进行分页。我加载的主图像最大,不超过 1MB(平均大小可能为 400-500kb),头像图像更小,最大可能 200kb,最小不到 20kb。这些图像与它们一直以来属于地方文档的图像相同。
\n地点屏幕有一个从 200kb 的资源加载的背景图像,并且也是预先缓存的(我为解决此问题所做的另一件事,但以前没有或没有必要)。我还对正在加载的每个缓存网络图像使用相同的占位符图像,这也是 200kb 并且是预先缓存的。
\n您能否指导我如何识别内存泄漏?尝试在 DevTools 中使用内存分析器不起作用,因为崩溃发生时它会自动断开连接,除了上面给出的错误之外,我从控制台没有得到任何信息(仅适用于 iPhone 12,iPhone 6 根本没有日志)。当我在 Galaxy s8 上以配置文件模式运行它时,保留的大小从未超过 25MB,而我采取了可能导致 iPhone 6 和 12 中崩溃的所有步骤,并且它运行得像黄油一样流畅。没有内存峰值。
\n当我在配置文件模式下运行 iPhone 6 时,在断开连接之前,dart 堆保持在 16MB 左右,从启动开发工具到崩溃,保留大小保持在 20-30MB 左右,没有明显的内存峰值,但也许我正在使用该工具错误地?我不知道...
\nSentry.io 记录了这些 iPhone 6 崩溃并表示:“内存不足:操作系统很可能终止了您的应用程序,因为它过度使用了 RAM。” 但它没有提供进一步的细节。
\nSentry 也是尝试诊断此问题的新成员。
\n这是地点项目代码:
\nimport 'package:flutter/material.dart';\nimport 'package:font_awesome_flutter/font_awesome_flutter.dart';\nimport 'package:like_button/like_button.dart';\nimport 'package:provider/provider.dart';\nimport 'package:firebase_auth/firebase_auth.dart';\nimport 'package:cached_network_image/cached_network_image.dart';\n\nimport '../data/color_palette.dart';\nimport '../widgets/pricing_icons.dart';\nimport '../models/place.dart';\nimport '../screens/place_detail_screen.dart';\nimport '../models/user.dart';\nimport '../services/database.dart';\nimport './custom_dialogbox.dart';\n\nclass PlaceItem extends StatelessWidget {\n final Place venue;\n PlaceItem(this.venue);\n\n final FirebaseAuth _auth = FirebaseAuth.instance;\n final DatabaseService _db = DatabaseService();\n\n @override\n Widget build(BuildContext context) {\n Size size = MediaQuery.of(context).size;\n final venue = Provider.of<Place>(context, listen: false);\n return ClipRRect(\n clipBehavior: Clip.antiAlias,\n borderRadius: BorderRadius.circular(20),\n child: GestureDetector(\n onTap: () {\n ScaffoldMessenger.of(context).hideCurrentSnackBar();\n Navigator.of(context).pushNamed(\n PlaceDetailScreen.routeName,\n arguments: venue.venueId,\n // Extract in the screen we'll nav to with ModalRoute\n );\n },\n child: GridTile(\n header: Consumer<Place>(\n builder: (ctx, venue, child) => _gridTileHeader(context)),\n footer: _gridTileFooter(size),\n // Venue Image\n child: CachedNetworkImage(\n imageUrl: venue.venueImageUrl,\n \n fadeInDuration: const Duration(milliseconds: 500),\n\n placeholder: (context, url) => Image.asset(\n 'assets/images/tile_background.png',\n fit: BoxFit.cover,\n ),\n fit: BoxFit.cover,\n errorWidget: (context, url, error) => Container(\n decoration: const BoxDecoration(\n image: DecorationImage(\n image: AssetImage(\n 'assets/images/tile_background.png',\n ),\n fit: BoxFit.cover,\n ),\n ),\n child: const Center(\n child: Icon(Icons.error, color: Colors.grey,),\n ),\n ),\n ),\n ),\n ),\n );\n }\n\n Widget _gridTileHeader(context) {\n final user = _auth.currentUser;\n final animationDuration = const Duration(milliseconds: 700);\n return StreamBuilder<UserData>(\n stream: DatabaseService(uid: user.uid).userData,\n builder: (context, snapshot) {\n if (snapshot.hasData) {\n UserData userData = snapshot.data;\n bool isBookmarked = userData.bookmarks.contains(venue.venueId);\n bool isFaved = userData.favorites.contains(venue.venueId);\n\n return GridTileBar(\n backgroundColor: Colors.black.withOpacity(0.4),\n // Animated bookmark button\n leading: LikeButton(\n // key: _globalKey,\n isLiked: isBookmarked,\n circleColor: const CircleColor(\n start: firstColor,\n end: sixthColor,\n ),\n bubblesColor: const BubblesColor(\n dotPrimaryColor: sixthColor,\n dotSecondaryColor: Colors.red,\n ),\n // size: 100, // used when testing for closer look\n // size: 50, // size of containing circle\n likeBuilder: (bool bookmarked) {\n return Icon(\n Icons.bookmark,\n color: isBookmarked ? sixthColor : Colors.white,\n // size: 75, // used when testing for closer look\n size: 20,\n );\n },\n animationDuration: animationDuration,\n\n onTap: (bookmarked) async {\n print(bookmarked);\n print(isBookmarked);\n // return !bookmarked;\n try {\n // // if user's email isn't verified\n if (!user.emailVerified) {\n showDialog(\n context: context, builder: (ctx) => VerifyEmail());\n return bookmarked; // do nothing\n } else {\n // actual functionality\n DatabaseService(uid: user.uid)\n .toggleToUserList('bookmarks', venue.venueId);\n ScaffoldMessenger.of(context).hideCurrentSnackBar();\n ScaffoldMessenger.of(context).showSnackBar(\n SnackBar(\n elevation: 10,\n backgroundColor: firstColor,\n content: !isBookmarked\n ? const Text('Venue Added to Bookmarks!')\n : const Text('Venue Removed from Bookmarks'),\n duration: const Duration(milliseconds: 2500),\n action: SnackBarAction(\n textColor: sixthColor,\n label: 'UNDO',\n onPressed: () async {\n // widget.venue.toggleBookmarkStatus();\n DatabaseService(uid: user.uid)\n .toggleToUserList('bookmarks', venue.venueId);\n\n // reverse it\n _db.updateLikesBookmarks(\n venue.venueId,\n venue.vendorId,\n venue.vendorName,\n venue.address['city'],\n 'bookmarks',\n bookmarked,\n );\n },\n ),\n ),\n );\n\n // Track bookmarks\n // putting "await" with the function stops the animation\n _db.updateLikesBookmarks(\n venue.venueId,\n venue.vendorId,\n venue.vendorName,\n venue.address['city'],\n 'bookmarks',\n !bookmarked,\n );\n\n return !bookmarked;\n }\n } catch (e) {\n print(e.toString());\n return bookmarked; // dont change it if there was an error\n }\n },\n ),\n title: venue.ownerManaged\n ? const Icon(\n Icons.verified,\n color: Colors.white,\n )\n : const Text(''),\n\n // Animated Favorites Button\n trailing: LikeButton(\n // key: _globalKey,\n isLiked: isFaved,\n circleColor: const CircleColor(\n start: firstColor,\n end: Colors.red,\n ),\n bubblesColor: const BubblesColor(\n dotPrimaryColor: secondColor,\n dotSecondaryColor: Colors.red,\n ),\n // size: 100, // used when testing for closer look\n // size: 50, // sizze of containing circle\n likeBuilder: (bool faved) {\n return Icon(\n Icons.favorite,\n color: isFaved ? secondColor : Colors.white,\n // size: 75, // used when testing for closer look\n size: 20,\n );\n },\n animationDuration: animationDuration,\n\n onTap: (faved) async {\n // print(faved);\n // print(isFaved);\n // return !faved;\n try {\n // // if user's email isn't verified\n if (!user.emailVerified) {\n showDialog(\n context: context, builder: (ctx) => VerifyEmail());\n return faved; // do nothing\n } else {\n // actual functionality\n DatabaseService(uid: user.uid)\n .toggleToUserList('favorites', venue.venueId);\n ScaffoldMessenger.of(context).hideCurrentSnackBar();\n ScaffoldMessenger.of(context).showSnackBar(\n SnackBar(\n // behavior: SnackBarBehavior.floating,\n elevation: 10,\n backgroundColor: firstColor,\n content: !isFaved\n ? const Text('Venue Added to Favorites!')\n : const Text('Venue Removed from Favorites'),\n duration: const Duration(milliseconds: 2500),\n action: SnackBarAction(\n textColor: sixthColor,\n label: 'UNDO',\n onPressed: () async {\n // widget.venue.toggleBookmarkStatus();\n DatabaseService(uid: user.uid)\n .toggleToUserList('favorites', venue.venueId);\n\n // reverse it\n _db.updateLikesBookmarks(\n venue.venueId,\n venue.vendorId,\n venue.vendorName,\n venue.address['city'],\n 'likes',\n faved,\n );\n },\n ),\n ),\n );\n\n // Track bookmarks\n // putting "await" with the function stops the animation\n _db.updateLikesBookmarks(\n venue.venueId,\n venue.vendorId,\n venue.vendorName,\n venue.address['city'],\n 'likes',\n !faved,\n );\n\n return !faved;\n }\n } catch (e) {\n print(e.toString());\n return faved; // dont change it if there was an error\n }\n },\n ),\n );\n } else if (user.isAnonymous) {\n return GridTileBar(\n backgroundColor: Colors.black.withOpacity(0.35),\n leading: IconButton(\n icon: const Icon(\n Icons.bookmark_border,\n color: Colors.white,\n size: 20,\n ),\n onPressed: () {\n showDialog(context: context, builder: (ctx) => AnonDialog());\n },\n ),\n title: const Text(''),\n trailing: IconButton(\n icon: const Icon(\n Icons.favorite_border,\n size: 20,\n ),\n onPressed: () {\n showDialog(context: context, builder: (ctx) => AnonDialog());\n },\n ),\n );\n } else {\n // fake tile bar with no functionality\n return GridTileBar(\n backgroundColor: Colors.black.withOpacity(0.35),\n leading: IconButton(\n icon: const Icon(\n Icons.bookmark_border,\n color: Colors.white,\n size: 20,\n ),\n onPressed: () {},\n ),\n title: const Text(''),\n trailing: IconButton(\n icon: const Icon(\n Icons.favorite_border,\n size: 20,\n ),\n onPressed: () {},\n ),\n );\n }\n });\n }\n\n Widget _gridTileFooter(size) {\n return GridTileBar(\n backgroundColor: Colors.black87,\n // title\n title: Padding(\n padding: const EdgeInsets.only(top: 1.0, bottom: 3),\n child: Text(\n venue.venueName,\n overflow: TextOverflow.ellipsis,\n softWrap: true,\n maxLines: 2,\n textAlign: TextAlign.left,\n style: const TextStyle(\n color: Colors.white,\n fontFamily: 'Poppins',\n fontSize: 12,\n height: 1),\n ),\n ),\n // subtitle column\n subtitle: Column(\n crossAxisAlignment: CrossAxisAlignment.start,\n children: <Widget>[\n // price icons\n FittedBox(\n fit: BoxFit.fitWidth,\n child: PricingIconsDB(venue),\n ),\n // Google rating\n Container(\n padding: const EdgeInsets.only(\n left: 2,\n top: 2,\n ),\n width: size.width * 0.25,\n child: FittedBox(\n fit: BoxFit.fitWidth,\n child: Row(\n children: [\n const Icon(\n FontAwesomeIcons.google,\n color: Colors.white70,\n // color: firstColor,\n size: 10,\n ),\n const Text(' Rating: '),\n Text(\n venue.googleRating == null\n ? '-'\n : venue.googleRating\n .toStringAsFixed(1)\n .replaceAll(RegExp(r"([.]*0)(?!.*\\d)"), ""),\n // '4.1',\n style: const TextStyle(color: firstColor),\n ),\n const Icon(\n Icons.star,\n // color: Colors.white,\n color: firstColor,\n size: 15,\n ),\n ],\n ),\n ),\n ),\n ],\n ),\n trailing: Container(\n height: size.width * 0.126,\n width: size.width * 0.126,\n clipBehavior: Clip.antiAlias,\n decoration: const BoxDecoration(\n shape: BoxShape.circle,\n ),\n child: venue.vendorLogoUrl == null || venue.vendorLogoUrl.isEmpty\n ? Container(\n color: Colors.black26,\n )\n// What I used as placeholders before the crashes started:\n // The gifs may be expensive in terms of RAM - 200KB - 500KB each\n // ? Image.asset(\n // // restaurant or cafe\n // // loadedVenue\n // // .businessCategories\n // // .contains('c2')\n // venue.venueCategories["restaurant"]\n // ? 'assets/images/food_logo.gif'\n //
正如您所期望的,崩溃与图像有关。\n当我构建一个使用大量图像的应用程序时,我遇到了与您相同的问题。
\n当内存不足(大量实时图像)和/或同时运行许多 https 请求(下载图像)时,可能会发生崩溃。
\n您可以清除图像缓存,然后在包含图像的网格上快速滚动以重现此问题。
\n解决方案可能是:
\n使用较低的图像分辨率,或者在显示图像之前调整图像大小。
\n或者,如果您想使用高分辨率图像,您可以尝试通过调用您正在使用的图像evict上的方法来手动处理图像ImageProvider,如下例所示。
final imageProvider = NetworkImage(bytes);\n imageProvider.evict();\nRun Code Online (Sandbox Code Playgroud)\n不过,调用前需要确保图片已完全加载evict,否则不会有任何效果。
确保不要同时下载太多图像,这可能会很棘手。一种可能是手动下载 a 中的图像StatefulWidget,然后在 dispose 方法中取消下载。\xc2\xa0
我已经开发了一个包来解决这个问题disposable_cached_images。尝试一下,如果它解决了问题,您可以使用它或修改源代码以获得所需的行为。
\n| 归档时间: |
|
| 查看次数: |
9420 次 |
| 最近记录: |