探索Flutter_Image显示Webp逻辑
本文主要介紹探索Flutter_Image顯示Webp邏輯
簡介
最近探索了一下新增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;@overrideFuture<FileImage> obtainKey(ImageConfiguration configuration) {return new SynchronousFuture<FileImage>(this);}@overrideImageStreamCompleter 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);}@overridebool operator ==(dynamic other) {if (other.runtimeType != runtimeType)return false;final FileImage typedOther = other;return file?.path == typedOther.file?.path&& scale == typedOther.scale;}@overrideint get hashCode => hashValues(file?.path, scale);@overrideString 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 whatnotinformationCollector: (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 [==]).@protectedFuture<T> obtainKey(ImageConfiguration configuration);/// Converts a key into an [ImageStreamCompleter], and begins fetching the/// image.@protectedImageStreamCompleter load(T key);@overrideString 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的流程就這樣,
總結
以上是生活随笔為你收集整理的探索Flutter_Image显示Webp逻辑的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Flutter创建圆圈图标按钮
- 下一篇: flutter 一行代码取消 返回按钮