一、是谁加载了图片
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
。
只有一个图片的缓存
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;
}
提示:在
Flutter
框架中内置的图片缓存实现都是基于内存的,并没有进行本地文件持久化存储,应用重启后需要重新通过网络下载。
六、设置缓存大小
通过ImageCache提供的方法来设置:
PaintingBinding.instance!.imageCache. maximumSize = 2000; // 最多2000张
PaintingBinding.instance!.imageCache. maximumSizeBytes = 200 << 20; // 最大200M
网友评论