Flutter加载图片流程之ImageProvider源码示例解析

加载网络图片 Image network()是Flutter提供的一种从网络上加载图片的方法,它可以从指定的URL加载图片,并在加载完成后将其显示在应用程序中。

加载网络图片

Image.network()是Flutter提供的一种从网络上加载图片的方法,它可以从指定的URL加载图片,并在加载完成后将其显示在应用程序中。本节内容,我们从源码出发,探讨下图片的加载流程。

ImageProvider

ImageProvider是Flutter中一个抽象类,它定义了一种用于加载图片的通用接口,可以用于加载本地图片、网络图片等各种类型的图片。

ImageProvider类包含两个核心方法:obtainKeyloadBuffer

resolve

/// Resolves this image provider using the given `configuration`, returning
/// an [ImageStream].
///
/// This is the public entry-point of the [ImageProvider] class hierarchy.
///
/// Subclasses should implement [obtainKey] and [load], which are used by this
/// method. If they need to change the implementation of [ImageStream] used,
/// they should override [createStream]. If they need to manage the actual
/// resolution of the image, they should override [resolveStreamForKey].
///
/// See the Lifecycle documentation on [ImageProvider] for more information.
@nonVirtual
ImageStream resolve(ImageConfiguration configuration) {
  assert(configuration != null);
  final ImageStream stream = createStream(configuration);
  // Load the key (potentially asynchronously), set up an error handling zone,
  // and call resolveStreamForKey.
  _createErrorHandlerAndKey(
    configuration,
    (T key, ImageErrorListener errorHandler) {
      resolveStreamForKey(configuration, stream, key, errorHandler);
    },
    (T? key, Object exception, StackTrace? stack) async {
      await null; // wait an event turn in case a listener has been added to the image stream.
      InformationCollector? collector;
      assert(() {
        collector = () => <DiagnosticsNode>[          DiagnosticsProperty<ImageProvider>('Image provider', this),          DiagnosticsProperty<ImageConfiguration>('Image configuration', configuration),          DiagnosticsProperty<T>('Image key', key, defaultValue: null),        ];
        return true;
      }());
      if (stream.completer == null) {
        stream.setCompleter(_ErrorImageCompleter());
      }
      stream.completer!.reportError(
        exception: exception,
        stack: stack,
        context: ErrorDescription('while resolving an image'),
        silent: true, // could be a network error or whatnot
        informationCollector: collector,
      );
    },
  );
  return stream;
}

根据文档解释,我们可以了解到以下几点:

1、使用给定的`configuration`解析该图片提供器,返回一个 [ImageStream]。  

2、这是 [ImageProvider] 类层次结构的公共入口点。

3、子类应该实现 [obtainKey] 和 [load] 方法,这两个方法将被该方法使用。 

4、如果子类需要更改使用的 [ImageStream] 的实现,则应该重写 [createStream] 方法。 

5、 如果子类需要管理实际的图像分辨率,则应该重写 [resolveStreamForKey] 方法。 

阅读resolve方法的实现。我们可以知道:

1、它使用给定的configuration参数创建一个ImageStream对象(createStream)。然后调用_createErrorHandlerAndKey方法,该方法会异步获取图片的唯一标识符,并设置一个错误处理区域,以防图片加载过程中发生错误。

2、如果获取唯一标识符的过程中出现异常,则会将错误信息封装成一个_ErrorImageCompleter对象,并将其设置为ImageStreamcompleter属性,表示图片加载失败。

3、如果唯一标识符获取成功,则会调用resolveStreamForKey方法来解析图片,并将图片数据存储到ImageStream对象中,供后续使用。

4、该方法是ImageProvider类层次结构的公共入口点,因为它是所有图片提供器的解析方法。子类只需要实现obtainKeyload方法来获取图片的唯一标识符和加载图片的数据,而不需要重写resolve方法。

5、如果子类需要更改使用的ImageStream的实现方式,则可以重写createStream方法。如果子类需要管理实际的图像分辨率,则可以重写resolveStreamForKey方法。例如,AssetImage类中的createStream方法返回一个AssetBundleImageStreamCompleter对象,该对象用于从应用程序资源中加载图片数据。而NetworkImage类中的resolveStreamForKey方法使用HTTP客户端从网络上加载图片数据。

6、这段代码中还有一些调试信息,例如将图片提供器、图片配置和图片唯一标识符添加到调试信息中,以便在出现错误时进行调试。

obtainKey

/// Converts an ImageProvider's settings plus an ImageConfiguration to a key
/// that describes the precise image to load.
///
/// The type of the key is determined by the subclass. It is a value that
/// unambiguously identifies the image (_including its scale_) that the [load]
/// method will fetch. Different [ImageProvider]s given the same constructor
/// arguments and [ImageConfiguration] objects should return keys that are
/// '==' to each other (possibly by using a class for the key that itself
/// implements [==]).
Future&lt;T&gt; obtainKey(ImageConfiguration configuration);

这段注释是关于obtainKey方法的说明。该方法是ImageProvider的子类应该实现的方法之一,用于将ImageProvider的设置及ImageConfiguration转换为一个可以唯一标识图片的key

不同的ImageProvider根据相同的构造函数参数和ImageConfiguration对象应该返回相等的key,以便于后续加载和缓存图片。key的类型由子类确定,它应该是一个值,可以唯一地标识出要加载的图片(包括其缩放比例)。

在实现obtainKey方法时,子类可以考虑使用自定义的类来表示key,并实现==方法以保证唯一性。

resolveStreamForKey

@protected
void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) {
  // This is an unusual edge case where someone has told us that they found
  // the image we want before getting to this method. We should avoid calling
  // load again, but still update the image cache with LRU information.
  if (stream.completer != null) {
    final ImageStreamCompleter? completer = PaintingBinding.instance.imageCache.putIfAbsent(
      key,
      () => stream.completer!,
      onError: handleError,
    );
    assert(identical(completer, stream.completer));
    return;
  }
  final ImageStreamCompleter? completer = PaintingBinding.instance.imageCache.putIfAbsent(
    key,
    /// 加载
    () => loadBuffer(key, PaintingBinding.instance.instantiateImageCodecFromBuffer),
    onError: handleError,
  );
  if (completer != null) {
    /// 关键是解析并设置ImageStreamCompleter对象
    stream.setCompleter(completer);
  }
}

官方文档解释:

  • 该方法是ImageProvider的子类应该实现的方法之一,用于根据key来解析图片。
  • resolveStreamForKey方法是由resolve方法调用的,其参数包括ImageConfigurationImageStreamkeyerrorHandler。子类可以通过实现resolveStreamForKey方法来管理图片的实际解析过程,同时也可以通过调用errorHandler来处理解析过程中可能发生的错误。
  • 实现resolveStreamForKey方法时,子类可以考虑使用keyImageCache交互,例如调用ImageCache.putIfAbsent方法,并向stream通知监听器。默认实现已经使用keyImageCache交互,子类可以选择调用super.resolveStreamForKey方法或不调用。

从上面的源码,我们可以知道以下几点:

  • 1、如果 stream 对象已经有了 completer(即已经有了可以加载图片的方式),则将 completer 添加到 ImageCache 中,实现缓存功能,并直接返回。
  • 2、如果 stream 对象还没有 completer,则调用 loadBuffer 方法加载图片,并将其返回的 ImageStreamCompleter 对象添加到 ImageCache 中,同时设置到 stream 对象的 completer 中。
  • 3、如果 loadBuffer 方法出现了异常,则会将异常交给 onError 回调处理,以便在异常处理时能够提供详细的错误信息。
  • 4、关键是解析并设置ImageStreamCompleter对象
  • 5、PaintingBinding.instance.imageCache.putIfAbsent方法在内部将ImageStreamListener对象添加到ImageStreamCompleter对象的_listeners数组中了。
   PaintingBinding.instance.imageCache.putIfAbsent(
     key,
     () =&gt; loadBuffer(key, PaintingBinding.instance.instantiateImageCodecFromBuffer),
     onError: handleError,
   )

loadBuffer

/// Converts a key into an [ImageStreamCompleter], and begins fetching the
/// image.
///
/// For backwards-compatibility the default implementation of this method calls
/// through to [ImageProvider.load]. However, implementors of this interface should
/// only override this method and not [ImageProvider.load], which is deprecated.
///
/// The [decode] callback provides the logic to obtain the codec for the
/// image.
///
/// See also:
///
///  * [ResizeImage], for modifying the key to account for cache dimensions.
@protected
ImageStreamCompleter loadBuffer(T key, DecoderBufferCallback decode) {
  return load(key, PaintingBinding.instance.instantiateImageCodec);
}

从源码我们知道, [ImageProvider.load], which is deprecated被废弃了。子类只需要重写loadBuffer方法即可。

  • 这个方法是ImageProvider的一个protected方法,用于从缓存中加载指定的图片。
  • 它接受两个参数:一个是唯一标识图片的key,另一个是一个用于解码图片数据的回调函数decode。
  • 这个方法调用了load方法,然后返回一个ImageStreamCompleter对象,它表示加载过程中的一个数据流。
  • 在load方法中,使用传入的decode回调函数从缓存或网络中获取图片数据并解码,然后将解码后的图片数据传递给ImageStreamCompleter对象,以便它可以生成一个带有正确图片数据的ImageInfo对象,这个ImageInfo对象可以被传递到Image widget中用于显示图片。

load(被废弃)

/// Converts a key into an [ImageStreamCompleter], and begins fetching the
/// image.
///
/// This method is deprecated. Implement [loadBuffer] for faster image
/// loading. Only one of [load] and [loadBuffer] must be implemented, and
/// [loadBuffer] is preferred.
///
/// The [decode] callback provides the logic to obtain the codec for the
/// image.
///
/// See also:
///
///  * [ResizeImage], for modifying the key to account for cache dimensions.
@protected
@Deprecated(
  'Implement loadBuffer for faster image loading. '
  'This feature was deprecated after v2.13.0-1.0.pre.',
)
ImageStreamCompleter load(T key, DecoderCallback decode) {
  throw UnsupportedError('Implement loadBuffer for faster image loading');
}

从注释可知:

这个方法被废弃了,现在已经不再建议使用了。如果需要更快的图像加载,请实现 [loadBuffer] 方法。在 [load] 和 [loadBuffer] 方法中只需要实现其中一个,而且 [loadBuffer] 更受推荐。

[decode] 回调提供了获取图像编解码器的逻辑。

evict

/// Evicts an entry from the image cache.
///
/// Returns a [Future] which indicates whether the value was successfully
/// removed.
///
/// The [ImageProvider] used does not need to be the same instance that was
/// passed to an [Image] widget, but it does need to create a key which is
/// equal to one.
///
/// The [cache] is optional and defaults to the global image cache.
///
/// The [configuration] is optional and defaults to
/// [ImageConfiguration.empty].
///
/// {@tool snippet}
///
/// The following sample code shows how an image loaded using the [Image]
/// widget can be evicted using a [NetworkImage] with a matching URL.
///
/// ```dart
/// class MyWidget extends StatelessWidget {
///   const MyWidget({
///     super.key,
///     this.url = ' ... ',
///   });
///
///   final String url;
///
///   @override
///   Widget build(BuildContext context) {
///     return Image.network(url);
///   }
///
///   void evictImage() {
///     final NetworkImage provider = NetworkImage(url);
///     provider.evict().then&lt;void&gt;((bool success) {
///       if (success) {
///         debugPrint('removed image!');
///       }
///     });
///   }
/// }
/// ```
/// {@end-tool}
Future&lt;bool&gt; evict({ ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty }) async {
  cache ??= imageCache;
  final T key = await obtainKey(configuration);
  return cache.evict(key);
}

这是一个名为evict的异步方法,它的作用是从图像缓存中删除给定配置下的图片。它有两个可选参数:cacheconfiguration。如果cache参数为null,则默认使用全局的imageCacheconfiguration参数是一个图像配置,它用于获取将要从缓存中删除的图片的键值。这个方法返回一个Future<bool>对象,表示删除是否成功。如果缓存中没有找到要删除的图片,则返回false

列表快速滑动,内存暴增时,可以用这个方法做些事情。

总结

ImageProvider是Flutter中一个用于提供图像数据的抽象类,它定义了如何从不同的数据源(如文件系统、网络、内存)中获取图像数据,并将其转换成ImageStreamCompleter对象,以供Image组件使用。

在Flutter中,使用Image组件来加载和显示图像,需要先提供一个ImageProvider对象作为其image属性的值。ImageProvider类包含了两个关键的方法:obtainKeyload

obtainKey方法用于获取一个用于唯一标识图像数据的ImageProvider对象,这个对象可以用来缓存图像数据,以便在需要重新加载图像时能够快速获取到它。例如,AssetImage类使用图片资源的路径作为其ImageProvider对象的标识符,以便在需要重新加载该资源时能够快速地从内存或磁盘缓存中获取到它。

load方法用于获取一个ImageStreamCompleter对象,该对象包含了用于绘制图像的图像数据。在Flutter 2.5之前,load方法是一个抽象方法,必须由子类实现。但是从Flutter 2.5开始,load方法已被废弃,取而代之的是resolve方法。resolve方法接受一个ImageConfiguration参数,并返回一个Future<ImageStreamCompleter>对象。它与load方法的功能类似,都是用于获取图像数据,并将其转换成ImageStreamCompleter对象,以供Image组件使用。

使用ImageProvider类加载和显示图像的流程如下:

  • 创建一个ImageProvider对象,该对象提供了图像数据的来源和标识符。
  • 使用ImageProvider对象作为Image组件的image属性的值。
  • Image组件会调用obtainKey方法获取一个用于唯一标识图像数据的ImageProvider对象。
  • Image组件会调用resolve方法获取一个Future<ImageStreamCompleter>对象。
  • 当图像数据加载完成后,ImageStreamCompleter对象会将其通知给Image组件,Image组件会将其渲染到屏幕上。

总的来说,ImageProvider类是Flutter中一个非常重要的类,它提供了一种方便的方式来加载和显示图像。虽然load方法已被废弃,但是resolve方法提供了更好的替代方案,可以用于获取图像数据并将其转换成ImageStreamCompleter对象。

参考链接

Flutter系统网络图片加载流程解析_Android

Flutter入门系列(四)---Flutter图片缓存

Flutter | Image 源码分析与优化方式

困惑解答

第一次加载图片时,stream对象通常没有completer。在第一次调用resolveStreamForKey时,会将stream对象的completer与对应的ImageCacheImageStreamCompleter进行绑定,并且completer会被设置为ImageStreamCompleter

以上就是Flutter加载图片流程之ImageProvider源码示例解析的详细内容,更多关于Flutter加载图片ImageProvider的资料请关注好代码网其它相关文章!

您可能有感兴趣的文章
Flutter入门学习Dart语言变量及基本如何使用概念

Flutter 异步编程之单线程下异步模型图文示例详解

如何如何使用Flutter开发一款电影APP详解

Flutter进阶之如何实现动画效果(二)

Flutter定义tabbar底部导航路由跳转的方法