我分析图片加载流程,不是直接从Image这个类开始分析的。我现拿 cached_network_image ^3.2.3这个图片缓存框架进行解析。其实cached_network_image这个框架本质上还是处理Image类的,往下看就知道了,只是cached_network_image这个框架对他进行的一些封装,加了原生没有的文件缓存功能。
图片处理机制流程
- 注册图片流数据监听
- 从网络获取图片数据,并进行图片缓存
- 对图片数据进行解码
- 返回图片解码数据,最终绘制图片
一、注册图片流数据监听
CachedNetworkImage类
Widget build(BuildContext context) {
return OctoImage(
image: _image,
imageBuilder: imageBuilder != null ? _octoImageBuilder : null,
placeholderBuilder: octoPlaceholderBuilder,
progressIndicatorBuilder: octoProgressIndicatorBuilder,
errorBuilder: errorWidget != null ? _octoErrorBuilder : null,
fadeOutDuration: fadeOutDuration,
fadeOutCurve: fadeOutCurve,
fadeInDuration: fadeInDuration,
fadeInCurve: fadeInCurve,
width: width,
height: height,
fit: fit,
alignment: alignment,
repeat: repeat,
matchTextDirection: matchTextDirection,
color: color,
filterQuality: filterQuality,
colorBlendMode: colorBlendMode,
placeholderFadeInDuration: placeholderFadeInDuration,
gaplessPlayback: useOldImageOnUrlChange,
memCacheWidth: memCacheWidth,
memCacheHeight: memCacheHeight,
);
}
我们看到CachedNetworkImage的build的方法返回的是OctoImage,看来CachedNetworkImage就是个马甲,我们继续进入OctoImage类看看。
我们看到了OctoImage类调用了 _imageHandler.build(context),看来OctoImage也是个马甲,最终的实现看来是在ImageHandler类里了
ImageHandler类
Widget build(BuildContext context) {
return Image(
key: ValueKey(image),
image: image,
loadingBuilder: imageLoadingBuilder(),
frameBuilder: imageFrameBuilder(),
errorBuilder: errorWidgetBuilder(),
fit: fit,
width: width,
height: height,
alignment: alignment,
repeat: repeat,
color: color,
colorBlendMode: colorBlendMode,
matchTextDirection: matchTextDirection,
filterQuality: filterQuality,
);
}
从上面的代码来看,ImageHandler也是个马甲,最终还是调用framework类里Image类。
那我们来看看Image类做了什么
在didChangeDependencies方法中,我们看到了一个比较重要的方法_resolveImage();
void _resolveImage() {
final ScrollAwareImageProvider provider = ScrollAwareImageProvider<Object>(
context: _scrollAwareContext,
imageProvider: widget.image,
);
final ImageStream newStream =
provider.resolve(createLocalImageConfiguration(
context,
size: widget.width != null && widget.height != null ? Size(widget.width!, widget.height!) : null,
));
_updateSourceStream(newStream);
}
void _updateSourceStream(ImageStream newStream) {
if (_imageStream?.key == newStream.key) {
return;
}
if (_isListeningToStream) {
_imageStream!.removeListener(_getListener());
}
if (!widget.gaplessPlayback) {
setState(() { _replaceImage(info: null); });
}
setState(() {
_loadingProgress = null;
_frameNumber = null;
_wasSynchronouslyLoaded = false;
});
_imageStream = newStream;
if (_isListeningToStream) {
_imageStream!.addListener(_getListener());
}
}
ImageStreamListener _getListener({bool recreateListener = false}) {
if (_imageStreamListener == null || recreateListener) {
_lastException = null;
_lastStack = null;
_imageStreamListener = ImageStreamListener(
_handleImageFrame,
onChunk: widget.loadingBuilder == null ? null : _handleImageChunk,
onError: widget.errorBuilder != null || kDebugMode
? (Object error, StackTrace? stackTrace) {
setState(() {
_lastException = error;
_lastStack = stackTrace;
});
return true;
}());
}
: null,
);
}
return _imageStreamListener!;
}
从源码可以看出_resolveImage方法主要做的是
将ImageProvider 和 ImageStream产生了关联。然后注册一个图片流监听事件
就是ImageStreamListener这个类。当图片数据获取到之后就会通过监听回调,然后setState将图片渲染出来。
在 Flutter 中,ImageProvider 是一个抽象类,定义了加载图像所需的方法和属性。它的主要作用是为 Image widget 提供图像数据。
ImageProvider 的作用包括以下几个方面:
-
加载图像数据:ImageProvider 提供了 resolve 方法,用于加载图像数据。根据具体的子类实现,它可以从本地文件、网络地址、内存缓存或其他来源获取图像数据。
-
图像缓存管理:ImageProvider 通常与图像缓存一起工作,以提高图像加载性能。它可以使用缓存来避免重复加载相同的图像数据,提高图像的加载速度和效率。
-
图像大小和缩放处理:ImageProvider 可以提供图像的大小信息,以便 Image widget 可以正确布局和显示图像。它还可以根据 Image widget 的要求进行图像的缩放和裁剪,以适应不同的显示需求。
-
错误处理和备用图像:如果图像加载过程中发生错误,ImageProvider 提供了错误处理机制。它可以通知使用 Image widget 的代码,以便显示备用图像或执行其他错误处理逻辑。
-
图像加载状态管理:ImageProvider 负责跟踪图像加载的状态,并通知 Image widget 更新其显示状态。它可以告知 Image widget 图像的加载进度,从而实现加载中、加载完成等不同的状态展示。
通过使用不同的 ImageProvider 子类,可以从不同的来源加载图像,如网络图像、本地文件、内存等。ImageProvider 的具体子类包括 AssetImage、NetworkImage、FileImage 等,每个子类都提供了特定的图像加载方式和参数。
总之,ImageProvider 是 Image widget 的数据提供者,负责加载、缓存和管理图像数据,并与 Image widget 协同工作,确保图像正确地显示在应用程序中。
ImageStreamCompleter 的作用包括以下几个方面:
在 Flutter 中,ImageStreamCompleter 是用于处理图像加载和解码的重要组件。它是 ImageProvider 的一部分,负责管理图像的加载、解码和处理过程。
当您在 Flutter 中使用 Image widget 来显示图像时,Image widget 内部会使用 ImageProvider 来获取图像数据。而 ImageProvider 则使用 ImageStreamCompleter 来管理图像的加载和解码。
ImageStreamCompleter 的主要作用是监听图像加载过程中的各个阶段,并在加载完成后通知 ImageProvider。它负责以下几个任务:
-
发起图像加载:ImageStreamCompleter 会根据提供的图像资源路径或网络地址等信息,发起图像加载请求。
-
图像解码:一旦图像数据被下载完成,ImageStreamCompleter 会负责将图像数据解码为可用的位图数据。
-
图像缩放和裁剪:在解码完成后,ImageStreamCompleter 可以应用缩放、裁剪或其他图像处理操作,以适应 Image widget 的显示需求。
-
错误处理:如果加载或解码过程中发生错误,ImageStreamCompleter 会通知 ImageProvider,以便进行错误处理或显示备用图像。
-
图像加载状态管理:ImageStreamCompleter 会跟踪图像加载的各个阶段,并提供相应的状态,如开始加载、加载中、加载完成等,以便 ImageProvider 更新 Image widget 的显示状态。
总之,ImageStreamCompleter 在 ImageProvider 和 Image widget 之间充当了一个桥梁,负责管理图像的加载、解码和处理过程,并提供相应的状态通知。它确保图像能够正确加载并在 Image widget 中显示出来。
ImageProvider类的resolve方法
final ImageStream stream = createStream(configuration);
_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.
InformationCollector? collector;
return true;
}());
if (stream.completer == null) {
stream.setCompleter(_ErrorImageCompleter());
}
stream.completer!.reportError(
exception: exception,
stack: stack,
context: ErrorDescription('while resolving an image'),
silent: true, // could be a network error or whatnot
informationCollector: collector,
);
},
);
return stream;
}
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,
() {
ImageStreamCompleter result = loadImage(key, PaintingBinding.instance.instantiateImageCodecWithSize);
if (result is _AbstractImageStreamCompleter) {
result = loadBuffer(key, PaintingBinding.instance.instantiateImageCodecFromBuffer);
if (result is _AbstractImageStreamCompleter) {
result = load(key, PaintingBinding.instance.instantiateImageCodec);
}
}
return result;
},
onError: handleError,
);
if (completer != null) {
stream.setCompleter(completer);
}
}
从上面的源码可以看出,新建了一个ImageStream对象和图片的key。然后将生成的ImageStreamCompleter对象存入到PaintingBinding的imageCache
ImageCache类里的三个变量
final Map<Object, _PendingImage> _pendingImages = <Object, _PendingImage>{};
final Map<Object, _CachedImage> _cache = <Object, _CachedImage>{};
final Map<Object, _LiveImage> _liveImages = <Object, _LiveImage>{};
_pendingImages:这是一个 Map 对象,用于存储正在加载中的图像。它将图像的标识符(Object 类型)作为键,将 _PendingImage 对象作为值,表示正在等待加载的图像。
_cache:这是一个 Map 对象,用于存储已加载的图像缓存。它将图像的标识符(Object 类型)作为键,将 _CachedImage 对象作为值,表示已加载的图像。
_liveImages:这也是一个 Map 对象,用于存储活动的图像。它将图像的标识符(Object 类型)作为键,将 _LiveImage 对象作为值,表示当前活动的图像。
这些属性用于在 ImageCache 类中跟踪和管理图像的加载状态和缓存情况。_pendingImages 存储正在加载中的图像,_cache 存储已加载的图像缓存,而 _liveImages 存储当前活动的图像。
ImageStreamCompleter? putIfAbsent(Object key, ImageStreamCompleter Function() loader, { ImageErrorListener? onError }) {
ImageStreamCompleter? result = _pendingImages[key]?.completer;
if (result != null) {
if (!kReleaseMode) {
timelineTask!.finish(arguments: <String, dynamic>{'result': 'pending'});
}
return result;
}
final _CachedImage? image = _cache.remove(key);
if (image != null) {
_trackLiveImage(
key,
image.completer,
image.sizeBytes,
);
_cache[key] = image;
return image.completer;
}
final _LiveImage? liveImage = _liveImages[key];
if (liveImage != null) {
_touch(
key,
_CachedImage(
liveImage.completer,
sizeBytes: liveImage.sizeBytes,
),
timelineTask,
);
return liveImage.completer;
}
try {
result = loader();
_trackLiveImage(key, result, null);
} catch (error, stackTrace) {
if (!kReleaseMode) {
timelineTask!.finish(arguments: <String, dynamic>{
'result': 'error',
'error': error.toString(),
'stackTrace': stackTrace.toString(),
});
}
if (onError != null) {
onError(error, stackTrace);
return null;
} else {
rethrow;
}
}
if (!kReleaseMode) {
timelineTask!.start('listener');
}
bool listenedOnce = false;
final bool trackPendingImage = maximumSize > 0 && maximumSizeBytes > 0;
late _PendingImage pendingImage;
void listener(ImageInfo? info, bool syncCall) {
int? sizeBytes;
if (info != null) {
sizeBytes = info.sizeBytes;
info.dispose();
}
final _CachedImage image = _CachedImage(
result!,
sizeBytes: sizeBytes,
);
_trackLiveImage(key, result, sizeBytes);
if (trackPendingImage) {
_touch(key, image, timelineTask);
} else {
image.dispose();
}
_pendingImages.remove(key);
if (!listenedOnce) {
pendingImage.removeListener();
}
listenedOnce = true;
}
final ImageStreamListener streamListener = ImageStreamListener(listener);
pendingImage = _PendingImage(result, streamListener);
if (trackPendingImage) {
_pendingImages[key] = pendingImage;
}
result.addListener(streamListener);
return result;
}
上面的那个方法就是将ImageStreamCompleter缓存起来,接着我们看下面的代码
ImageStreamCompleter result = loadImage(key, PaintingBinding.instance.instantiateImageCodecWithSize);
if (result is _AbstractImageStreamCompleter) {
result = loadBuffer(key, PaintingBinding.instance.instantiateImageCodecFromBuffer);
if (result is _AbstractImageStreamCompleter) {
result = load(key, PaintingBinding.instance.instantiateImageCodec);
}
这部分代码才是真正加载图片数据的处理
loadImage
load
loadBuffer
这三个方法都是ImageProvider的方法,不同的子类有不同的实现
ImageProvider的子类有
FileImage
MemoryImage
ExactAssetImage
NetworkImage
还可以自己继承ImageProvider,就拿CacheManagerImage这个框架来说,它的CachedNetworkImageProvider就是继承了ImageProvider类。
class CachedNetworkImageProvider
extends ImageProvider<image_provider.CachedNetworkImageProvider> {}
二、从网络获取图片数据,并同时做文件缓存
接下来下面就分析CachedNetworkImageProvider这个类,
我们主要看loadBuffer这个方法,其它方法官方说已经过时了就不看了
@override
ImageStreamCompleter loadBuffer(image_provider.CachedNetworkImageProvider key,
DecoderBufferCallback decode) {
final chunkEvents = StreamController<ImageChunkEvent>();
return MultiImageStreamCompleter(
codec: _loadBufferAsync(key, chunkEvents, decode),
chunkEvents: chunkEvents.stream,
scale: key.scale,
informationCollector: () sync* {
yield DiagnosticsProperty<ImageProvider>(
'Image provider: $this \n Image key: $key',
this,
style: DiagnosticsTreeStyle.errorProperty,
);
},
);
}
MultiImageStreamCompleter这个类主要继承了ImageStreamCompleter
接着看_loadBufferAsync方法
Stream<ui.Codec> _loadBufferAsync(
image_provider.CachedNetworkImageProvider key,
StreamController<ImageChunkEvent> chunkEvents,
DecoderBufferCallback decode,
) {
assert(key == this);
return ImageLoader().loadBufferAsync(
url,
cacheKey,
chunkEvents,
decode,
cacheManager ?? DefaultCacheManager(),
maxHeight,
maxWidth,
headers,
errorListener,
imageRenderMethodForWeb,
() => PaintingBinding.instance.imageCache.evict(key),
);
可以看出又代理给ImageLoader处理了
class ImageLoader implements platform.ImageLoader
ImageLoader类
@override
Stream<ui.Codec> loadBufferAsync(
String url,
String? cacheKey,
StreamController<ImageChunkEvent> chunkEvents,
DecoderBufferCallback decode,
BaseCacheManager cacheManager,
int? maxHeight,
int? maxWidth,
Map<String, String>? headers,
Function()? errorListener,
ImageRenderMethodForWeb imageRenderMethodForWeb,
Function() evictImage) {
return _load(
url,
cacheKey,
chunkEvents,
(bytes) async {
final buffer = await ImmutableBuffer.fromUint8List(bytes);
return decode(buffer);
},
cacheManager,
maxHeight,
maxWidth,
headers,
errorListener,
imageRenderMethodForWeb,
evictImage,
);
}
Stream<ui.Codec> _load(
String url,
String? cacheKey,
StreamController<ImageChunkEvent> chunkEvents,
_FileDecoderCallback decode,
BaseCacheManager cacheManager,
int? maxHeight,
int? maxWidth,
Map<String, String>? headers,
Function()? errorListener,
ImageRenderMethodForWeb imageRenderMethodForWeb,
Function() evictImage,
) async* {
try {
var stream = cacheManager is ImageCacheManager
? cacheManager.getImageFile(url,
maxHeight: maxHeight,
maxWidth: maxWidth,
withProgress: true,
headers: headers,
key: cacheKey)
: cacheManager.getFileStream(url,
withProgress: true, headers: headers, key: cacheKey);
await for (var result in stream) {
if (result is DownloadProgress) {
chunkEvents.add(ImageChunkEvent(
cumulativeBytesLoaded: result.downloaded,
expectedTotalBytes: result.totalSize,
));
}
if (result is FileInfo) {
var file = result.file;
var bytes = await file.readAsBytes();
var decoded = await decode(bytes);
yield decoded;
}
}
} catch (e) {
scheduleMicrotask(() {
evictImage();
});
errorListener?.call();
rethrow;
} finally {
await chunkEvents.close();
}
}
我们看这行代码,从方法名称就可以看出是获取文件流的
cacheManager.getFileStream(url,
maxHeight: maxHeight,
maxWidth: maxWidth,
withProgress: true,
headers: headers,
key: cacheKey)
getFileStream 是BaseCacheManager的空方法,由子类实现,他的子类是CacheManager类
Stream<FileResponse> getFileStream(String url,
{String? key, Map<String, String>? headers, bool withProgress = false}) {
key ??= url;
final streamController = StreamController<FileResponse>();
_pushFileToStream(streamController, url, key, headers, withProgress);
return streamController.stream;
}
Future<void> _pushFileToStream(
StreamController<dynamic> streamController,
String url,
String? key,
Map<String, String>? headers,
bool withProgress,
) async {
key ??= url;
FileInfo? cacheFile;
try {
cacheFile = await getFileFromCache(key);
if (cacheFile != null) {
streamController.add(cacheFile);
withProgress = false;
}
} on Object catch (e) {
cacheLogger.log(
'CacheManager: Failed to load cached file for $url with error:\n$e',
CacheManagerLogLevel.debug);
}
//判断缓存是否过期
if (cacheFile == null || cacheFile.validTill.isBefore(DateTime.now())) {
try {
await for (final response
in _webHelper.downloadFile(url, key: key, authHeaders: headers)) {
if (response is DownloadProgress && withProgress) {
streamController.add(response);
}
if (response is FileInfo) {
streamController.add(response);
}
}
} on Object catch (e) {
cacheLogger.log(
'CacheManager: Failed to download file from $url with error:\n$e',
CacheManagerLogLevel.debug);
if (cacheFile == null && streamController.hasListener) {
streamController.addError(e);
}
}
}
streamController.close();
}
从方法名字可以看出这个函数主要从缓存获取数据
@override
Future<FileInfo?> getFileFromCache(String key,
{bool ignoreMemCache = false}) =>
_store.getFile(key, ignoreMemCache: ignoreMemCache);
Future<FileInfo?> getFile(String key, {bool ignoreMemCache = false}) async {
final cacheObject =
await retrieveCacheData(key, ignoreMemCache: ignoreMemCache);
if (cacheObject == null) {
return null;
}
final file = await fileSystem.createFile(cacheObject.relativePath);
cacheLogger.log(
'CacheManager: Loaded $key from cache', CacheManagerLogLevel.verbose);
return FileInfo(
file,
FileSource.Cache,
cacheObject.validTill,
cacheObject.url,
);
}
首先从缓存里面拿文件数据 cacheFile = await getFileFromCache(key);
如果有的话直接添加到流控制器中 streamController.add(cacheFile);
如果缓存对象是空的话,就调用_webHelper.downloadFile(url, key: key, authHeaders: headers)从网络获取数据,然后再存入文件缓存里。
Stream<FileResponse> _manageResponse(
CacheObject cacheObject, FileServiceResponse response) async* {
final hasNewFile = statusCodesNewFile.contains(response.statusCode);
final keepOldFile = statusCodesFileNotChanged.contains(response.statusCode);
final oldCacheObject = cacheObject;
var newCacheObject = _setDataFromHeaders(cacheObject, response);
if (statusCodesNewFile.contains(response.statusCode)) {
var savedBytes = 0;
await for (final progress in _saveFile(newCacheObject, response)) {
savedBytes = progress;
yield DownloadProgress(
cacheObject.url, response.contentLength, progress);
}
newCacheObject = newCacheObject.copyWith(length: savedBytes);
}
_store.putFile(newCacheObject).then((_) {
if (newCacheObject.relativePath != oldCacheObject.relativePath) {
_removeOldFile(oldCacheObject.relativePath);
}
});
//创建文件
final file = await _store.fileSystem.createFile(
newCacheObject.relativePath,
);
yield FileInfo(
file,
FileSource.Online,
newCacheObject.validTill,
newCacheObject.url,
);
}
//缓存到本地文件
_store.putFile(newCacheObject).then((_) {
if (newCacheObject.relativePath != oldCacheObject.relativePath) {
_removeOldFile(oldCacheObject.relativePath);
}
});
这段代码主要职责是从网络获取的文件对象,先移除旧文件,然后缓存到本地文件上。
三、对图片数据进行解码
获取到图片对象后,需要对图片进行解码,接下来就是图片的解码操作
_load方法里的一段代码
if (result is FileInfo) {
var file = result.file;
var bytes = await file.readAsBytes();
var decoded = await decode(bytes);
yield decoded;
}
这就是解码操作了,decode是一个函数对象, 就是DecoderBufferCallback
typedef DecoderBufferCallback = Future<ui.Codec> Function(ui.ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling});
(bytes) async {
final buffer = await ImmutableBuffer.fromUint8List(bytes);
return decode(buffer);
},
这段代码主要是先将Uint8List 转换成ImmutableBuffer类型。
我们看一下CachedNetworkImageProvider类里的loadBuffer方法,这个类是继承ImageProvider。
然后实现了ImageProvider的loadBuffer方法
@override
ImageStreamCompleter loadBuffer(image_provider.CachedNetworkImageProvider key,
DecoderBufferCallback decode) {
final chunkEvents = StreamController<ImageChunkEvent>();
return MultiImageStreamCompleter(
codec: _loadBufferAsync(key, chunkEvents, decode),
chunkEvents: chunkEvents.stream,
scale: key.scale,
informationCollector: () sync* {
yield DiagnosticsProperty<ImageProvider>(
'Image provider: $this \n Image key: $key',
this,
style: DiagnosticsTreeStyle.errorProperty,
);
},
);
}
MultiFrameImageStreamCompleter({
required Future<ui.Codec> codec,
required double scale,
String? debugLabel,
Stream<ImageChunkEvent>? chunkEvents,
InformationCollector? informationCollector,
}) : _informationCollector = informationCollector,
_scale = scale {
this.debugLabel = debugLabel;
codec.then<void>(_handleCodecReady, onError: (Object error, StackTrace stack) {
reportError(
context: ErrorDescription('resolving an image codec'),
exception: error,
stack: stack,
informationCollector: informationCollector,
silent: true,
);
});
if (chunkEvents != null) {
_chunkSubscription = chunkEvents.listen(reportImageChunkEvent,
onError: (Object error, StackTrace stack) {
reportError(
context: ErrorDescription('loading an image'),
exception: error,
stack: stack,
informationCollector: informationCollector,
silent: true,
);
},
);
}
}
我们看一下这段代码
codec.listen((event) {
if (_timer != null) {
_nextImageCodec = event;
} else {
_handleCodecReady(event);
}
}, onError: (dynamic error, StackTrace stack) {
reportError(
context: ErrorDescription('resolving an image codec'),
exception: error,
stack: stack,
informationCollector: informationCollector,
silent: true,
);
});
void _handleCodecReady(ui.Codec codec) {
_codec = codec;
if (hasListeners) {
_decodeNextFrameAndSchedule();
}
}
Future<void> _decodeNextFrameAndSchedule() async {
try {
_nextFrame = await _codec!.getNextFrame();
} catch (exception, stack) {
reportError(
context: ErrorDescription('resolving an image frame'),
exception: exception,
stack: stack,
informationCollector: _informationCollector,
silent: true,
);
return;
}
if (_codec!.frameCount == 1) {
if (!hasListeners) {
return;
}
_emitFrame(ImageInfo(image: _nextFrame!.image, scale: _scale));
return;
}
_scheduleAppFrame();
}
void _emitFrame(ImageInfo imageInfo) {
setImage(imageInfo);
_framesEmitted += 1;
}
void setImage(ImageInfo image) {
_checkDisposed();
_currentImage?.dispose();
_currentImage = image;
if (_listeners.isEmpty) {
return;
}
// Make a copy to allow for concurrent modification.
final List<ImageStreamListener> localListeners =
List<ImageStreamListener>.of(_listeners);
for (final ImageStreamListener listener in localListeners) {
try {
listener.onImage(image.clone(), false);
} catch (exception, stack) {
reportError(
context: ErrorDescription('by an image listener'),
exception: exception,
stack: stack,
);
}
}
}
从上面可以清晰的看到,最终调用了ImageStreamListener的 onImage方法,将图片的解码数据封装成了一个ImageInfo对象回传到
ImageState类注册的ImageStreamListener监听,然后通过setState将图片渲染出来。
最后loadBuffer方法调用会返回到resolveStreamForKey方法。最终ImageStreamCompleter会被缓存到全局的ImageCache。
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,
() {
ImageStreamCompleter result = loadImage(key, PaintingBinding.instance.instantiateImageCodecWithSize);
if (result is _AbstractImageStreamCompleter) {
result = loadBuffer(key, PaintingBinding.instance.instantiateImageCodecFromBuffer);
if (result is _AbstractImageStreamCompleter) {
result = load(key, PaintingBinding.instance.instantiateImageCodec);
}
}
return result;
},
onError: handleError,
);
if (completer != null) {
stream.setCompleter(completer);
}
}
这里的PaintingBinding.instance.instantiateImageCodecFromBuffer 就是上面
DecoderBufferCallback这个函数对象调用,他们是等价的。最终的图片解码操作会进入PaintingBinding类的instantiateImageCodecFromBuffer方法里,
然后将原先的Uint8List文件转化成ui.Codec可渲染的图片数据。
四、 绘制图片
我们知道刚开始的时候已经注册了一个图片流的监听事件,当最终的图片数据获取到之后,就会回调监听,就是下面的_handleImageFrame方法
ImageStreamListener _getListener({bool recreateListener = false}) {
if (_imageStreamListener == null || recreateListener) {
_lastException = null;
_lastStack = null;
_imageStreamListener = ImageStreamListener(
_handleImageFrame,
onChunk: widget.loadingBuilder == null ? null : _handleImageChunk,
onError: widget.errorBuilder != null || kDebugMode
? (Object error, StackTrace? stackTrace) {
setState(() {
_lastException = error;
_lastStack = stackTrace;
});
assert(() {
if (widget.errorBuilder == null) {
// ignore: only_throw_errors, since we're just proxying the error.
throw error; // Ensures the error message is printed to the console.
}
return true;
}());
}
: null,
);
}
return _imageStreamListener!;
}
void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) {
setState(() {
_replaceImage(info: imageInfo);
_loadingProgress = null;
_lastException = null;
_lastStack = null;
_frameNumber = _frameNumber == null ? 0 : _frameNumber! + 1;
_wasSynchronouslyLoaded = _wasSynchronouslyLoaded | synchronousCall;
});
}
void _replaceImage({required ImageInfo? info}) {
final ImageInfo? oldImageInfo = _imageInfo;
SchedulerBinding.instance.addPostFrameCallback((_) => oldImageInfo?.dispose());
_imageInfo = info;
}
我们知道ImageState类里的build方法返回的对象是RawImage类,然后传入解析完的图片数据,进行渲染。
Widget result = RawImage(
image: _imageInfo?.image,
debugImageLabel: _imageInfo?.debugLabel,
width: widget.width,
height: widget.height,
scale: _imageInfo?.scale ?? 1.0,
color: widget.color,
opacity: widget.opacity,
colorBlendMode: widget.colorBlendMode,
fit: widget.fit,
alignment: widget.alignment,
repeat: widget.repeat,
centerSlice: widget.centerSlice,
matchTextDirection: widget.matchTextDirection,
invertColors: _invertColors,
isAntiAlias: widget.isAntiAlias,
filterQuality: widget.filterQuality,
);