一、是谁加载了图片
Image构造方法:
const Image({
Key? key,
required this.image,
this.frameBuilder,
this.loadingBuilder,
this.errorBuilder,
this.semanticLabel,
this.excludeFromSemantics = false,
this.width,
this.height,
this.color,
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,
}) : assert(image != null),
assert(alignment != null),
assert(repeat != null),
assert(filterQuality != null),
assert(matchTextDirection != null),
assert(isAntiAlias != null),
super(key: key);
其中,参数image类型为抽象类ImageProvider,定义了图片数据获取和加载的相关接口。
/// The image to display.
final ImageProvider image;
ImageProvider作用:
- 提供图片数据源
- 缓存图片
ImageProvider定义:
abstract class ImageProvider<T extends Object> {
const ImageProvider();
ImageStream resolve(ImageConfiguration configuration) {
...
}
/// Evicts an entry from the image cache.
Future<bool> evict({ ImageCache? cache, ImageConfiguration
...
}
@protected
ImageStreamCompleter load(T key, DecoderCallback decode);
@override
String toString() => '${objectRuntimeType(this, 'ImageConfiguration')}()';
}

ImageProvider派生类
根据不同的数据来源,派生出不同的ImageProvider:
// 本地项目包中的图片
abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKey> {}
class AssetImage extends AssetBundleImageProvider {}
// 对图片进行宽高处理的图片
class ResizeImage extends ImageProvider<_SizeAwareCacheKey> {}
// 网络图片
abstract class NetworkImage extends ImageProvider<NetworkImage> {}
class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkImage> implements image_provider.NetworkImage {}
// 本地图片文件
class FileImage extends ImageProvider<FileImage> {}
// 内存图片
class MemoryImage extends ImageProvider<MemoryImage> {}
二、图片如何加载
抽象类ImageProvider提供了一个用于加载数据源的抽象方法@protected ImageStreamCompleter load(T key, DecoderCallback decode);接口,不同的数据源定义各自的实现。
子类NetworkImage实现如下:
@override
ImageStreamCompleter load(image_provider.NetworkImage key, image_provider.DecoderCallback decode) {
// Ownership of this controller is handed off to [_loadAsync]; it is that
// method's responsibility to close the controller's stream when the image
// has been loaded or an error is thrown.
final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key as NetworkImage, chunkEvents, decode),
chunkEvents: chunkEvents.stream,
scale: key.scale,
debugLabel: key.url,
informationCollector: () {
return <DiagnosticsNode>[
DiagnosticsProperty<image_provider.ImageProvider>('Image provider', this),
DiagnosticsProperty<image_provider.NetworkImage>('Image key', key),
];
},
);
}
load方法返回类型为抽象类ImageStreamCompleter,其中定义了一些管理图片加载过程的接口,比如addListener、removeListener、addOnLastListenerRemovedCallback等,MultiFrameImageStreamCompleter为其子类。
MultiFrameImageStreamCompleter第一个参数codec类型为Future<ui.Codec>,用来对突破进行解码,当codec准备好的时候,就会立即对图片第一帧进行解码操作。
/// Immediately starts decoding the first image frame when the codec is ready.
///
/// The `codec` parameter is a future for an initialized [ui.Codec] that will
/// be used to decode the image.
codec为_loadAsync方法返回值,
Future<ui.Codec> _loadAsync(
NetworkImage key,
StreamController<ImageChunkEvent> chunkEvents,
image_provider.DecoderCallback decode,
) async {
try {
assert(key == this);
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) {
// The network may be only temporarily unavailable, or the file will be
// added on the server later. Avoid having future calls to resolve
// fail to check the network again.
await response.drain<List<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 (bytes.lengthInBytes == 0)
throw Exception('NetworkImage is an empty file: $resolved');
return decode(bytes);
} catch (e) {
// Depending on where the exception was thrown, the image cache may not
// have had a chance to track the key in the cache at all.
// Schedule a microtask to give the cache a chance to add the key.
scheduleMicrotask(() {
PaintingBinding.instance!.imageCache!.evict(key);
});
rethrow;
} finally {
chunkEvents.close();
}
}
_loadAsync方法实现:
- 通过图片
url网络请求获取图片的返回值Response,将返回值转为Uint8List。 - 调用
decode解码方法进行返回,传入Uint8List,返回Future<ui.Codec>。
三、图片如何进行解码
decode方法的类型:
其中解码传入的回调方法image_provider.DecoderCallback decode,
typedef DecoderCallback = Future<ui.Codec> Function(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool allowUpscaling});
传入Uint8List,返回Future<ui.Codec>。
而对decode回调方法的具体定义,在ImageProvider的resolveStreamForKey方法中做了定义,resolveStreamForKey方法在ImageProvider的resolve方法中有调用,resolve方法则为ImageProvider类层级结构的公共入口点。
resolveStreamForKey和resolve实现如下:
@nonVirtual
ImageStream resolve(ImageConfiguration configuration) {
assert(configuration != null);
final ImageStream stream = createStream(configuration);
// Load the key (potentially asynchronously), set up an error handling zone,
// and call resolveStreamForKey.
_createErrorHandlerAndKey(
configuration,
(T key, ImageErrorListener errorHandler) {
resolveStreamForKey(configuration, stream, key, errorHandler);
},
(T? key, Object exception, StackTrace? stack) async {
await null; // wait an event turn in case a listener has been added to the image stream.
final _ErrorImageCompleter imageCompleter = _ErrorImageCompleter();
stream.setCompleter(imageCompleter);
InformationCollector? collector;
assert(() {
collector = () sync* {
yield DiagnosticsProperty<ImageProvider>('Image provider', this);
yield DiagnosticsProperty<ImageConfiguration>('Image configuration', configuration);
yield DiagnosticsProperty<T>('Image key', key, defaultValue: null);
};
return true;
}());
imageCompleter.setError(
exception: exception,
stack: stack,
context: ErrorDescription('while resolving an image'),
silent: true, // could be a network error or whatnot
informationCollector: collector,
);
},
);
return stream;
}
@protected
void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) {
// This is an unusual edge case where someone has told us that they found
// the image we want before getting to this method. We should avoid calling
// load again, but still update the image cache with LRU information.
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,
() => load(key, PaintingBinding.instance!.instantiateImageCodec),
onError: handleError,
);
if (completer != null) {
stream.setCompleter(completer);
}
}
decode方法,即PaintingBinding.instance!.instantiateImageCodec,即为具体图片解码的方法实现。
Future<ui.Codec> instantiateImageCodec(Uint8List bytes, {
int? cacheWidth,
int? cacheHeight,
bool allowUpscaling = false,
}) {
assert(cacheWidth == null || cacheWidth > 0);
assert(cacheHeight == null || cacheHeight > 0);
assert(allowUpscaling != null);
return ui.instantiateImageCodec(
bytes,
targetWidth: cacheWidth,
targetHeight: cacheHeight,
allowUpscaling: allowUpscaling,
);
}
ui.instantiateImageCodec实现:
Future<Codec> instantiateImageCodec(
Uint8List list, {
int? targetWidth,
int? targetHeight,
bool allowUpscaling = true,
}) async {
final ImmutableBuffer buffer = await ImmutableBuffer.fromUint8List(list);
final ImageDescriptor descriptor = await ImageDescriptor.encoded(buffer);
if (!allowUpscaling) {
if (targetWidth != null && targetWidth > descriptor.width) {
targetWidth = descriptor.width;
}
if (targetHeight != null && targetHeight > descriptor.height) {
targetHeight = descriptor.height;
}
}
return descriptor.instantiateCodec(
targetWidth: targetWidth,
targetHeight: targetHeight,
);
}
descriptor.instantiateCodec方法实现:
Future<Codec> instantiateCodec({int? targetWidth, int? targetHeight}) async {
if (targetWidth != null && targetWidth <= 0) {
targetWidth = null;
}
if (targetHeight != null && targetHeight <= 0) {
targetHeight = null;
}
if (targetWidth == null && targetHeight == null) {
targetWidth = width;
targetHeight = height;
} else if (targetWidth == null && targetHeight != null) {
targetWidth = (targetHeight * (width / height)).round();
targetHeight = targetHeight;
} else if (targetHeight == null && targetWidth != null) {
targetWidth = targetWidth;
targetHeight = targetWidth ~/ (width / height);
}
assert(targetWidth != null);
assert(targetHeight != null);
final Codec codec = Codec._();
_instantiateCodec(codec, targetWidth!, targetHeight!);
return codec;
}
_instantiateCodec方法的实现,最终到了native的实现:
void _instantiateCodec(Codec outCodec, int targetWidth, int targetHeight) native 'ImageDescriptor_instantiateCodec';
其中返回值类型Codec里定义了一些属性:
class Codec extends NativeFieldWrapperClass2 {
@pragma('vm:entry-point')
Codec._();
// 缓存帧的数量
int? _cachedFrameCount;
// 获取图片帧数
int get frameCount => _cachedFrameCount ??= _frameCount;
int get _frameCount native 'Codec_frameCount';
int? _cachedRepetitionCount;
// 动画重复的次数
int get repetitionCount => _cachedRepetitionCount ??= _repetitionCount;
int get _repetitionCount native 'Codec_repetitionCount';
// 获取下一帧
Future<FrameInfo> getNextFrame() async {
...
}
/// Returns an error message on failure, null on success.
String? _getNextFrame(void Function(_Image?, int) callback) native 'Codec_getNextFrame';
void dispose() native 'Codec_dispose';
}
四、图片如何缓存
obtainKey方法:
ImageProvider定义了一个抽象方法Future<T> obtainKey(ImageConfiguration configuration);,供子类来实现,其中NetworkImage的实现为:
@override
Future<NetworkImage> obtainKey(image_provider.ImageConfiguration configuration) {
return SynchronousFuture<NetworkImage>(this);
}
obtainKey作用:
配合实现图片缓存,ImageProvider从数据源加载完数据后,会在ImageCache中缓存图片数据,图片数据缓存时一个Map,其中Map中的key便是obtainKey。
resolve作为ImageProvider提供给Image的主入口方法,参数为ImageConfiguration,
const ImageConfiguration({
this.bundle,
this.devicePixelRatio, // 设备像素比
this.locale,
this.textDirection,
this.size, // 图片大小
this.platform, // 设备平台
});
resolve其中调用了_createErrorHandlerAndKey方法,设置了成功回调和失败回调:
@nonVirtual
ImageStream resolve(ImageConfiguration configuration) {
assert(configuration != null);
final ImageStream stream = createStream(configuration);
// Load the key (potentially asynchronously), set up an error handling zone,
// and call resolveStreamForKey.
_createErrorHandlerAndKey(
configuration,
(T key, ImageErrorListener errorHandler) {
// 设置成功回调
resolveStreamForKey(configuration, stream, key, errorHandler);
},
(T? key, Object exception, StackTrace? stack) async {
// 设置失败回调
await null; // wait an event turn in case a listener has been added to the image stream.
final _ErrorImageCompleter imageCompleter = _ErrorImageCompleter();
stream.setCompleter(imageCompleter);
InformationCollector? collector;
assert(() {
collector = () sync* {
yield DiagnosticsProperty<ImageProvider>('Image provider', this);
yield DiagnosticsProperty<ImageConfiguration>('Image configuration', configuration);
yield DiagnosticsProperty<T>('Image key', key, defaultValue: null);
};
return true;
}());
imageCompleter.setError(
exception: exception,
stack: stack,
context: ErrorDescription('while resolving an image'),
silent: true, // could be a network error or whatnot
informationCollector: collector,
);
},
);
return stream;
}
其中_createErrorHandlerAndKey方法的实现,便调用了obtainKey来设置key。
void _createErrorHandlerAndKey(
ImageConfiguration configuration,
_KeyAndErrorHandlerCallback<T> successCallback,
_AsyncKeyErrorHandler<T?> errorCallback,
) {
T? obtainedKey;
bool didError = false;
Future<void> handleError(Object exception, StackTrace? stack) async {
if (didError) {
return;
}
if (!didError) {
errorCallback(obtainedKey, exception, stack);
}
didError = true;
}
// If an error is added to a synchronous completer before a listener has been
// added, it can throw an error both into the zone and up the stack. Thus, it
// looks like the error has been caught, but it is in fact also bubbling to the
// zone. Since we cannot prevent all usage of Completer.sync here, or rather
// that changing them would be too breaking, we instead hook into the same
// zone mechanism to intercept the uncaught error and deliver it to the
// image stream's error handler. Note that these errors may be duplicated,
// hence the need for the `didError` flag.
final Zone dangerZone = Zone.current.fork(
specification: ZoneSpecification(
handleUncaughtError: (Zone zone, ZoneDelegate delegate, Zone parent, Object error, StackTrace stackTrace) {
handleError(error, stackTrace);
},
),
);
dangerZone.runGuarded(() {
Future<T> key;
try {
key = obtainKey(configuration);
} catch (error, stackTrace) {
handleError(error, stackTrace);
return;
}
key.then<void>((T key) {
obtainedKey = key;
try {
successCallback(key, handleError);
} catch (error, stackTrace) {
handleError(error, stackTrace);
}
}).catchError(handleError);
});
}
在成功回调里,调用了方法resolveStreamForKey,里面有具体的缓存实现PaintingBinding.instance!.imageCache!.putIfAbsent:
@protected
void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) {
// This is an unusual edge case where someone has told us that they found
// the image we want before getting to this method. We should avoid calling
// load again, but still update the image cache with LRU information.
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,
() => load(key, PaintingBinding.instance!.instantiateImageCodec),
onError: handleError,
);
if (completer != null) {
stream.setCompleter(completer);
}
}
PaintingBinding.instance!.imageCache是ImageCache的一个实例,是PaintingBinding的一个属性,是一个单例,图片缓存是全局的。
如上述判断:
- 先判断图片缓存数据有没有缓存,如果有,直接返回
ImageStream - 如果没有缓存,则调用
load方法总数据源加载图片数据,然后返回ImageStream。
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;
// 设置缓存最大数量
set maximumSize(int value) {
...
}
// 当前缓存数量
int get currentSize => _cache.length;
// 获取最大缓存容量
int get maximumSizeBytes => _maximumSizeBytes;
int _maximumSizeBytes = _kDefaultSizeBytes;
// 设置缓存最大容量
set maximumSizeBytes(int value) {
...
}
// 当前缓存容量
int get currentSizeBytes => _currentSizeBytes;
int _currentSizeBytes = 0;
// 清除缓存
void clear() {
...
}
bool evict(Object key, { bool includeLive = true }) {
...
}
ImageStreamCompleter? putIfAbsent(Object key, ImageStreamCompleter Function() loader, { ImageErrorListener? onError }) {
...
}
ImageCacheStatus statusForKey(Object key) {
...
}
bool containsKey(Object key) {
...
}
// 正在使用中的图片数量
int get liveImageCount => _liveImages.length;
// 正在加载中的图片数量
int get pendingImageCount => _pendingImages.length;
// 清空正在使用中的图片
void clearLiveImages() {
...
}
void _checkCacheSize(TimelineTask? timelineTask) {
...
}
}
ImageCache缓存池:
-
_pendingImages:正在加载的图片缓存池,图片解码完成后会自动移除_pendingImages中对应的缓存Entry。 -
_cache:已经缓存的所有图片的缓存池,如果缓存的图片数量和缓存容量没有超出设置的最大数量和容量,则_cache会一直保留缓存Entry,超出则按照图片url进行释放。 -
_liveImages:跟踪正在使用的图片的缓存池,如果Image widget被移除或更换图片,则会从_liveImages中移除缓存Entry。
五、图片缓存key的生成
在NetworkImage中,对ImageProvider的抽象方法obtainKey进行了实现,将自己创建了一个同步Future进行返回:
@override
Future<NetworkImage> obtainKey(image_provider.ImageConfiguration configuration) {
return SynchronousFuture<NetworkImage>(this);
}
同时,自身又重写了ImageProvider定义的==比较操作符,通过图片url和图片的缩放比例scale进行比较:
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is NetworkImage && other.url == url && other.scale == scale;
}
六、设置缓存大小
通过ImageCache提供的方法来设置:
PaintingBinding.instance!.imageCache. maximumSize = 2000; // 最多2000张
PaintingBinding.instance!.imageCache. maximumSizeBytes = 200 << 20; // 最大200M










