Flutter加载图片流程之ImageProvider源码示例解析 天天观察
目录
加载网络图片ImageProviderresolveobtainKeyresolveStreamForKeyloadBufferload(被废弃)evict总结困惑解答加载网络图片
Image.network()
是Flutter提供的一种从网络上加载图片的方法,它可以从指定的URL加载图片,并在加载完成后将其显示在应用程序中。本节内容,我们从源码出发,探讨下图片的加载流程。
(相关资料图)
ImageProvider
ImageProvider
是Flutter中一个抽象类,它定义了一种用于加载图片的通用接口,可以用于加载本地图片、网络图片等各种类型的图片。
ImageProvider
类包含两个核心方法:obtainKey
和loadBuffer
。
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 = () =>[ DiagnosticsProperty ("Image provider", this), DiagnosticsProperty ("Image configuration", configuration), DiagnosticsProperty ("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
对象,并将其设置为ImageStream
的completer
属性,表示图片加载失败。
3、如果唯一标识符获取成功,则会调用resolveStreamForKey
方法来解析图片,并将图片数据存储到ImageStream
对象中,供后续使用。
4、该方法是ImageProvider
类层次结构的公共入口点,因为它是所有图片提供器的解析方法。子类只需要实现obtainKey
和load
方法来获取图片的唯一标识符和加载图片的数据,而不需要重写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<T> 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
方法调用的,其参数包括ImageConfiguration
、ImageStream
、key
和errorHandler
。子类可以通过实现resolveStreamForKey
方法来管理图片的实际解析过程,同时也可以通过调用errorHandler
来处理解析过程中可能发生的错误。实现resolveStreamForKey
方法时,子类可以考虑使用key
与ImageCache
交互,例如调用ImageCache.putIfAbsent
方法,并向stream
通知监听器。默认实现已经使用key
与ImageCache
交互,子类可以选择调用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, () => 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
方法即可。
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<void>((bool success) { /// if (success) { /// debugPrint("removed image!"); /// } /// }); /// } /// } /// ``` /// {@end-tool} Future<bool> evict({ ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty }) async { cache ??= imageCache; final T key = await obtainKey(configuration); return cache.evict(key); }
这是一个名为evict
的异步方法,它的作用是从图像缓存中删除给定配置下的图片。它有两个可选参数:cache
和configuration
。如果cache
参数为null,则默认使用全局的imageCache
。configuration
参数是一个图像配置,它用于获取将要从缓存中删除的图片的键值。这个方法返回一个Future
对象,表示删除是否成功。如果缓存中没有找到要删除的图片,则返回false
。
列表快速滑动,内存暴增时,可以用这个方法做些事情。
总结
ImageProvider
是Flutter中一个用于提供图像数据的抽象类,它定义了如何从不同的数据源(如文件系统、网络、内存)中获取图像数据,并将其转换成ImageStreamCompleter
对象,以供Image
组件使用。
在Flutter中,使用Image
组件来加载和显示图像,需要先提供一个ImageProvider
对象作为其image
属性的值。ImageProvider
类包含了两个关键的方法:obtainKey
和load
。
obtainKey
方法用于获取一个用于唯一标识图像数据的ImageProvider
对象,这个对象可以用来缓存图像数据,以便在需要重新加载图像时能够快速获取到它。例如,AssetImage
类使用图片资源的路径作为其ImageProvider
对象的标识符,以便在需要重新加载该资源时能够快速地从内存或磁盘缓存中获取到它。
load
方法用于获取一个ImageStreamCompleter
对象,该对象包含了用于绘制图像的图像数据。在Flutter 2.5之前,load
方法是一个抽象方法,必须由子类实现。但是从Flutter 2.5开始,load
方法已被废弃,取而代之的是resolve
方法。resolve
方法接受一个ImageConfiguration
参数,并返回一个Future
对象。它与load
方法的功能类似,都是用于获取图像数据,并将其转换成ImageStreamCompleter
对象,以供Image
组件使用。
使用ImageProvider
类加载和显示图像的流程如下:
ImageProvider
对象,该对象提供了图像数据的来源和标识符。使用ImageProvider
对象作为Image
组件的image
属性的值。Image
组件会调用obtainKey
方法获取一个用于唯一标识图像数据的ImageProvider
对象。Image
组件会调用resolve
方法获取一个Future
对象。当图像数据加载完成后,ImageStreamCompleter
对象会将其通知给Image
组件,Image
组件会将其渲染到屏幕上。
总的来说,ImageProvider
类是Flutter中一个非常重要的类,它提供了一种方便的方式来加载和显示图像。虽然load
方法已被废弃,但是resolve
方法提供了更好的替代方案,可以用于获取图像数据并将其转换成ImageStreamCompleter
对象。
参考链接
Flutter系统网络图片加载流程解析_Android
Flutter入门系列(四)---Flutter图片缓存
Flutter | Image 源码分析与优化方式
困惑解答
第一次加载图片时,stream
对象通常没有completer
。在第一次调用resolveStreamForKey
时,会将stream
对象的completer
与对应的ImageCache
的ImageStreamCompleter
进行绑定,并且completer
会被设置为ImageStreamCompleter
。
以上就是Flutter加载图片流程之ImageProvider源码示例解析的详细内容,更多关于Flutter加载图片ImageProvider的资料请关注脚本之家其它相关文章!