验证码: 看不清楚,换一张 查询 注册会员,免验证
  • {{ basic.site_slogan }}
  • 打开微信扫一扫,
    您还可以在这里找到我们哟

    关注我们

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

阅读:856 来源:乙速云 作者:代码code

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 = () => [          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对象,并将其设置为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<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方法调用的,其参数包括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,
           () => 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的异步方法,它的作用是从图像缓存中删除给定配置下的图片。它有两个可选参数:cacheconfiguration。如果cache参数为null,则默认使用全局的imageCacheconfiguration参数是一个图像配置,它用于获取将要从缓存中删除的图片的键值。这个方法返回一个Future对象,表示删除是否成功。如果缓存中没有找到要删除的图片,则返回false

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

      困惑解答

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

    分享到:
    *特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: hlamps#outlook.com (#换成@)。
    相关文章
    {{ v.title }}
    {{ v.description||(cleanHtml(v.content)).substr(0,100)+'···' }}
    你可能感兴趣
    推荐阅读 更多>