我的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中。
1条答案
按热度按时间7vux5j2d1#
正如你所料,崩溃与图像有关。我在构建一个使用大量图像的应用程序时,也遇到了你遇到的同样问题。
崩溃可能发生在内存不足(大量实时图像)和/或同时运行多个https请求(下载图像)时。
您可以清除图像缓存,然后在包含图像的网格上快速滚动,以重现此问题。
解决办法可以是:
使用较低的图像分辨率,或者在显示图像之前调整其大小。
或者,如果您想使用高分辨率图像,可以尝试通过在正在使用的
ImageProvider
上调用evict
方法来手动处理图像,如下面的示例所示。但是,在调用
evict
之前,您需要确保映像已完全加载,否则它将无效。确保不要同时下载太多的图像,这可能会很棘手。一种可能性是在
StatefulWidget
中手动下载图像,然后在dispose方法中取消下载。我已经开发了一个包来解决这个问题disposable_cached_images.给予它一个尝试,如果它解决了这个问题,你可以使用它或修改源代码,以获得所需的行为。