You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的缓存过期策略、最大缓存文件数量,自动清理过期或不常用的缓存文件。

三、修复自定义缓存组件

针对你的自定义组件,通过以下修改避免重复操作:

修改思路

  1. 用FutureBuilder替代手动在initState中管理异步状态,利用Future的缓存特性(同一Future不会重复执行)。
  2. 混入AutomaticKeepAliveClientMixin,防止列表滚动时组件重建丢失状态。
  3. 使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 22:50:54