Flutter 扒一扒图片缓存框架cached_network_image

ops/2024/9/23 11:15:20/

我分析图片加载流程,不是直接从Image这个类开始分析的。我现拿 cached_network_image ^3.2.3这个图片缓存框架进行解析。其实cached_network_image这个框架本质上还是处理Image类的,往下看就知道了,只是cached_network_image这个框架对他进行的一些封装,加了原生没有的文件缓存功能。

图片处理机制流程

  1. 注册图片流数据监听
  2. 从网络获取图片数据,并进行图片缓存
  3. 对图片数据进行解码
  4. 返回图片解码数据,最终绘制图片

一、注册图片流数据监听


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 的作用包括以下几个方面:

  1. 加载图像数据:ImageProvider 提供了 resolve 方法,用于加载图像数据。根据具体的子类实现,它可以从本地文件、网络地址、内存缓存或其他来源获取图像数据。

  2. 图像缓存管理:ImageProvider 通常与图像缓存一起工作,以提高图像加载性能。它可以使用缓存来避免重复加载相同的图像数据,提高图像的加载速度和效率。

  3. 图像大小和缩放处理:ImageProvider 可以提供图像的大小信息,以便 Image widget 可以正确布局和显示图像。它还可以根据 Image widget 的要求进行图像的缩放和裁剪,以适应不同的显示需求。

  4. 错误处理和备用图像:如果图像加载过程中发生错误,ImageProvider 提供了错误处理机制。它可以通知使用 Image widget 的代码,以便显示备用图像或执行其他错误处理逻辑。

  5. 图像加载状态管理: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。它负责以下几个任务:

  1. 发起图像加载:ImageStreamCompleter 会根据提供的图像资源路径或网络地址等信息,发起图像加载请求。

  2. 图像解码:一旦图像数据被下载完成,ImageStreamCompleter 会负责将图像数据解码为可用的位图数据。

  3. 图像缩放和裁剪:在解码完成后,ImageStreamCompleter 可以应用缩放、裁剪或其他图像处理操作,以适应 Image widget 的显示需求。

  4. 错误处理:如果加载或解码过程中发生错误,ImageStreamCompleter 会通知 ImageProvider,以便进行错误处理或显示备用图像。

  5. 图像加载状态管理: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 whatnotinformationCollector: 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这个方法,其它方法官方说已经过时了就不看了

@overrideImageStreamCompleter 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.ImageLoaderImageLoader类@overrideStream<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 responsein _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();}

从方法名字可以看出这个函数主要从缓存获取数据

  @overrideFuture<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是一个函数对象, 就是DecoderBufferCallbacktypedef 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方法

  @overrideImageStreamCompleter 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,);

http://www.ppmy.cn/ops/21896.html

相关文章

Java23种设计模式-创建型模式之单例模式

单例模式&#xff08;Singleton Pattern&#xff09;&#xff1a;通过单例模式的方法创建的类在当前进程中只有一个实例&#xff08;根据需要&#xff0c;也有可能一个线程中属于单例&#xff0c;如&#xff1a;仅线程上下文内使用同一个实例&#xff09;&#xff0c;该类负责创…

Matlab新手快速上手2(粒子群算法)

本文根据一个较为简单的粒子群算法框架详细分析粒子群算法的实现过程&#xff0c;对matlab新手友好&#xff0c;源码在文末给出。 粒子群算法简介 粒子群算法&#xff08;Particle Swarm Optimization&#xff0c;PSO&#xff09;是一种群体智能优化算法&#xff0c;灵感来源于…

Docker镜像和容器操作

目录 一.Docker镜像创建与操作 1. 搜索镜像 2. 获取镜像 3. 镜像加速下载 4. 查看镜像信息 5. 查看下载的镜像文件信息 ​编辑6. 查看下载到本地的所有镜像 7. 根据镜像的唯一标识ID号&#xff0c;获取镜像详细信息 8. 为本地的镜像添加新的标签 9. 删除镜像 10. 存入…

数据库之数据库恢复技术思维导图+大纲笔记

大纲笔记&#xff1a; 事务的基本概念 事务 定义 用户定义的一个数据库操作系列&#xff0c;这些操作要么全做&#xff0c;要么全不做&#xff0c;是一个不可分割的基本单位 语句 BEGIN TRANSACTION 开始 COMMIT 提交&#xff0c;提交事务的所有操作 ROLLBACK 回滚&#xff0c…

CUDA的基础知识

文章目录 数据精度CUDA概念线程&线程块&线程网络&计算核心GPU规格参数内存 GPU并行方式数据并行流水并行张量并行混合专家系统 数据精度 FP32 是单精度浮点数&#xff0c;用8bit 表示指数&#xff0c;23bit 表示小数&#xff1b;FP16 是半精度浮点数&#xff0c;用…

PlayerSettings.WebGL.emscriptenArgs设置无效的问题

1&#xff09;PlayerSettings.WebGL.emscriptenArgs设置无效的问题 2&#xff09;java.lang.NoSuchMethodError的不明崩溃问题 3&#xff09;UE电影摄像机旋转问题 4&#xff09;Android设备游戏切后台后唤起&#xff0c;有概率变卡且黑屏 这是第383篇UWA技术知识分享的推送&am…

MATLAB使用速成 第七章(多项式运算与代数方程求解)

一、多项式运算 1、多项式的表示方法 &#xff08;1&#xff09;在MATLAB中&#xff0c;n次多项式用一个长度为n1的向量来表示。 &#xff08;2&#xff09;多项式与符号表达式的互化&#xff1a; ①语句“poly2sym(x)”可将多项式x转化为符号表达式。 ②语句“sym2poly(f)…

c++继承

目录 前言 继承的概念及定义 继承方式与访问限定符 基类和派生类对象赋值转换 隐藏的概念 派生类的默认成员函数 派生类的构造函数 派生类的赋值重载 派生类的析构函数 继承之友元与静态成员 菱形继承及菱形虚继承 单继承 多继承 菱形继承 虚继承原理 继承与组合…