探索Flutter_Image显示Webp逻辑
本文主要介紹探索Flutter_Image顯示W(wǎng)ebp邏輯
簡介
最近探索了一下新增Flutter的Image widget對(duì)webp做一個(gè)stopAnimation的拓展的Api,順便了解一下Image整個(gè)結(jié)構(gòu)和對(duì)一些多幀圖片的處理。 我們先看看Image的一個(gè)類圖結(jié)構(gòu)。
其中:
- ImageProvider 提供加載圖片的入口,不同的圖片資源加載方式不一樣,只要重寫其load方法即可。同樣,緩存圖片的key值也有其生成。
- FileImage 負(fù)責(zé)讀取文件圖片的數(shù)據(jù),讀取到的文件數(shù)據(jù)轉(zhuǎn)化成ui.Codec對(duì)象交給ImageStreamCompleter去處理解析。
- ImageStreamCompleter就是逐幀解析圖片的類,生成之后會(huì)加入ImageCache,下載可以從緩存中得到。
- ImageStream是處理Image Resource的,ImageState通過ImageStream與ImageStreamCompleter建立聯(lián)系。ImageStream里也存儲(chǔ)著圖片加載完畢的監(jiān)聽回調(diào)。
- MultiFrameImageStreamCompleter就是多幀圖片解析器。 Flutter imgae支持的圖片格式為:JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP, and WBMP。Flutter Image是顯示圖片的一個(gè)Widget。 Flutter Image的幾個(gè)構(gòu)造方法:
| Image() | 從ImageProvider中獲取圖片,從本質(zhì)上看,下面的幾個(gè)方法都是他的具體實(shí)現(xiàn)。 |
| Image.asset(String name) | 從AssetBundler中獲取圖片 |
| Image.network(String src) | 顯示網(wǎng)絡(luò)圖片 |
| Image.file(File file) | 從文件中獲取圖片 |
| Image.memory(Uint8List bytes) | 從Uint8List獲取數(shù)據(jù)顯示圖片 |
Image
從Image的構(gòu)造體上看,ImageProvider才是圖片提供方,所以我們后面會(huì)看看ImageProvider究竟是要做點(diǎn)什么的。 其他的參數(shù)是一些圖片的屬性和一些builder。
ImageState
關(guān)鍵代碼:
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
其實(shí)ImageProvider是一個(gè)抽象類,讓需要定制的子類去做一些實(shí)現(xiàn)。
比如:FileImage、MemoryImage、ExactAssetImage等等。其中對(duì)FileImage的代碼進(jìn)行了一些分析。
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的方法。但是在什么地方會(huì)調(diào)用這兩個(gè)重寫的方法呢?那肯定是ImageProvider這個(gè)父類了。
@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()'; }該方法的作用就是創(chuàng)建一個(gè)ImageStream,并且ImageConfiguration作為key從ImageCache中獲取ImageCompleter,設(shè)置到ImageStream上面。而ImageCompleter是為了設(shè)置一些回調(diào)和幫助ImageStream設(shè)置圖片的一個(gè)類。
ImageConfiguration是對(duì)于ImageCompleter的一些配置。
ImageCache是對(duì)于ImageCompleter的緩存。 ImageStreamCompleter putIfAbsent(Object key, ImageStreamCompleter loader(), { ImageErrorListener onError }) 這個(gè)方法在resolve方法中是一個(gè)關(guān)鍵方法。
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;}這個(gè)方法是在imageCache里面的,提供的是內(nèi)存緩存api的入口方法,putIfAbsent會(huì)先通過key獲取之前的ImageStreamCompleter對(duì)象,這個(gè)key就是NetworkImage對(duì)象,當(dāng)然我們也可以重寫obtainKey方法自定義key,如果存在則直接返回,如果不存在則執(zhí)行l(wèi)oad方法加載ImageStreamCompleter對(duì)象,并將其放到首位(最少最近使用算法)。 也就是說ImageProvider已經(jīng)實(shí)現(xiàn)了內(nèi)存緩存:默認(rèn)緩存圖片的最大個(gè)數(shù)是1000,默認(rèn)緩存圖片的最大空間是10MiB。 第一次加載圖片肯定是沒有緩存的,所以會(huì)調(diào)用loader方法,那就是方法外面?zhèn)鬟M(jìn)去的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方法中使用了一個(gè)叫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可以對(duì)webp文件進(jìn)行解析,MultiFrameImageStreamCompleter拿到外面?zhèn)魅氲腸odec數(shù)據(jù)對(duì)象,通過handleCodecReady來保存Codec,之后調(diào)用decodeNextFrameAndSchedule方法,從Codec獲取下一幀圖片數(shù)據(jù)和把數(shù)據(jù)通知回調(diào)到Image,并且開啟定時(shí)解析下一幀圖片數(shù)據(jù)。
到此為止,基本dart流程就走完了,所以需要做stopAnimation和startAnimation的改造就應(yīng)該這這個(gè)MultiFrameImageStreamCompleter入手了。
最后
整個(gè)在Dart層面Image解析webp的流程就這樣,
總結(jié)
以上是生活随笔為你收集整理的探索Flutter_Image显示Webp逻辑的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Flutter创建圆圈图标按钮
- 下一篇: flutter 一行代码取消 返回按钮