赞
踩
在Flutter中有个图片组件:Image
,通常会使用它的Image.network(src)
、Image.file(src)
、Image.asset(src)
来加载图片。
下面是Image
的普通构造方法:
const Image({ super.key, required this.image, this.frameBuilder, this.loadingBuilder, this.errorBuilder, this.semanticLabel, this.excludeFromSemantics = false, this.width, this.height, this.color, this.opacity, this.colorBlendMode, this.fit, this.alignment = Alignment.center, this.repeat = ImageRepeat.noRepeat, this.centerSlice, this.matchTextDirection = false, this.gaplessPlayback = false, this.isAntiAlias = false, this.filterQuality = FilterQuality.low, })
从它的构造方法可以看出Image组件有个必传参数image,它是ImageProvider类型。ImageProvider是个抽象类,定义了图片数据获取和加载的相关接口。它的主要职责有两个:
ImageProvider
抽象类:abstract class ImageProvider<T extends Object> { const ImageProvider(); ImageStream resolve(ImageConfiguration configuration) { ... } @protected ImageStream createStream(ImageConfiguration configuration) { return ImageStream(); } @protected void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) { ... } Future<bool> evict({ ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty }) async { ... } Future<T> obtainKey(ImageConfiguration configuration); }
从上面源码中可以发现图片的加载和解析是由ImageProvider
来完成的,具体说是由它的子类实现的。ImageProvider
派生了很多子类,例如NetworkImage
类和AssetImage
类,NetworkImage
是从网络来加载图片数据,而AssetImage
则是从安装包里的资源文件中加载。
在ImageProvider中提供了一个load()
方法,它是一个用于加载图片数据源的接口,不同的数据源的加载方式不同。
加载网络图片是用Image.network()
,对应的ImageProvider是NetworkImage
类,它实现了load()方法:
@override
ImageStreamCompleter load(FileImage key, DecoderCallback decode) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, null, decode),
scale: key.scale,
debugLabel: key.file.path,
informationCollector: () => <DiagnosticsNode>[
ErrorDescription('Path: ${file.path}'),
],
);
}
MultiFrameImageSteamCompleter有个codec参数,源码中是用调用了_loadAsync()
方法,方法的实现如下:
Future<ui.Codec> _loadAsync( NetworkImage key, StreamController<ImageChunkEvent> chunkEvents, image_provider.DecoderBufferCallback? decode, image_provider.DecoderCallback? decodeDepreacted, ) async { try { final Uri resolved = Uri.base.resolve(key.url); final HttpClientRequest request = await _httpClient.getUrl(resolved); headers?.forEach((String name, String value) { request.headers.add(name, value); }); final HttpClientResponse response = await request.close(); if (response.statusCode != HttpStatus.ok) { await response.drain<List<int>>(<int>[]); throw image_provider.NetworkImageLoadException(statusCode: response.statusCode, uri: resolved); } final Uint8List bytes = await consolidateHttpClientResponseBytes( response, onBytesReceived: (int cumulative, int? total) { chunkEvents.add(ImageChunkEvent( cumulativeBytesLoaded: cumulative, expectedTotalBytes: total, )); }, ); ... if (decode != null) { final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes); return decode(buffer); } else { assert(decodeDepreacted != null); return decodeDepreacted!(bytes); } }
通过源码可以发现_loadAsync()
方法负责下载图片,并调用decode()
方法对下载的图片数据进行解码。
图片的缓存的关键方法是:obtainKey(ImageConfiguration)
该方法主要是为了配合实现图片缓存,ImageProvider从数据源加载完数据后,会在全局的ImageCache中缓存图片数据,而图片数据缓存是一个Map,而Map的key便是调用此方法的返回值,不同的key代表不同的图片数据缓存。
resolve
方法是ImageProvider
暴露给Image的主入口方法,它接收一个ImageConfiguration参数,返回ImageStream。
ImageStream resolve(ImageConfiguration configuration) {
assert(configuration != null);
final ImageStream stream = createStream(configuration);
_createErrorHandlerAndKey(
configuration,
(T key, ImageErrorListener errorHandler) {
resolveStreamForKey(configuration, stream, key, errorHandler);
},
...
);
return stream;
}
ImageConfiguration:包含图片和设备的相关信息。内部会调用_createErrorHandlerAndKey
来加载key并创建错误处理函数。
resolveStreamForKey
方法:
@protected void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) { 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) { stream.setCompleter(completer); } }
在resolve方法中会调用resolveStreamForKey,其中PaintingBinding.instance.imageCache
是ImageCache的一个实例,它是PaintingBinding的一个属性,并且PaintingBinding.instance和imageCache都是单例,所以图片缓存是全局的,统一由PaintingBinding.instance.imageCache
来管理。
ImageCache的定义:
const int _kDefaultSize = 1000;
const int _kDefaultSizeBytes = 100 << 20; // 100 MiB
class ImageCache {
final Map<Object, _PendingImage> _pendingImages = <Object, _PendingImage>{};
final Map<Object, _CachedImage> _cache = <Object, _CachedImage>{};
final Map<Object, _LiveImage> _liveImages = <Object, _LiveImage>{};
int get maximumSize => _maximumSize;
int _maximumSize = _kDefaultSize;
...
}
ImageCache中有三个缓存池:
因为Map中相同key的值会被覆盖,也就是说key是图片缓存的唯一标识,只要key不同,那么图片数据就会分布缓存。那么图片的唯一标识是什么呢?从源码中可以看到ImageProvider.obtainKey()
方法,图片在缓存时所使用的key就是通过这个方法生成的,并且ImageProvider的子类对这个方法进行了重写。
这就意味着不同的ImageProvider对key的定义逻辑是不同的。NetworkImage的obtainKey()方法:
@override
Future<NetworkImage> obtainKey(image_provider.ImageConfiguration configuration) {
return SynchronousFuture<NetworkImage>(this);
}
创建了一个SynchronousFuture,然后将自身返回,所以对比key的时候,看操作符==
就可以了:
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is NetworkImage && other.url == url && other.scale == scale;
}
对于网络图片来说,key实际上是url+缩放比例。所以两张图片的url和scale都相同,那么他们在内存里面就只会缓存一份。
ImageCache类中有默认的缓存大小:
const int _kDefaultSize = 1000;//最多1000张
const int _kDefaultSizeBytes = 100 << 20; //最大 100 MiB
我们也可以通过如下代码去设置自定义的缓存上限:
PaintingBinding.instance.imageCache.maximumSize=500; //最多500张
PaintingBinding.instance.imageCache.maximumSizeBytes = 50 << 20; //最大50M
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。