Flutter加载图片流程之ImageProvider源码示例解析
作者:Nicholas68 发布时间:2021-07-19 05:05:19
加载网络图片
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 = () => <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
对象,并将其设置为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
方法即可。
这个方法是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<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<bool>
对象,表示删除是否成功。如果缓存中没有找到要删除的图片,则返回false
。
列表快速滑动,内存暴增时,可以用这个方法做些事情。
来源:https://juejin.cn/post/7219621949777117243
猜你喜欢
- 本文实例讲述了C#实现在启动目录创建快捷方式的方法。分享给大家供大家参考。具体如下:添加引用,选择 COM 选项卡并选择 Windows S
- 强调一下阅读系统源码,起码要对进程间通信要了解,对binder机制非常非常清楚,binder就是指南针,要不然你会晕头转向;强行阅读,就容易
- AndroidStduio3.0使用gradle将module打包jar文件,首先需要安装gradle。打开控制台输入  
- 1、什么是Java反射机制?在程序运行中动态地获取类的相关属性,同时调用对象的方法和获取属性,这种机制被称之为Java反射机制下面给出一个反
- 单选题:(每道题目2分)1. 下列哪个声明是错误的?(B) A. int i=10;B. float f=1.1;&
- 一、问题描述有时候,我们会遇到在遍历List集合的过程中删除数据的情况。看着自己写的代码,感觉完全没有问题,但就是达不到预期的效果,这是为什
- 现象正常情况下修改完代码,运行项目就会立即生效的。但是突然有一天发现运行的还是老的代码,新代码根本没有生效。通过 mvn clean、 in
- @PathVariable接收两个参数首先@PathVariable无法接收对象,但是可以接收多个值var data = obj.data;
- 自己定义的栈的接口,完全是按照栈的常用方法以及命名方式实现: 注意以下类,接口都是在一个命名空间下栈的接口:包括了常用的方法namespac
- 背景介绍在一些需求中,可能存在某些场景,比如先加载自己的bean,然后自己的bean做一些DB操作,初始化配置问题,然后后面的bean基于这
- Windows 服务在后台执行着各种各样任务,支持着我们日常的桌面操作。有时候可能需要服务与用户进行信息或界面交互操作,这种方式在XP 时代
- 本文实例为大家分享了Java实现石头剪刀布的具体代码,供大家参考,具体内容如下代码:package com.neusoft.test;imp
- 本文为大家分享了类似微信朋友圈,点击+号图片,可以加图片功能,供大家参考,具体内容如下xml:<?xml version="
- 1,Java中操作方法:import java.io.*; public class FileInputStreamTest &
- 将脚本挂在要判断声音是否播放完毕的物体上using System.Collections;using UnityEngine;using U
- Java对称加密Cipher实现对称加密public class EncrypDES { // 字符串默认键值 &
- 把最近听的写的一些题目做下笔记!1.下列程序的执行,说法错误的是 ( ABC )public class MultiCatch
- 多级缓存在实际开发项目,为了减少数据库的访问压力,都会将数据缓存到内存中比如:Redis(分布式缓存)、EHCHE(JVM内置缓存).例如在
- 这段时间想到一个有趣的功能,就是在Android的代码编译期间进行一些骚操作,来达到一些日常情境下难以实现的功能,比如监听应用中的所有onC
- 前言本文将实现一个MyBatis的Springboot的Starter包,引用这个Starter包后,仅需要提供少量配置信息,就能够完成My