最近探索了一下新增Flutter的Image widget對webp作一個stopAnimation的拓展的Api,順便了解一下Image整個結構和對一些多幀圖片的處理。 咱們先看看Image的一個類圖結構。web
其中:方法 | 釋義 |
---|---|
Image() | 從ImageProvider中獲取圖片,從本質上看,下面的幾個方法都是他的具體實現。 |
Image.asset(String name) | 從AssetBundler中獲取圖片 |
Image.network(String src) | 顯示網絡圖片 |
Image.file(File file) | 從文件中獲取圖片 |
Image.memory(Uint8List bytes) | 從Uint8List獲取數據顯示圖片 |
從Image的構造體上看,ImageProvider纔是圖片提供方,因此咱們後面會看看ImageProvider到底是要作點什麼的。 其餘的參數是一些圖片的屬性和一些builder。算法
關鍵代碼:express
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是一個抽象類,讓須要定製的子類去作一些實現。api
好比: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這個父類了。markdown
@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的一些配置。async
ImageCache是對於ImageCompleter的緩存。 ImageStreamCompleter putIfAbsent(Object key, ImageStreamCompleter loader(), { ImageErrorListener onError }) 這個方法在resolve中是一個關鍵方法。ide
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()方法。fetch
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的流程就這樣,下篇再介紹下如今Dart的一些Codec的工做流程。