ios 为什么我的Flutter应用程序随机崩溃以及如何查找潜在内存泄漏- EXC_RESOURCE RESOURCE_TYPE_MEMORY

xdnvmnnf  于 2023-02-01  发布在  iOS
关注(0)|答案(1)|浏览(501)
    • bounty将在4天后过期**。回答此问题可获得+50的声望奖励。Marvioso正在寻找规范答案

我的Flutter应用程序在所有平台上运行良好,几个月来一直随机开始崩溃,我找不到问题所在,也不知道为什么会突然发生。崩溃关闭了应用程序,应用程序无法再次打开。
我设置了Crashlytics和Sentry来检查崩溃日志,但都没有显示问题是什么。我只能通过使用连接到VSCode的真实设备重现崩溃来提取以下错误。我提供了两个,错误几乎总是发生在特定屏幕或之前的屏幕上。错误发生在一些Android设备上,但在我亲自测试过的三星Galaxy S21或S8上从未出现过。在iPhone 6上,应用程序在我进入屏幕之前就崩溃了。模拟器上不会出现错误。一旦它在iPhone 12上崩溃并停止,即使我试图打开它,应用程序也不会在手机上启动。
我尝试过更新Flutter和XCode,使用CachedNetworkImage而不仅仅是NetworkImage,我已经确保在调用setState之前调用if(mounted),以最大限度地减少内存泄漏的可能性,并通过覆盖dispose()方法正确处理任何StreamSubscriptions。我甚至不知道从哪里开始寻找这个问题。
请帮助我确定这是怎么回事。我可以使用什么方法来查找导致此崩溃的原因??
在iPhone 12上运行:

Error 1:
* thread #15, name = 'io.worker.3', stop reason = EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=2098 MB, unused=0x0)
    frame #0: 0x000000010f011538 Flutter`ycc_rgb_convert + 140
Flutter`ycc_rgb_convert:
->  0x10f011538 <+140>: strb   w21, [x5]
    0x10f01153c <+144>: ldr    x21, [x12, x19, lsl #3]
    0x10f011540 <+148>: ldr    x20, [x11, x20, lsl #3]
    0x10f011544 <+152>: add    x20, x20, x21
Target 0: (Runner) stopped.
Lost connection to device.

Error 2: 
[Process] 0x11f000d30 - NetworkProcessProxy::didClose (Network Process 0 crash)
[ServicesDaemonManager] interruptionHandler is called. -[FontServicesDaemonManager connection]_block_invoke
* thread #15, name = 'io.worker.3', stop reason = EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=2098 MB, unused=0x0)
    frame #0: 0x000000011336f7ac Flutter`ycc_rgb_convert + 140
Flutter`ycc_rgb_convert:
->  0x11336f7ac <+140>: strb   w21, [x5]
    0x11336f7b0 <+144>: ldr    x21, [x12, x19, lsl #3]
    0x11336f7b4 <+148>: ldr    x20, [x11, x20, lsl #3]
    0x11336f7b8 <+152>: add    x20, x20, x21
Target 0: (Runner) stopped.
Lost connection to device.

Flutter Version:
Flutter 3.7.0 • channel stable • https://github.com/flutter/flutter.git
Framework • revision b06b8b2710 (2 days ago) • 2023-01-23 16:55:55 -0800
Engine • revision b24591ed32
Tools • Dart 2.19.0 • DevTools 2.20.1

Running flutter doctor...
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.7.0, on macOS 12.6.3 21G419 darwin-x64, locale en-GB)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.2)
[✓] VS Code (version 1.74.3)
[✓] Connected device (3 available)
[✓] HTTP Host Availability

• No issues found!
    • 编辑:**

感谢您的回复。崩溃大多发生在应用程序的某个特定部分,即我在屏幕中加载图像时(iPhone 6)或加载图像屏幕后的一两个屏幕(iPhone 12)。我升级到Flutter 3.7,试图修复仍然存在的问题。
我认为这是一个形象问题,因为ChatGPT说:"函数名" ycc_rgb_convert "表明它可能与图像处理有关,因为YCC(YUV)是图像和视频压缩中使用的颜色空间,但它并不确定它与加载图像有关。"当然,这只是一个线索,并不确定。
它也不会发生在所有设备上。它发生在iPhone 6上,它需要更长的时间发生在iPhone 12上,它根本不会发生在三星Galaxy S21或S8上。错误开始发生大约2周前,我只是开始使用CacheNetworkImage,同时试图解决这个问题,我以前不需要它,它现在没有真正帮助说实话。
在地点屏幕中发生崩溃(或者在这个屏幕进入堆栈后不久)我在那里加载了一个"地点项目"的长条网格。地点项目是一个网格瓦片,它有一个主图像和一个化身图像,以及一些文本和2个来自like_button包的图标按钮放置在网格瓦片的页眉和页脚。点击一个地点项目会带你进入地点详细信息屏幕,iPhone12有时会发生崩溃。如果我完全避开iPhone6上的位置屏幕,就不会发生崩溃。但如果我在位置屏幕上加载哪怕是微不足道的4个位置项目,iPhone6上就会发生崩溃。
我最初加载了6个位置条目,然后随着用户滚动,加载了另外4个。我加载的主图像最大,不超过1MB(它们具有大约400 - 500KB的平均大小)并且化身图像甚至更小,最大的可能是200KB,最小的不到20KB。图像是相同的,他们一直是,因为他们属于地方文档。
地点屏幕有一个从资产加载的背景图像,大小为200kb,也是预缓存的(我做了另一件事来修复这个问题,但之前没有或没有必要)。我还为加载的每个缓存网络图像使用相同的占位符图像,这也是200kb,也是预缓存的。
你能不能给我一些提示,告诉我如何识别内存泄漏?尝试在DevTools中使用内存分析器是行不通的,因为当崩溃发生时,它会自动断开连接,除了上面给出的错误外,我从控制台什么也得不到(只使用iPhone 12,完全没有来自iPhone 6的日志)当我在Galaxy S8上以配置文件模式运行它时,保留的大小从来没有超过25MB,而我采取了所有的步骤,将导致崩溃的iPhone 6和12,它运行平稳如黄油。没有内存尖峰。
当我在配置文件模式下运行iPhone 6时,在断开连接之前,dart堆保持在16MB左右,从启动开发工具到崩溃,保留大小保持在20 - 30MB左右,没有可见的内存峰值,但也许我使用的工具不正确?
Sentry.io "内存不足:操作系统很可能因为过度使用RAM而终止了你的应用程序。"但它没有给出进一步的细节。 The OS most likely terminated your app because it overused RAM." but it gives no further details.
哨兵也是一个新增加的试图诊断这个问题。
以下是地方项目代码:

import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:like_button/like_button.dart';
import 'package:provider/provider.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cached_network_image/cached_network_image.dart';

import '../data/color_palette.dart';
import '../widgets/pricing_icons.dart';
import '../models/place.dart';
import '../screens/place_detail_screen.dart';
import '../models/user.dart';
import '../services/database.dart';
import './custom_dialogbox.dart';

class PlaceItem extends StatelessWidget {
  final Place venue;
  PlaceItem(this.venue);

  final FirebaseAuth _auth = FirebaseAuth.instance;
  final DatabaseService _db = DatabaseService();

  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    final venue = Provider.of<Place>(context, listen: false);
    return ClipRRect(
      clipBehavior: Clip.antiAlias,
      borderRadius: BorderRadius.circular(20),
      child: GestureDetector(
        onTap: () {
          ScaffoldMessenger.of(context).hideCurrentSnackBar();
          Navigator.of(context).pushNamed(
            PlaceDetailScreen.routeName,
            arguments: venue.venueId,
            // Extract in the screen we'll nav to with ModalRoute
          );
        },
        child: GridTile(
          header: Consumer<Place>(
              builder: (ctx, venue, child) => _gridTileHeader(context)),
          footer: _gridTileFooter(size),
          // Venue Image
          child: CachedNetworkImage(
            imageUrl: venue.venueImageUrl,
          
            fadeInDuration: const Duration(milliseconds: 500),

            placeholder: (context, url) => Image.asset(
              'assets/images/tile_background.png',
              fit: BoxFit.cover,
            ),
            fit: BoxFit.cover,
            errorWidget: (context, url, error) => Container(
              decoration: const BoxDecoration(
                image: DecorationImage(
                  image: AssetImage(
                    'assets/images/tile_background.png',
                  ),
                  fit: BoxFit.cover,
                ),
              ),
              child: const Center(
                child: Icon(Icons.error, color: Colors.grey,),
              ),
            ),
          ),
        ),
      ),
    );
  }

  Widget _gridTileHeader(context) {
    final user = _auth.currentUser;
    final animationDuration = const Duration(milliseconds: 700);
    return StreamBuilder<UserData>(
        stream: DatabaseService(uid: user.uid).userData,
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            UserData userData = snapshot.data;
            bool isBookmarked = userData.bookmarks.contains(venue.venueId);
            bool isFaved = userData.favorites.contains(venue.venueId);

            return GridTileBar(
              backgroundColor: Colors.black.withOpacity(0.4),
              // Animated bookmark button
              leading: LikeButton(
                // key: _globalKey,
                isLiked: isBookmarked,
                circleColor: const CircleColor(
                  start: firstColor,
                  end: sixthColor,
                ),
                bubblesColor: const BubblesColor(
                  dotPrimaryColor: sixthColor,
                  dotSecondaryColor: Colors.red,
                ),
                // size: 100, // used when testing for closer look
                // size: 50, // size of containing circle
                likeBuilder: (bool bookmarked) {
                  return Icon(
                    Icons.bookmark,
                    color: isBookmarked ? sixthColor : Colors.white,
                    // size: 75, // used when testing for closer look
                    size: 20,
                  );
                },
                animationDuration: animationDuration,

                onTap: (bookmarked) async {
                  print(bookmarked);
                  print(isBookmarked);
                  // return !bookmarked;
                  try {
                    // // if user's email isn't verified
                    if (!user.emailVerified) {
                      showDialog(
                          context: context, builder: (ctx) => VerifyEmail());
                      return bookmarked; // do nothing
                    } else {
                      // actual functionality
                      DatabaseService(uid: user.uid)
                          .toggleToUserList('bookmarks', venue.venueId);
                      ScaffoldMessenger.of(context).hideCurrentSnackBar();
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(
                          elevation: 10,
                          backgroundColor: firstColor,
                          content: !isBookmarked
                              ? const Text('Venue Added to Bookmarks!')
                              : const Text('Venue Removed from Bookmarks'),
                          duration: const Duration(milliseconds: 2500),
                          action: SnackBarAction(
                            textColor: sixthColor,
                            label: 'UNDO',
                            onPressed: () async {
                              // widget.venue.toggleBookmarkStatus();
                              DatabaseService(uid: user.uid)
                                  .toggleToUserList('bookmarks', venue.venueId);

                              // reverse it
                              _db.updateLikesBookmarks(
                                venue.venueId,
                                venue.vendorId,
                                venue.vendorName,
                                venue.address['city'],
                                'bookmarks',
                                bookmarked,
                              );
                            },
                          ),
                        ),
                      );

                      // Track bookmarks
                      // putting "await" with the function stops the animation
                      _db.updateLikesBookmarks(
                        venue.venueId,
                        venue.vendorId,
                        venue.vendorName,
                        venue.address['city'],
                        'bookmarks',
                        !bookmarked,
                      );

                      return !bookmarked;
                    }
                  } catch (e) {
                    print(e.toString());
                    return bookmarked; // dont change it if there was an error
                  }
                },
              ),
              title: venue.ownerManaged
                  ? const Icon(
                      Icons.verified,
                      color: Colors.white,
                    )
                  : const Text(''),

              // Animated Favorites Button
              trailing: LikeButton(
                // key: _globalKey,
                isLiked: isFaved,
                circleColor: const CircleColor(
                  start: firstColor,
                  end: Colors.red,
                ),
                bubblesColor: const BubblesColor(
                  dotPrimaryColor: secondColor,
                  dotSecondaryColor: Colors.red,
                ),
                // size: 100, // used when testing for closer look
                // size: 50, // sizze of containing circle
                likeBuilder: (bool faved) {
                  return Icon(
                    Icons.favorite,
                    color: isFaved ? secondColor : Colors.white,
                    // size: 75, // used when testing for closer look
                    size: 20,
                  );
                },
                animationDuration: animationDuration,

                onTap: (faved) async {
                  // print(faved);
                  // print(isFaved);
                  // return !faved;
                  try {
                    // // if user's email isn't verified
                    if (!user.emailVerified) {
                      showDialog(
                          context: context, builder: (ctx) => VerifyEmail());
                      return faved; // do nothing
                    } else {
                      // actual functionality
                      DatabaseService(uid: user.uid)
                          .toggleToUserList('favorites', venue.venueId);
                      ScaffoldMessenger.of(context).hideCurrentSnackBar();
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(
                          // behavior: SnackBarBehavior.floating,
                          elevation: 10,
                          backgroundColor: firstColor,
                          content: !isFaved
                              ? const Text('Venue Added to Favorites!')
                              : const Text('Venue Removed from Favorites'),
                          duration: const Duration(milliseconds: 2500),
                          action: SnackBarAction(
                            textColor: sixthColor,
                            label: 'UNDO',
                            onPressed: () async {
                              // widget.venue.toggleBookmarkStatus();
                              DatabaseService(uid: user.uid)
                                  .toggleToUserList('favorites', venue.venueId);

                              // reverse it
                              _db.updateLikesBookmarks(
                                venue.venueId,
                                venue.vendorId,
                                venue.vendorName,
                                venue.address['city'],
                                'likes',
                                faved,
                              );
                            },
                          ),
                        ),
                      );

                      // Track bookmarks
                      // putting "await" with the function stops the animation
                      _db.updateLikesBookmarks(
                        venue.venueId,
                        venue.vendorId,
                        venue.vendorName,
                        venue.address['city'],
                        'likes',
                        !faved,
                      );

                      return !faved;
                    }
                  } catch (e) {
                    print(e.toString());
                    return faved; // dont change it if there was an error
                  }
                },
              ),
            );
          } else if (user.isAnonymous) {
            return GridTileBar(
              backgroundColor: Colors.black.withOpacity(0.35),
              leading: IconButton(
                icon: const Icon(
                  Icons.bookmark_border,
                  color: Colors.white,
                  size: 20,
                ),
                onPressed: () {
                  showDialog(context: context, builder: (ctx) => AnonDialog());
                },
              ),
              title: const Text(''),
              trailing: IconButton(
                icon: const Icon(
                  Icons.favorite_border,
                  size: 20,
                ),
                onPressed: () {
                  showDialog(context: context, builder: (ctx) => AnonDialog());
                },
              ),
            );
          } else {
            // fake tile bar with no functionality
            return GridTileBar(
              backgroundColor: Colors.black.withOpacity(0.35),
              leading: IconButton(
                icon: const Icon(
                  Icons.bookmark_border,
                  color: Colors.white,
                  size: 20,
                ),
                onPressed: () {},
              ),
              title: const Text(''),
              trailing: IconButton(
                icon: const Icon(
                  Icons.favorite_border,
                  size: 20,
                ),
                onPressed: () {},
              ),
            );
          }
        });
  }

  Widget _gridTileFooter(size) {
    return GridTileBar(
      backgroundColor: Colors.black87,
      // title
      title: Padding(
        padding: const EdgeInsets.only(top: 1.0, bottom: 3),
        child: Text(
          venue.venueName,
          overflow: TextOverflow.ellipsis,
          softWrap: true,
          maxLines: 2,
          textAlign: TextAlign.left,
          style: const TextStyle(
              color: Colors.white,
              fontFamily: 'Poppins',
              fontSize: 12,
              height: 1),
        ),
      ),
      // subtitle column
      subtitle: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          // price icons
          FittedBox(
            fit: BoxFit.fitWidth,
            child: PricingIconsDB(venue),
          ),
          // Google rating
          Container(
            padding: const EdgeInsets.only(
              left: 2,
              top: 2,
            ),
            width: size.width * 0.25,
            child: FittedBox(
              fit: BoxFit.fitWidth,
              child: Row(
                children: [
                  const Icon(
                    FontAwesomeIcons.google,
                    color: Colors.white70,
                    // color: firstColor,
                    size: 10,
                  ),
                  const Text(' Rating: '),
                  Text(
                    venue.googleRating == null
                        ? '-'
                        : venue.googleRating
                            .toStringAsFixed(1)
                            .replaceAll(RegExp(r"([.]*0)(?!.*\d)"), ""),
                    // '4.1',
                    style: const TextStyle(color: firstColor),
                  ),
                  const Icon(
                    Icons.star,
                    // color: Colors.white,
                    color: firstColor,
                    size: 15,
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
      trailing: Container(
          height: size.width * 0.126,
          width: size.width * 0.126,
          clipBehavior: Clip.antiAlias,
          decoration: const BoxDecoration(
            shape: BoxShape.circle,
          ),
          child: venue.vendorLogoUrl == null || venue.vendorLogoUrl.isEmpty
              ? Container(
                  color: Colors.black26,
                )
// What I used as placeholders before the crashes started:
              // The gifs may be expensive in terms of RAM - 200KB - 500KB each
              // ? Image.asset(
              //     // restaurant or cafe
              //     // loadedVenue
              //     //         .businessCategories
              //     //         .contains('c2')
              //     venue.venueCategories["restaurant"]
              //         ? 'assets/images/food_logo.gif'
              //         :
              //         // bar or pub
              //         // loadedVenue
              //         //         .businessCategories
              //         //         .contains('c3')
              //         venue.venueCategories["barOrPub"]
              //             ? 'assets/images/cocktail_logo.gif'
              //             : venue.venueCategories["cafe"]
              //                 ? 'assets/images/cafe_logo.gif'
              //                 :
              //                 // cinema
              //                 'assets/images/cinema_logo.gif')
              : CachedNetworkImage(
                  imageUrl: venue.vendorLogoUrl,
                  placeholder: (context, url) =>
                      Container(color: Colors.black26),
                  errorWidget: (context, url, error) => const Icon(Icons.error),
                  fadeInDuration: const Duration(milliseconds: 700),
                )
// What I used before incorporating CachedNetworkImage for this crash 
          // : FadeInImage.assetNetwork(
          //     placeholder: venue.venueCategories["restaurant"]
          //         ? 'assets/images/food_logo.gif'
          //         :
          //         // bar or pub
          //         // loadedVenue
          //         //         .businessCategories
          //         //         .contains('c3')
          //         venue.venueCategories["barOrPub"]
          //             ? 'assets/images/cocktail_logo.gif'
          //             : venue.venueCategories["cafe"]
          //                 ? 'assets/images/cafe_logo.gif'
          //                 :
          //                 // cinema
          //                 'assets/images/cinema_logo.gif',
          //     image: venue.vendorLogoUrl,
          //     fit: BoxFit.fitHeight,
          //   ),
          ),
    );
  }
}

下面是使用这些PlaceItems的Sliver Grid:

SliverGrid(
                      
                        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                          crossAxisCount: constraints.maxWidth > 700 ? 3 : 2,
                          mainAxisSpacing: size.width * 0.025,
                          crossAxisSpacing: size.width * 0.025,
                          childAspectRatio: 1,
                        ),
                        delegate: SliverChildBuilderDelegate(
                          (ctx, i) => ChangeNotifierProvider.value(
                            value: _places[i],
                            child: PlaceItem(_places[i]),
                          ),
                          childCount: _places.length,
                        ),
                      ),

它作为其中一个薄片驻留在CustomScrollView中。

7vux5j2d

7vux5j2d1#

正如你所料,崩溃与图像有关。我在构建一个使用大量图像的应用程序时,也遇到了你遇到的同样问题。
崩溃可能发生在内存不足(大量实时图像)和/或同时运行多个https请求(下载图像)时。
您可以清除图像缓存,然后在包含图像的网格上快速滚动,以重现此问题。
解决办法可以是:
使用较低的图像分辨率,或者在显示图像之前调整其大小。
或者,如果您想使用高分辨率图像,可以尝试通过在正在使用的ImageProvider上调用evict方法来手动处理图像,如下面的示例所示。

final imageProvider = NetworkImage(bytes);
    imageProvider.evict();

但是,在调用evict之前,您需要确保映像已完全加载,否则它将无效。
确保不要同时下载太多的图像,这可能会很棘手。一种可能性是在StatefulWidget中手动下载图像,然后在dispose方法中取消下载。
我已经开发了一个包来解决这个问题disposable_cached_images.给予它一个尝试,如果它解决了这个问题,你可以使用它或修改源代码,以获得所需的行为。

相关问题