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

目录
  • 加载网络图片
  • ImageProvider
    • resolve
    • obtainKey
    • resolveStreamForKey
    • loadBuffer
    • load(被废弃)
    • evict
  • 总结
  • 困惑解答

加载网络图片

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的资料请关注我们其它相关文章!

(0)

相关推荐

  • Flutter图片与文件选择器使用实例

    目录 引言 一.image_picker 1.安装 2.使用 3.属性 4.注意 二.flutter_document_picker 1.安装 2.使用 总结 引言 我已经一个多星期没碰过电脑了,今日上班,打开电脑的第一件事就是想着写点什么.反正大家都还沉浸在节后的喜悦中,还没进入工作状态,与其浪费时间,不如做些更有意义的事情. 今天就跟大家简单分享一下Flutter开发过程中经常会用到的图片和文件选择器. 一.image_picker 一个适用于iOS和Android的Flutter插件,能够

  • Flutter加载图片的多样玩法汇总

    目录 加载本地图片 圆角本地图片 效果图 代码 加载网络图片-本地图片占位图 加载网络图片-loading 效果 代码 圆角.边框.渐变 总结 加载本地图片 在项目目录下创建assets文件夹,再在其文件夹下创建images文件夹,后面将需要的图片复制到其中即可 在pubspec.yaml文件中添加引用 flutter: uses-material-design: true assets: - assets/images/ 在Container中加载本地图片 Container( width:

  • Flutter 图片开发核心技能快速掌握教程

    目录 正文 使用网络图片 把网络图片缓存到磁盘 使用 assets 图片 适配浅色与深色模式 在不同的设备使用不同分辨率的图片 关于设备 dpr 不完全匹配的处理 忽略 dpr 信息 使用相册图片 使用相机拍摄的图片 使用内存图片 图片用做装饰 图片预加载 centerSlice centerSlice 只能放大,不能缩小. 全局缓存 ImageCache 的设置 图片类之间的关系 ImageProvider obtainKey(ImageConfiguration) 方法 resolve(Im

  • Flutter图片缓存管理ImageCache原理分析

    目录 引言 PaintingBinding 减少图片缓存 增大阀值 思考 引言 设计: 嗯? 这个图片点击跳转进详情再返回图片怎么变白闪一下呢?产品: 是啊是啊! 一定是个bug开发: 囧囧囧 在开发过程中, 也许你也遇到过这样一个场景. 进入一个页面后,前一个页面的图片都会闪白一下. 或者在列表中,加载很多列表项后,之前列表中的图片都需要重新加载.你有没有想过这一切的原因是什么呢? 没错! 它就是我们今天介绍的主人公 --- ImageCache 可能有些人对ImageCache还有些陌生,

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

    目录 ImageCache _pendingImages._cache._liveImages maximumSize.currentSize clear evict _touch _checkCacheSize _trackLiveImage putIfAbsent clearLiveImages 答疑解惑 ImageCache const int _kDefaultSize = 1000; const int _kDefaultSizeBytes = 100 << 20; // 100 M

  • Flutter加载图片流程MultiFrameImageStreamCompleter解析

    目录 MultiFrameImageStreamCompleter _handleCodecReady _decodeNextFrameAndSchedule _codec!.getNextFrame() _emitFrame(重要方法, 通知监听器触发回调,更新UI) _scheduleAppFrame _handleAppFrame addListener removeListener _maybeDispose 总结 MultiFrameImageStreamCompleter Multi

  • 详解Flutter网络图片本地缓存的实现

    目录 一.问题 二.思路 三.实现 四.使用 五.缓存清理 一.问题 Flutter原有的图片缓存机制,是通过PaintingBinding.instance!.imageCache来管理缓存的,这个缓存缓存到的是内存中,每次重新打开APP或者缓存被清理都会再次进行网络请求,大图片加载慢不友好,且增加服务器负担. 二.思路 1.查看FadeInImage.assetNetwork.Image.network等几个网络请求的命名构造方法,初始化了ImageProvider. FadeInImage

  • flutter图片组件核心类源码解析

    目录 导语 问题 Image的核心类图及其关系 网络图片的加载过程 网络图片数据的回调和展示过程 补上图片内存缓存的源码分析 如何支持图片的磁盘缓存 总结 导语 在使用flutter 自带图片组件的过程中,大家有没有考虑过flutter是如何加载一张网络图片的? 以及对自带的图片组件我们可以做些什么优化? 问题 flutter 网络图片是怎么请求的? 图片请求成功后是这么展示的? gif的每一帧是怎么支持展示的? 如何支持图片的磁盘缓存? 接下来,让我们带着问题一起探究flutter 图片组件的

  • flutter中的资源和图片加载示例详解

    目录 封面图 指定相应的资源 资源绑定 Asset bundling 资源变体 加载资源 加载文本资源 加载图片 加载依赖包中的图片 最后 封面图 下个季度的目标是把前端监控相关的内容梳理出来,梳理出来之后可能会在公司内部做个分享- Flutter应用程序既括代码也包括一些其他的资产,我们通常这些资产为资源. 有时候我会思考assets这个单词,在程序中到底应该翻译为资产呢?还是翻译为资源?按照习惯,我们这里还是称为资源好了- 这些资源是一些与应用程序捆绑在一起和并且部署应用时会用到的的文件,在

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

    目录 加载网络图片 ImageProvider resolve obtainKey resolveStreamForKey loadBuffer load(被废弃) evict 总结 困惑解答 加载网络图片 Image.network()是Flutter提供的一种从网络上加载图片的方法,它可以从指定的URL加载图片,并在加载完成后将其显示在应用程序中.本节内容,我们从源码出发,探讨下图片的加载流程. ImageProvider ImageProvider是Flutter中一个抽象类,它定义了一种

  • SpringBoot自定义加载yml实现方式,附源码解读

    目录 自定义加载yml,附源码解读 解决方法 源码解读 如何引入多个yml方法 方案一:无前缀,使用@Value注解 方案二:有前缀,无需@Value注解 自定义加载yml,附源码解读 昨天在对公司的微服务配置文件标准化的过程中,发现将原来的properties文件转为yml文件之后,微服务module中标记有@Configuration的配置类都不能正常工作了,究其原因,是由于@PropertySource属性默认只用于标记并告诉spring boot加载properties类型的文件 spr

  • MyBatis SqlSource源码示例解析

    目录 正文 SqlNode SqlNode接口定义 BoundSql SqlSource SqlSource解析时机 SqlSource调用时机 总结 正文 MyBatis版本:3.5.12. 本篇讲从mybatis的角度分析SqlSource.在xml中sql可能是带?的预处理语句,也可能是带$或者动态标签的动态语句,也可能是这两者的混合语句. SqlSource设计的目标就是封装xml的crud节点,使得mybatis运行过程中可以直接通过SqlSource获取xml节点中解析后的SQL.

  • Flink 侧流输出源码示例解析

    目录 Flink 侧流输出源码解析 源码解析 TimestampedCollector#collect CountingOutput#collect BroadcastingOutputCollector#collect RecordWriterOutput#collect ProcessOperator#ContextImpl#output CountingOutput#collect BroadcastingOutputCollector#collect RecordWriterOutput

  • JS前端操作 Cookie源码示例解析

    目录 引言 源码分析 使用 源码 分析 set get remove withAttributes & withConverter 总结 引言 前端操作Cookie的场景其实并不多见,Cookie也因为各种问题被逐渐淘汰,但是我们不用Cookie也可以学习一下它的思想,或者通过这次的源码来学习其他的一些知识. 今天带来的是:js-cookie 源码分析 使用 根据README,我们可以看到js-cookie的使用方式: // 设置 Cookies.set('name', 'value'); //

  • OpenMP task construct 实现原理及源码示例解析

    目录 前言 从编译器角度看 task construct Task Construct 源码分析 总结 前言 在本篇文章当中主要给大家介绍在 OpenMP 当中 task 的实现原理,以及他调用的相关的库函数的具体实现. 在本篇文章当中最重要的就是理解整个 OpenMP 的运行机制. 从编译器角度看 task construct 在本小节当中主要给大家分析一下编译器将 openmp 的 task construct 编译成什么样子,下面是一个 OpenMP 的 task 程序例子: #inclu

  • Python 装饰器常用的创建方式及源码示例解析

    目录 装饰器简介 基础通用装饰器 源码示例 执行结果 带参数装饰器 源码示例 源码结果 源码解析 多装饰器执行顺序 源码示例 执行结果 解析 类装饰器 源码示例 执行结果 解析 装饰器简介 装饰器(decorator)是一种高级Python语法.可以对一个函数.方法或者类进行加工.在Python中,我们有多种方法对函数和类进行加工,相对于其它方式,装饰器语法简单,代码可读性高.因此,装饰器在Python项目中有广泛的应用.修饰器经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理

  • 如何使用PHP+jQuery+MySQL实现异步加载ECharts地图数据(附源码下载)

    ECharts地图主要用于地理区域数据的可视化,展示不同区域的数据分布信息.ECharts官网提供了中国地图.世界地图等地图数据下载,通过js引入或异步加载json文件的形式调用地图. 效果演示      源码下载 本文将结合实例讲解如何使用PHP+jQuery+MySQL实现异步加载ECharts地图数据,我们以中国地图为例,展示去年(2015年)我国各省份GDP数据.通过异步请求php,读取mysql中的数据,然后展示在地图上,因此本文除了你掌握前端知识外,还需要你了解PHP以及MySQL方

  • React Refs 的使用forwardRef 源码示例解析

    目录 三种使用方式 1. String Refs 2. 回调 Refs 3. createRef 两种使用目的 Refs 转发 createRef 源码 forwardRef 源码 三种使用方式 React 提供了 Refs,帮助我们访问 DOM 节点或在 render 方法中创建的 React 元素. React 提供了三种使用 Ref 的方式: 1. String Refs class App extends React.Component { constructor(props) { su

随机推荐