探索Flutter_Image显示Webp逻辑

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 简介最近探索了一下新增Flutter的Image widget对webp做一个stopAnimation的拓展的Api,顺便了解一下Image整个结构和对一些多帧图片的处理。 我们先看看Image的一个类图结构。

简介

最近探索了一下新增Flutter的Image widget对webp做一个stopAnimation的拓展的Api,顺便了解一下Image整个结构和对一些多帧图片的处理。 我们先看看Image的一个类图结构。

网络异常,图片无法展示
|

其中:

  • ImageProvider 提供加载图片的入口,不同的图片资源加载方式不一样,只要重写其load方法即可。同样,缓存图片的key值也有其生成。
  • FileImage 负责读取文件图片的数据,读取到的文件数据转化成ui.Codec对象交给ImageStreamCompleter去处理解析。
  • ImageStreamCompleter就是逐帧解析图片的类,生成之后会加入ImageCache,下载可以从缓存中得到。
  • ImageStream是处理Image Resource的,ImageState通过ImageStream与ImageStreamCompleter建立联系。ImageStream里也存储着图片加载完毕的监听回调。
  • MultiFrameImageStreamCompleter就是多帧图片解析器。 Flutter imgae支持的图片格式为:JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP, and WBMP。Flutter Image是显示图片的一个Widget。 Flutter Image的几个构造方法:
方法 释义
Image() 从ImageProvider中获取图片,从本质上看,下面的几个方法都是他的具体实现。
Image.asset(String name) 从AssetBundler中获取图片
Image.network(String src) 显示网络图片
Image.file(File file) 从文件中获取图片
Image.memory(Uint8List bytes) 从Uint8List获取数据显示图片

Image

从Image的构造体上看,ImageProvider才是图片提供方,所以我们后面会看看ImageProvider究竟是要做点什么的。 其他的参数是一些图片的属性和一些builder。

ImageState

关键代码:

void didUpdateWidget(Image oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (_isListeningToStream &&
        (widget.loadingBuilder == null) != (oldWidget.loadingBuilder == null)) {
      _imageStream.removeListener(_getListener(oldWidget.loadingBuilder));
      _imageStream.addListener(_getListener());
    }
    if (widget.image != oldWidget.image)
      _resolveImage();
  }
复制代码

ImageProvider

其实ImageProvider是一个抽象类,让需要定制的子类去做一些实现。

比如:FileImage、MemoryImage、ExactAssetImage等等。其中对FileImage的代码进行了一些分析。

class FileImage extends ImageProvider<FileImage> {
  /// Creates an object that decodes a [File] as an image.
  ///
  /// The arguments must not be null.
  const FileImage(this.file, { this.scale: 1.0 })
      : assert(file != null),
        assert(scale != null);
  /// The file to decode into an image.
  final File file;
  /// The scale to place in the [ImageInfo] object of the image.
  final double scale;
  @override
  Future<FileImage> obtainKey(ImageConfiguration configuration) {
    return new SynchronousFuture<FileImage>(this);
  }
  @override
  ImageStreamCompleter load(FileImage key) {
    return new MultiFrameImageStreamCompleter(
      codec: _loadAsync(key),
      scale: key.scale,
      informationCollector: (StringBuffer information) {
        information.writeln('Path: ${file?.path}');
      }
    );
  }
  Future<ui.Codec> _loadAsync(FileImage key) async {
    assert(key == this);
    final Uint8List bytes = await file.readAsBytes();
    if (bytes.lengthInBytes == 0)
      return null;
    return await ui.instantiateImageCodec(bytes);
  }
  @override
  bool operator ==(dynamic other) {
    if (other.runtimeType != runtimeType)
      return false;
    final FileImage typedOther = other;
    return file?.path == typedOther.file?.path
        && scale == typedOther.scale;
  }
  @override
  int get hashCode => hashValues(file?.path, scale);
  @override
  String toString() => '$runtimeType("${file?.path}", scale: $scale)';
}
复制代码

FileImage重写了 obtainKey、load的方法。但是在什么地方会调用这两个重写的方法呢?那肯定是ImageProvider这个父类了。

@optionalTypeArgs
abstract class ImageProvider<T> {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const ImageProvider();
  /// 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.
  ImageStream resolve(ImageConfiguration configuration) {
    assert(configuration != null);
    final ImageStream stream = new ImageStream();
    T obtainedKey;
    obtainKey(configuration).then<void>((T key) {
      obtainedKey = key;
      stream.setCompleter(PaintingBinding.instance.imageCache.putIfAbsent(key, () => load(key)));
    }).catchError(
      (dynamic exception, StackTrace stack) async {
        FlutterError.reportError(new FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'services library',
          context: 'while resolving an image',
          silent: true, // could be a network error or whatnot
          informationCollector: (StringBuffer information) {
            information.writeln('Image provider: $this');
            information.writeln('Image configuration: $configuration');
            if (obtainedKey != null)
              information.writeln('Image key: $obtainedKey');
          }
        ));
        return null;
      }
    );
    return stream;
  }
  /// 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 [==]).
  @protected
  Future<T> obtainKey(ImageConfiguration configuration);
  /// Converts a key into an [ImageStreamCompleter], and begins fetching the
  /// image.
  @protected
  ImageStreamCompleter load(T key);
  @override
  String toString() => '$runtimeType()';
}
复制代码

该方法的作用就是创建一个ImageStream,并且ImageConfiguration作为key从ImageCache中获取ImageCompleter,设置到ImageStream上面。而ImageCompleter是为了设置一些回调和帮助ImageStream设置图片的一个类。

ImageConfiguration是对于ImageCompleter的一些配置。

ImageCache是对于ImageCompleter的缓存。 ImageStreamCompleter putIfAbsent(Object key, ImageStreamCompleter loader(), { ImageErrorListener onError }) 这个方法在resolve方法中是一个关键方法。

ImageStreamCompleter putIfAbsentImageStreamCompleter putIfAbsent(Object key, ImageStreamCompleter loader()) {
    assert(key != null);
    assert(loader != null);
    ImageStreamCompleter result = _cache[key];
    if (result != null) {
      // Remove the provider from the list so that we can put it back in below
      // and thus move it to the end of the list.
      _cache.remove(key);
    } else {
      if (_cache.length == maximumSize && maximumSize > 0)
        _cache.remove(_cache.keys.first);
      result = loader();
    }
    if (maximumSize > 0) {
      assert(_cache.length < maximumSize);
      _cache[key] = result;
    }
    assert(_cache.length <= maximumSize);
    return result;
 }
复制代码

这个方法是在imageCache里面的,提供的是内存缓存api的入口方法,putIfAbsent会先通过key获取之前的ImageStreamCompleter对象,这个key就是NetworkImage对象,当然我们也可以重写obtainKey方法自定义key,如果存在则直接返回,如果不存在则执行load方法加载ImageStreamCompleter对象,并将其放到首位(最少最近使用算法)。 也就是说ImageProvider已经实现了内存缓存:默认缓存图片的最大个数是1000,默认缓存图片的最大空间是10MiB。 第一次加载图片肯定是没有缓存的,所以会调用loader方法,那就是方法外面传进去的load()方法。

FileImage的load方法

@override
ImageStreamCompleter load(FileImage key) {
  return new MultiFrameImageStreamCompleter(
    codec: _loadAsync(key),
    scale: key.scale,
    informationCollector: (StringBuffer information) {
      information.writeln('Path: ${file?.path}');
    }
  );
}
Future<ui.Codec> _loadAsync(FileImage key) async {
  assert(key == this);
  final Uint8List bytes = await file.readAsBytes();
  if (bytes.lengthInBytes == 0)
    return null;
  return await ui.instantiateImageCodec(bytes);
}
复制代码

load方法中使用了一个叫MultiFrameImageStreamCompleter的类:

MultiFrameImageStreamCompleter({
  @required Future<ui.Codec> codec,
  @required double scale,
  InformationCollector informationCollector
}) : assert(codec != null),
     _informationCollector = informationCollector,
     _scale = scale,
     _framesEmitted = 0,
     _timer = null {
  codec.then<void>(_handleCodecReady, onError: (dynamic error, StackTrace stack) {
    FlutterError.reportError(new FlutterErrorDetails(
      exception: error,
      stack: stack,
      library: 'services',
      context: 'resolving an image codec',
      informationCollector: informationCollector,
      silent: true,
    ));
  });
}
复制代码

MultiFrameImageStreamCompleter是ImageStreamCompleter的子类,为了处理多帧的图片加载,Flutter的Image支持加载webp,通过MultiFrameImageStreamCompleter可以对webp文件进行解析,MultiFrameImageStreamCompleter拿到外面传入的codec数据对象,通过handleCodecReady来保存Codec,之后调用decodeNextFrameAndSchedule方法,从Codec获取下一帧图片数据和把数据通知回调到Image,并且开启定时解析下一帧图片数据。

到此为止,基本dart流程就走完了,所以需要做stopAnimation和startAnimation的改造就应该这这个MultiFrameImageStreamCompleter入手了。

最后

整个在Dart层面Image解析webp的流程就这样,



相关文章
|
23天前
|
Dart UED
在 Flutter鸿蒙next版本 中使用 if 语句和三元表达式进行视图逻辑判断
在 Flutter 开发中,构建动态和响应式的用户界面是核心任务。本文详细探讨了如何使用 if 语句、三元表达式等方法进行视图逻辑判断,并提供了示例代码。通过这些方法,可以根据不同条件动态渲染组件,提高用户体验。文章还强调了保持代码可读性和合理使用匿名函数的最佳实践。
67 2
|
23天前
|
存储 UED 开发者
Flutter鸿蒙版本灵活使用方法间的回调处理复杂化的逻辑
在 Flutter 开发中,灵活使用函数回调可以提高代码的可重用性、简化异步编程、增强解耦设计和提升用户体验。本文通过一个简单的示例,展示了如何在 Flutter 中实现函数调用和回调的基本使用。示例代码包括主入口、页面组件和回调函数的定义与调用,详细解析了每个部分的功能和作用。通过这种方式,开发者可以在操作完成后执行特定逻辑,使代码更易读和维护。
73 0
|
3月前
|
缓存
Flutter Image从网络加载图片刷新、强制重新渲染
Flutter Image从网络加载图片刷新、强制重新渲染
126 1
|
存储 Android开发
Flutter控件之图片Image封装
Flutter中偏偏原生的控件,少了很多需要又常用的属性,比如宽高,比如内外边距,又比如点击事件,如果不采取封装,视图的结构会一层嵌套一层,徒增很多的冗余代码,所以,为了简洁代码,还有为了拓展原生组件没有的属性,就不得不进行一次简单的封装,使其在调用的时候,可以很方便的实现某些功能。
142 0
|
Android开发 iOS开发
android和flutter的混合工程启动逻辑
Flutter混合工程是指将Flutter代码集成到现有原生Android或iOS应用程序中的过程。在这种情况下,您需要在原生应用程序中添加一些代码来启动Flutter引擎并加载Flutter代码。以下是Flutter混合工程启动逻辑的详细说明
android和flutter的混合工程启动逻辑
|
Web App开发
Flutter web问题:Failed to load network image
我的解决办法: flutter build web --release --web-renderer html flutter run --web-renderer html flutter run -d chrome --web-renderer html
337 0
Flutter web问题:Failed to load network image
|
Dart 开发者
【Flutter】Image 组件 ( 配置本地 gif 图片资源 | 本地资源加载 placeholder )(二)
【Flutter】Image 组件 ( 配置本地 gif 图片资源 | 本地资源加载 placeholder )(二)
292 0
【Flutter】Image 组件 ( 配置本地 gif 图片资源 | 本地资源加载 placeholder )(二)
【Flutter】Image 组件 ( 配置本地 gif 图片资源 | 本地资源加载 placeholder )(一)
【Flutter】Image 组件 ( 配置本地 gif 图片资源 | 本地资源加载 placeholder )(一)
453 0
【Flutter】Image 组件 ( 配置本地 gif 图片资源 | 本地资源加载 placeholder )(一)
Flutter基础widgets教程-Image篇
Flutter基础widgets教程-Image篇
204 0
下一篇
无影云桌面