Flutter加载图片流程之ImageProvider源码分析

开发技术 作者:iii 2024-05-05 13:30:01
本篇内容主要讲解“Flutter加载图片流程之ImageProvider源码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习...

本篇内容主要讲解“Flutter加载图片流程之ImageProvider源码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“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

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

    困惑解答

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

    到此,相信大家对“Flutter加载图片流程之ImageProvider源码分析”有了更深的了解,不妨来实际操作一番吧!这里是捷杰建站网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

    原创声明
    本站部分文章基于互联网的整理,我们会把真正“有用/优质”的文章整理提供给各位开发者。本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
    本文链接:http://www.jiecseo.com/news/show_23314.html
    flutter