Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to understand the mechanism of Flutter image loading and caching

2025-04-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article focuses on "how to understand the Flutter image loading and caching mechanism", interested friends may wish to take a look. The method introduced in this paper is simple, fast and practical. Let's let the editor take you to learn how to understand the Flutter image loading and caching mechanism.

Preface

Today let's learn how Flutter itself loads and manages images.

Flutter provides a picture control Image,Image that defines several ways to load pictures, including Image.asset, Image.file, Image.network, and Image.memory.

Image maintains an ImageProvider object internally, while ImageProvider actually maintains the entire image loading job. The Widget itself is embodied in RawImage:

Picture control

/ / ImageWidget result = RawImage (image: _ imageInfo?.image, debugImageLabel: _ imageInfo?.debugLabel, width: widget.width, height: widget.height, scale: _ imageInfo?.scale?? 1.0, color: widget.color, 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,) Return result

You can see here that _ imageInfo determines how RawImage displays the picture.

_ imageInfo will be reassigned at each frame of the image:

/ / image.dartvoid _ handleImageFrame (ImageInfo imageInfo, bool synchronousCall) {setState (() {_ imageInfo = imageInfo;}})

So where does the picture information come from? it is initiated by the _ resolveImage method. This method is called in the didChangeDependencies, didUpdateWidget, and reassemble methods of _ ImageState.

That is, when the control changes to refresh the state, the picture will be reparsed.

Picture analysis

The _ resolveImage logic is as follows:

Void _ resolveImage () {final ScrollAwareImageProvider provider = ScrollAwareImageProvider (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);}

Here we will wrap it in ScrollAwareImageProvider. We will introduce the functions of ScrollAwareImageProvider later. We will skip it here.

/ / ImageProvider# resolveImageStream resolve (ImageConfiguration configuration) {_ createErrorHandlerAndKey (configuration, (T key, ImageErrorListener errorHandler) {resolveStreamForKey (configuration, stream, key, errorHandler);}, (T? Key, dynamic 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 ('Image provider', this); yield DiagnosticsProperty (' Image configuration', configuration); yield DiagnosticsProperty ('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,);}

The resolve method calls _ createErrorHandlerAndKey to handle the exception of picture loading. When the picture is loaded normally, resolveStreamForKey is executed.

/ / resolveStreamForKeyvoid resolveStreamForKey (ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) {if (stream.completer! = null) {final ImageStreamCompleter? Completer = PaintingBinding.instanceroom.imageCachett.putIfAbsent (key, () = > stream.completerflows, onError: handleError,); return;} final ImageStreamCompleter? Completer = PaintingBinding.instanceCodec. ImageCache.putIfAbsent (key, () = > load (key, PaintingBinding.instanceimageCodec), onError: handleError,); if (completer! = null) {stream.setCompleter (completer);}}

Flutter maintains the logic related to image caching in the ImageCache object.

Cache management

There are 3 map in ImageCache:

Represent separately

The picture being loaded

Pictures cached in memory

Indicates an active picture, which may be emptied after a change in Widget status

Add cach

The key of map is set when the cache is added, and the key is provided by the ImageProvider object. For example:

AssetImage when the package name is the same as bundle, key can be considered the same.

NetworkImage when the picture url is the same as the ratio, key can be considered the same.

ImageCache is actually a singleton object. That is, Flutter's image cache management is global. The most important method of ImageCache is putIfAbsent:

/ / the code ImageStreamCompleter that has sorted out the core logic? PutIfAbsent (Object key, ImageStreamCompleter loader (), {ImageErrorListener? OnError}) {/ / get the cache from the loaded map according to key, and return ImageStreamCompleter directly if there is one? Result = _ pendingImages [key]? .completer; if (result! = null) {return result;} / / check the memory cache and update the surviving map final _ CachedImage if it exists? Image = _ cache.remove (key); if (image! = null) {_ trackLiveImage (key, _ LiveImage (image.completer, image.sizeBytes, () = > _ liveImages.remove (key); _ cache [key] = image; return image.completer;} / / No cache, take final _ CachedImage from _ live? LiveImage = _ liveImages [key]; if (liveImage! = null) {/ / update cache _ touch (key, liveImage, timelineTask); return liveImage.completer;} / / 3 map did not get the cached image result = loader (); / / load _ trackLiveImage (key, _ LiveImage (result, null, () = > _ liveImages.remove (key)); _ PendingImage? UntrackedPendingImage; / / define a listener void listener (ImageInfo? Info, bool syncCall) {/ / loaded listening} / / wraps a listener final ImageStreamListener streamListener = ImageStreamListener (listener); if (maximumSize > 0 & & maximumSizeBytes > 0) {/ / put _ PendingImage images [key] = _ PendingImage (result, streamListener);} else {untrackedPendingImage = _ PendingImage (result, streamListener) } / / add listening result.addListener (streamListener); return result;}

Logic of listener callback:

When the state of Image changes, a modification to liveImages is triggered:

/ / Image_imageStream.removeListener (_ getListener ()); / / ImageStreamvoid removeListener (ImageStreamListener listener) {for (final VoidCallback callback in _ onLastListenerRemovedCallbacks) {callback ();} _ onLastListenerRemovedCallbacks.clear ();}

In the case of _ trackLiveImage, _ LiveImage registers the above callback:

_ trackLiveImage (key, _ LiveImage (image.completer, image.sizeBytes, () = > _ liveImages.remove (key)

At this point, the changed image will be removed from _ liveImages.

Thus, the priority of caching is pending-> cache-> live-> load. The process of image caching and acquisition is shown below:

Cache cleanup

When the cache size is updated, the cache size is also checked:

Void _ checkCacheSize (TimelineTask? TimelineTask) {while (_ currentSizeBytes > _ maximumSizeBytes | | _ cache.length > _ maximumSize) {final Object key = _ cache.keys.first; final _ CachedImage image = _ cache [key]!; _ currentSizeBytes-= image.sizeBytesgiving; _ cache.remove (key);}}

When the current total cache capacity is greater than the maximum capacity or the number of caches is greater than the maximum number, the cache will be cleaned.

So in the process of using the cache above, the cache accessed multiple times will put the key back to avoid being cleaned up as soon as it comes up.

So Flutter's own cache cleanup algorithm also follows the "least recently used".

Picture loading

Image loading mainly depends on the load method above. Different ImageProvider subclasses have their own implementations. For example

AssetImage

Return MultiFrameImageStreamCompleter (codec: _ loadAsync (key, decode), scale: key.scale, debugLabel: key.name, informationCollector: collector)

NetworkImage

Final StreamController chunkEvents = StreamController (); return MultiFrameImageStreamCompleter (chunkEvents: chunkEvents.stream, codec: _ loadAsync (key as NetworkImage, decode, chunkEvents), scale: key.scale, debugLabel: key.url, informationCollector: _ imageStreamInformationCollector (key))

The logic is basically the same, and the specific process is reflected in loadAsync:

/ / AssetImage _ loadAsynctry {data = await key.bundle.load (key.name);} on FlutterError {PaintingBinding.instanceroom.imageCache.evict (key); rethrow;} if (data = = null) {/ / load data is null, clear the key cache PaintingBinding.instanceroom.imageCache.evict (key); throw StateError ('Unable to read data');} return await decode (data.buffer.asUint8List ()) / / NetworkImage _ loadAsyncFuture _ loadAsync (NetworkImage key, image_provider.DecoderCallback decode, StreamController chunkEvents) {final Uri resolved = Uri.base.resolve (key.url); return ui.webOnlyInstantiateImageCodecFromUrl (resolved, / / ignore: undefined_function chunkCallback: (int bytes, int total) {chunkEvents.add (ImageChunkEvent (cumulativeBytesLoaded: bytes, expectedTotalBytes: total));}) as Future;}

Here, the images will be loaded from bundle and pulled from the network.

Sliding processing

Remember the ScrollAwareImageProvider mentioned above, here is a judgment about sliding:

If (Scrollable.recommendDeferredLoadingForContext (context.context)) {SchedulerBinding.instance.scheduleFrameCallback ((_) {scheduleMicrotask (()) = > resolveStreamForKey (configuration, stream, key, handleError);}); return;}

When the logic in if holds, put the work of parsing the picture to the next frame. The specific logic of recommendDeferredLoadingForContext:

Static bool recommendDeferredLoadingForContext (BuildContext context) {final _ ScrollableScope widget = context.getElementForInheritedWidgetOfExactType ()? .widget as _ ScrollableScope; if (widget = = null) {return false;} / / there is a sliding widget return widget.position.recommendDeferredLoading (context);}

This will find the nearest _ ScrollableScope in the Widget tree. If ScrollableScope is in a fast slide, it returns true. So flutter will not load images in the fast-sliding list.

At this point, I believe you have a deeper understanding of "how to understand the Flutter image loading and caching mechanism". You might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report