Flutter中使用ListView、GridView等构建器加载图片如何避免OOM问题?
Flutter列表仅保留可见项图片资源的解决方案
Flutter 默认的 ImageCache 是全局单例,默认会缓存所有已加载的图片资源,直到占用内存达到预设阈值才会触发LRU清理,这就是列表销毁/项滑出可视区后图片仍占用内存的根本原因。要实现仅保留可见项图片的需求,可参考以下方案:
方案1:列表项销毁时手动清理对应缓存
在列表项的生命周期中精准控制单张图片的缓存释放,不会影响其他页面的缓存逻辑:
- 加载图片时为每张图片指定唯一的
cacheKey,ResizeImage、Image.network均支持该参数,作为缓存清理的唯一标识 - 在列表项State的
dispose方法中调用evict方法,将当前项的图片从全局缓存中移除
示例代码:
class ListImageItem extends StatefulWidget { final String imageUrl; const ListImageItem({super.key, required this.imageUrl}); @override State<ListImageItem> createState() => _ListImageItemState(); } class _ListImageItemState extends State<ListImageItem> { late String cacheKey; late ImageProvider imageProvider; @override void initState() { super.initState(); // 生成唯一缓存key,建议结合图片地址、缩放尺寸生成避免冲突 cacheKey = 'img_${widget.imageUrl}_${MediaQuery.of(context).size.width.toInt()}'; imageProvider = ResizeImage( Image.network(widget.imageUrl).image, width: MediaQuery.of(context).size.width.toInt(), cacheKey: cacheKey, ); } @override void dispose() { // 列表项销毁时清理当前图片缓存 PaintingBinding.instance.imageCache.evict(cacheKey); super.dispose(); } @override Widget build(BuildContext context) { return Image(image: imageProvider, fit: BoxFit.cover); } }
方案2:禁用单张图片的全局缓存
如果不需要跨页面/跨列表复用同一张图片,可以直接跳过全局缓存逻辑,完全由列表项自身持有图片资源,列表项销毁时资源会自动被GC回收:
- 自定义
ImageProvider实现加载逻辑,跳过向全局ImageCache存取数据的步骤 - 对于
Image.memory场景,确保没有其他全局变量持有传入的Uint8List引用即可,列表项销毁后对应内存会自动释放
方案3:维护列表局部缓存
如果需要保证短距离滑动的流畅度(避免刚滑过的图片再次滑入时重复加载),可以给当前列表单独维护一个缓存池,缓存容量设置为可见项数量的1.5~2倍,超出容量的旧资源自动清理,全局缓存则禁用当前列表的图片存储,兼顾性能和内存占用控制。
注意事项
- 手动清理缓存前确认对应图片没有其他页面/组件在使用,避免不必要的重复加载
cacheKey的生成规则要和加载图片时的规则完全一致,否则会出现清理失效的问题- 不要直接清空全局
ImageCache,会影响其他页面的正常缓存逻辑
内容的提问来源于stack exchange,提问作者Tom3652
相关产品推荐
相关产品推荐

