Flutter中Cached_Network_Image内存消耗过高及自定义缓存优化咨询
问题描述
- 在带有缩略图的大型列表场景中使用
cached_network_image时,遭遇严重内存消耗问题:每次分页调用setState时,会重复执行图片缓存与显示操作;当列表包含100张缩略图时,会触发全部100张图片的缓存流程。 - 自行实现的自定义缓存组件(代码如下)也存在相同问题:每次
setState都会导致组件重建,重复执行缓存检查、图片缓存、图片显示的完整逻辑。
class Custom_Cached_Image extends StatefulWidget { final String image; final double aspectRatio; final CacheManager cacheManager; final bool memory; final bool isMe; final int cacheWidth; final int cacheHeight; final double height; final double width; final colorProvider; final shimmerBaseColor; final shimmerHighlightColor; const Custom_Cached_Image({super.key, required this.image, required this.aspectRatio, required this.width, required this.cacheManager, required this.memory, required this.isMe, required this.cacheWidth, required this.cacheHeight, required this.height, required this.colorProvider, this.shimmerBaseColor, this.shimmerHighlightColor}); @override State<Custom_Cached_Image> createState() => _Custom_Cached_ImageState(); } class _Custom_Cached_ImageState extends State<Custom_Cached_Image> { String imagePath = ""; late Color shmBaseClr; late Color shmHighLightClr; @override void initState() { var colorProvider1 = Provider.of<themeProvider>(context, listen: false); // ---- Shimmer Base Color ----- // if(widget.shimmerBaseColor != null) { shmBaseClr = widget.shimmerBaseColor; } else { shmBaseClr = colorProvider1.accentColor; } // ---- Shimmer Highlight Color ----- // if(widget.shimmerHighlightColor != null) { shmHighLightClr = widget.shimmerHighlightColor; } else { shmHighLightClr = colorProvider1.shimmerColor; } getTheImagePath(); super.initState(); } Future<String> initImage() async { try { final _file = await widget.cacheManager.getFileFromCache(widget.image); if(_file != null && _file.file.existsSync()) { return _file.file.path; } else { final _nFile = await widget.cacheManager.getSingleFile(widget.image, key: widget.image); return _nFile.path; } } catch (e) { print("Image Caching Config: ${e}"); return "Error Occurred! Please Try Again!"; } } getTheImagePath() async { try { if((imagePath??"").trim().isEmpty) { print("Called!"); if(mounted) { imagePath = await initImage(); if(mounted) { setState(() {}); } } } else { print("Already Called!"); } } catch (e) { print("Error Caching Image (Custom_Caching_Mechanism) : ${e}"); } } @override void setState(VoidCallback fn) { // TODO: implement setState if(mounted) { super.setState(fn); } } @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: widget.aspectRatio, child: (imagePath??"").trim().isNotEmpty ? Image.file( File(imagePath), height: widget.height, width: widget.width, cacheWidth: widget.cacheWidth, cacheHeight: widget.cacheHeight, fit: BoxFit.cover, errorBuilder: (buildContext, object, stackTrace) { return Center( child: Icon(Icons.error, size: 24, color: widget.colorProvider.iconColor,), ); }, ) : Center( child: Shimmer.fromColors( baseColor: shmBaseClr, highlightColor: shmHighLightClr, child: Container( color: widget.colorProvider.accentColor, ), ), ), ); } }
优化方案与自定义缓存实现
一、核心问题分析
你的自定义组件和cached_network_image的重复操作问题,核心原因在于:
- 组件为
StatefulWidget,父组件setState时若无合理状态保留机制,会触发组件重建,initState重新执行,进而重复触发缓存检查与下载逻辑。 - 未利用
flutter_cache_manager的缓存状态监听能力,手动在initState中触发异步操作,组件重建时会重复执行流程。
二、通用优化方案
1. 列表层面优化
- 使用
ListView.builder/GridView.builder替代普通ListView,仅构建可见区域的组件,避免一次性创建所有缩略图组件。 - 为列表项组件混入
AutomaticKeepAliveClientMixin,让组件滚出视图后仍保留状态,防止重建时重复执行缓存逻辑。
2. 图片缓存层面优化
- 为缩略图设置固定的
cacheWidth/cacheHeight,避免缓存原始大图,降低内存占用。 - 配置
flutter_cache_manager的缓存过期策略、最大缓存文件数量,自动清理过期或不常用的缓存文件。
三、修复自定义缓存组件
针对你的自定义组件,通过以下修改避免重复操作:
修改思路
- 用
FutureBuilder替代手动在initState中管理异步状态,利用Future的缓存特性(同一Future不会重复执行)。 - 混入
AutomaticKeepAliveClientMixin,防止列表滚动时组件重建丢失状态。 - 使用
flutter_cache_manager的getSingleFile方法,内部自动处理缓存检查与下载,无需手动分步骤操作。
修改后的代码
class CustomCachedImage extends StatefulWidget { final String image; final double aspectRatio; final CacheManager cacheManager; final bool memory; final bool isMe; final int cacheWidth; final int cacheHeight; final double height; final double width; final themeProvider colorProvider; final Color? shimmerBaseColor; final Color? shimmerHighlightColor; const CustomCachedImage({ super.key, required this.image, required this.aspectRatio, required this.width, required this.cacheManager, required this.memory, required this.isMe, required this.cacheWidth, required this.cacheHeight, required this.height, required this.colorProvider, this.shimmerBaseColor, this.shimmerHighlightColor, }); @override State<CustomCachedImage> createState() => _CustomCachedImageState(); } class _CustomCachedImageState extends State<CustomCachedImage> with AutomaticKeepAliveClientMixin { late Color shmBaseClr; late Color shmHighLightClr; @override void initState() { super.initState(); // 初始化Shimmer颜色 shmBaseClr = widget.shimmerBaseColor ?? widget.colorProvider.accentColor; shmHighLightClr = widget.shimmerHighlightColor ?? widget.colorProvider.shimmerColor; } @override bool get wantKeepAlive => true; // 保留组件状态 @override Widget build(BuildContext context) { super.build(context); // 必须调用以保证AutomaticKeepAlive生效 return AspectRatio( aspectRatio: widget.aspectRatio, child: FutureBuilder<File>( // getSingleFile内部自动处理缓存检查与下载,同一image对应的Future仅执行一次 future: widget.cacheManager.getSingleFile(widget.image, key: widget.image), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { // 加载中显示Shimmer return Shimmer.fromColors( baseColor: shmBaseClr, highlightColor: shmHighLightClr, child: Container(color: widget.colorProvider.accentColor), ); } else if (snapshot.hasError || !snapshot.data!.existsSync()) { // 错误状态 return Center( child: Icon(Icons.error, size: 24, color: widget.colorProvider.iconColor), ); } else { // 显示缓存的图片 return Image.file( snapshot.data!, height: widget.height, width: widget.width, cacheWidth: widget.cacheWidth, cacheHeight: widget.cacheHeight, fit: BoxFit.cover, errorBuilder: (context, object, stackTrace) { return Center( child: Icon(Icons.error, size: 24, color: widget.colorProvider.iconColor), ); }, ); } }, ), ); } }
关键修改点说明
AutomaticKeepAliveClientMixin:让列表项滚出视图后不会被销毁,保留已完成的缓存状态,避免重新执行异步逻辑。FutureBuilder搭配getSingleFile:getSingleFile会先检查本地缓存,存在则直接返回文件,不存在则下载并缓存;同一image对应的Future只会执行一次,即使组件重建(只要widget.image不变),也不会重复触发缓存/下载。- 移除手动维护
imagePath的逻辑,避免不必要的setState触发组件重建。
四、基于flutter_cache_manager的自定义缓存系统进阶配置
若需要更精细的缓存控制,可自定义CacheManager实例:
// 自定义缓存配置 final customCacheManager = CacheManager( Config( 'custom_thumb_cache', // 缓存目录名称 stalePeriod: const Duration(days: 7), // 缓存过期时间 maxNrOfCacheObjects: 200, // 最大缓存文件数量 repo: JsonCacheInfoRepository(databaseName: 'custom_thumb_cache_db'), fileSystem: IOFileSystem('custom_thumb_cache'), ), );
使用时将该实例传入自定义组件即可,它会自动管理缓存清理、过期等逻辑,进一步降低内存与存储占用。
内容的提问来源于stack exchange,提问作者Uraam Asif
相关产品推荐
相关产品推荐

