软件编程
位置:首页>> 软件编程>> flutter编程>> Flutter加载图片流程之ImageProvider源码示例解析

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

作者:Nicholas68  发布时间:2021-07-19 05:05:19 

标签:Flutter,加载图片,ImageProvider

加载网络图片

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

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

来源:https://juejin.cn/post/7219621949777117243

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com