千家信息网

怎么理解Flutter图片加载与缓存机制

发表于:2026-11-03 作者:千家信息网编辑
千家信息网最后更新 2026年11月03日,本篇内容主要讲解"怎么理解Flutter图片加载与缓存机制",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"怎么理解Flutter图片加载与缓存机制"吧!前言
千家信息网最后更新 2026年11月03日怎么理解Flutter图片加载与缓存机制

本篇内容主要讲解"怎么理解Flutter图片加载与缓存机制",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"怎么理解Flutter图片加载与缓存机制"吧!

    前言

    今天来学习一下 Flutter 自身是如何加载图片和管理图片的。

    Flutter 提供了一个图片控件 Image,Image 定义了若干中加载图片的方式,包括 Image.asset、Image.file、Image.network、Image.memory。

    Image内部维护了一个 ImageProvider对象,ImageProvider则真正维护整个图片加载的工作。Widget 本身内部是体现在 RawImage中:

    图片控件

    // ImageWidget result = RawImage(      image: _imageInfo?.image,      debugImageLabel: _imageInfo?.debugLabel,      width: widget.width,      height: widget.height,      scale: _imageInfo?.scale ?? 1.0,      color: widget.color,      colorBlendMode: widget.colorBlendMode,      fit: widget.fit,      alignment: widget.alignment,      repeat: widget.repeat,      centerSlice: widget.centerSlice,      matchTextDirection: widget.matchTextDirection,      invertColors: _invertColors,      isAntiAlias: widget.isAntiAlias,      filterQuality: widget.filterQuality,    );return result;

    这里可以看到 _imageInfo 决定 RawImage如何展示图片。

    _imageInfo 则会在图片的每一帧进行重新赋值:

    // image.dartvoid _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) {   setState(() {    _imageInfo = imageInfo;  }}

    那么图片信息是从哪里来的呢,它是由 _resolveImage 这个方法发起的。这个方法会在 _ImageState 的 didChangeDependencies、 didUpdateWidget和 reassemble方法进行调用。

    也就是控件发生变化刷新状态的时候,就会重新去解析图片。

    图片解析

    _resolveImage 逻辑如下:

    void _resolveImage() {        final ScrollAwareImageProvider provider = ScrollAwareImageProvider(      context: _scrollAwareContext,      imageProvider: widget.image,    );        final ImageStream newStream =      provider.resolve(createLocalImageConfiguration(        context,        size: widget.width != null && widget.height != null ? Size(widget.width, widget.height) : null,      ));        _updateSourceStream(newStream);}

    这里会用 ScrollAwareImageProvider 包装一下,ScrollAwareImageProvider 的功能我们后面会介绍,这里先跳过。

    //ImageProvider# resolveImageStream resolve(ImageConfiguration configuration) {        _createErrorHandlerAndKey(configuration,(T key, ImageErrorListener errorHandler) {        resolveStreamForKey(configuration, stream, key, errorHandler);      },                (T? key, dynamic exception, StackTrace? stack) async {        await null; // wait an event turn in case a listener has been added to the image stream.        final _ErrorImageCompleter imageCompleter = _ErrorImageCompleter();        stream.setCompleter(imageCompleter);        InformationCollector? collector;        assert(() {          collector = () sync* {            yield DiagnosticsProperty('Image provider', this);            yield DiagnosticsProperty('Image configuration', configuration);            yield DiagnosticsProperty('Image key', key, defaultValue: null);          };          return true;        }());        imageCompleter.setError(          exception: exception,          stack: stack,          context: ErrorDescription('while resolving an image'),          silent: true, // could be a network error or whatnot          informationCollector: collector,        );      }        );}

    resolve 方法调用 _createErrorHandlerAndKey 来处理图片加载的异常情况。当图片正常加载的时候,会执行 resolveStreamForKey。

    //resolveStreamForKeyvoid resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) {        if (stream.completer != null) {                final ImageStreamCompleter? completer = PaintingBinding.instance!.imageCache!.putIfAbsent(        key,        () => stream.completer!,        onError: handleError,      );                return;        }        final ImageStreamCompleter? completer = PaintingBinding.instance!.imageCache!.putIfAbsent(                key,      () => load(key, PaintingBinding.instance!.instantiateImageCodec),      onError: handleError,                );        if (completer != null) {      stream.setCompleter(completer);    }}

    Flutter 会把图片缓存相关的逻辑维护在 ImageCache这个对象。

    缓存管理

    ImageCache里面有 3 个 map:

    分别表示

    • 正在加载的图片

    • 缓存在内存的图片

    • 表示正活跃的图片,Widget 状态变化后可能会清空

    新增缓存

    新增缓存的时候会设置 map 的 key, key 由 ImageProvider 对象提供。例如:

    • AssetImage 当包名和bundle一样的时候,key可以认为是一样的。

    • NetworkImage 当图片 url 和比例一样的时候,key可以认为是一样的。

    ImageCache 实际上是一个单例对象。也就是 Flutter 的图片缓存管理是全局的。ImageCache 最重要的方法就是 putIfAbsent:

    // 整理过核心逻辑的代码ImageStreamCompleter? putIfAbsent(Object key, ImageStreamCompleter loader(), { ImageErrorListener? onError }) {  // 根据key从正在加载的map里获取缓存,如果有直接返回      ImageStreamCompleter? result = _pendingImages[key]?.completer;      if (result != null) {      return result;    }    // 检查内存缓存,存在的话更新存活map  final _CachedImage? image = _cache.remove(key);  if (image != null) {    _trackLiveImage(key, _LiveImage(image.completer, image.sizeBytes, () => _liveImages.remove(key)));    _cache[key] = image;    return image.completer;  }    // 没有缓存,从 _live 里面取  final _CachedImage? liveImage = _liveImages[key];  if (liveImage != null) {    // 更新缓存    _touch(key, liveImage, timelineTask);    return liveImage.completer;  }    // 3 个 map 都没有获取到缓存的图片  result = loader(); // 加载  _trackLiveImage(key, _LiveImage(result, null, () => _liveImages.remove(key)));          _PendingImage? untrackedPendingImage;  //定义一个listener        void listener(ImageInfo? info, bool syncCall) {                // 加载的监听        }    // 包装一个listener        final ImageStreamListener streamListener = ImageStreamListener(listener);        if (maximumSize > 0 && maximumSizeBytes > 0) {                // 放入缓存                _pendingImages[key] = _PendingImage(result, streamListener);        } else {                untrackedPendingImage = _PendingImage(result, streamListener);        }        // 添加监听        result.addListener(streamListener);        return result;}

    listener 回调的逻辑:

    在 Image 状态改变的时候,会触发对 liveImages 的修改:

    // Image_imageStream.removeListener(_getListener());// ImageStreamvoid removeListener(ImageStreamListener listener) {  for (final VoidCallback callback in _onLastListenerRemovedCallbacks) {    callback();  }  _onLastListenerRemovedCallbacks.clear();}

    而在 _trackLiveImage 的时候,_LiveImage 都注册了上面的这个 callback:

    _trackLiveImage(key, _LiveImage(image.completer, image.sizeBytes, () => _liveImages.remove(key)));

    这时候改图片会从 _liveImages 里面移除。

    由此可见,缓存的优先级为 pending -> cache -> live -> load,图片缓存和获取的流程如下图所示:

    缓存清理

    在更新缓存大小的时候,还会进行缓存大小的检查:

    void _checkCacheSize(TimelineTask? timelineTask) {  while (_currentSizeBytes > _maximumSizeBytes || _cache.length > _maximumSize) {    final Object key = _cache.keys.first;    final _CachedImage image = _cache[key]!;    _currentSizeBytes -= image.sizeBytes!;    _cache.remove(key);  }}

    当当前缓存总容量大于最大容量或者缓存数量大于最大数量的时候,就会进行缓存的清理。

    所以上面使用缓存的过程中,多次访问的缓存就会把key往后放,避免一上来就被清理掉。

    所以 Flutter 自身的缓存清理算法也是遵循了 "最近最少使用" 的。

    图片加载

    图片加载主要依赖上面的 load方法进行。不同的 ImageProvider 子类有自己的实现。例如

    AssetImage

    return MultiFrameImageStreamCompleter(      codec: _loadAsync(key, decode),      scale: key.scale,      debugLabel: key.name,      informationCollector: collector    );

    NetworkImage

    final StreamController chunkEvents =        StreamController();    return MultiFrameImageStreamCompleter(        chunkEvents: chunkEvents.stream,        codec: _loadAsync(key as NetworkImage, decode, chunkEvents),        scale: key.scale,        debugLabel: key.url,        informationCollector: _imageStreamInformationCollector(key));

    逻辑基本一样,具体特异的流程体现在 loadAsync里面:

    // AssetImage _loadAsynctry {      data = await key.bundle.load(key.name);    } on FlutterError {      PaintingBinding.instance!.imageCache!.evict(key);      rethrow;    }if (data == null) {// 加载数据是null,清掉这个key的缓存        PaintingBinding.instance!.imageCache!.evict(key);        throw StateError('Unable to read data');}return await decode(data.buffer.asUint8List());/// NetworkImage _loadAsyncFuture _loadAsync(      NetworkImage key,      image_provider.DecoderCallback decode,      StreamController chunkEvents) {        final Uri resolved = Uri.base.resolve(key.url);        return ui.webOnlyInstantiateImageCodecFromUrl(resolved, // ignore: undefined_function        chunkCallback: (int bytes, int total) {      chunkEvents.add(ImageChunkEvent(          cumulativeBytesLoaded: bytes, expectedTotalBytes: total));    }) as Future;}

    这里分别会从 bundle 里加载图片和从网络拉取图片。

    滑动中处理

    还记得上面提到的 ScrollAwareImageProvider吗,这里会有一个关于滑动中的判断:

    if (Scrollable.recommendDeferredLoadingForContext(context.context)) {  SchedulerBinding.instance.scheduleFrameCallback((_) {                scheduleMicrotask(() => resolveStreamForKey(configuration, stream, key, handleError));         });        return;}

    当 if 里的逻辑成立,就把解析图片的工作放到下一帧。recommendDeferredLoadingForContext 的具体逻辑:

    static bool recommendDeferredLoadingForContext(BuildContext context) {                final _ScrollableScope widget =                context.getElementForInheritedWidgetOfExactType<_ScrollableScope>()?.widget as _ScrollableScope;        if (widget == null) {      return false;    }        // 存在滑动的widget        return widget.position.recommendDeferredLoading(context);}

    这个会找到 Widget 树里面最近的 _ScrollableScope。如果 ScrollableScope 处于快速滑动的时候,就返回true。所以 flutter 在快速滑动的列表中是不会加载图片的。

    到此,相信大家对"怎么理解Flutter图片加载与缓存机制"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

    图片 缓存 时候 方法 逻辑 对象 机制 控件 状态 学习 更新 管理 最大 也就是 内存 内容 大小 实际 容量 数量 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网科技公司 互联网科技行业发展 互联网属科技 工业互联网科技新基建 互联网汽车科技小镇 开一家科技互联网公司需要多少钱 本届互联网博览会上的黑科技 乌鲁木齐新华智原互联网科技学校 翼展科技 首家互联网医院 沐思想互联网科技有限公司 半导体是互联网高科技吗 工业互联网科技成果转化 上海万顺科技互联网有限公司招聘 科技信息时代互联网发展 科技互联网经济论坛 现代互联网科技公司 保山浩联互联网科技有限公司 上海互联网科技园景观 互联网科技创业计划的风险分析 科技美学互联网 艺术 中智农互联网科技 北京路客互联网科技有限公司 互联网 科技公司 武汉健翔互联网络科技有限公司 东莞新华互联网科技学校 西安小鹰速贷互联网科技有限公司 世界互联网领先科技成果获奖 科技赋能视角下互联网保险发展 互联网金融运营模式中的金融科技 互联网科技大会大佬
    0