This article is the tenth in a series of articles that delve into the image loading process of Flutter, stripping out the interesting parts of the image loading process, and ending with the implementation of Flutter to support local image caching.

Article summary address:

A complete series of articles on Flutter

A series of articles on the world outside Flutter

In Flutter, images are loaded mainly through the Image control, which is itself a StatefulWidget. The Image must be responsible for layout and paint for the RenderObject that has it. How does this process turn an Image into a screen?

1. Picture process

The image loading process of Flutter is actually “not complicated”. For details, click on the picture below. Taking the online image loading as an example, the main process of Flutter is briefly summarized.

  • 1, first of all,ImagethroughImageProvidergetImageStreamobject
  • 2, then_ImageStateusingImageStreamAdd listener and wait for picture data
  • 3, thenImageProviderthroughloadMethod to load and returnImageStreamCompleterobject
  • 4, thenImageStreamWill be associatedImageStreamCompleter
  • 5, afterImageStreamCompleterThe image will be downloaded via HTTP and passed throughPaintingBindingAfter the coding transformation, we getui.CodecCan draw objects and encapsulate intoImageInforeturn
  • 6, thenImageInfoThe callback toImageStreamListen to, set to_ImageStateThe build ofRawImageObject.
  • 7, the lastRawImageRenderImageDraw in PaintImageInfoIn theui.Codec

Notice, hereui.CodecAnd the back of theui.ImageBecause a rename was added to a Flutter to distinguish it from other types when importing objects:import 'dart:ui' as ui show Codec;

Are you feeling a little dizzy? Relax! We will understand this process step by step later.

There are three main roles in the loading process of Flutter images:

  • Image: The Widget used to display the Image, which is finally drawn with the RenderImage inside.
  • ImageProvider: Provides the way to load images as followsNetworkImageFileImageMemoryImageAssetImageEtc, so as to obtainImageStreamforSurveillance results.
  • ImageStream: image loading object, throughImageStreamCompleterOne will be returned at the endImageInfoAnd theImageInfoContain insideRenderImageThe last of theDraw the objectui.Image

As can be seen from the large picture process above, network images are loaded by NetworkImage Provider. The implementation of various providers is basically the same with minor differences, and the main methods to be realized are shown in the figure below:

1, obtainKey

This method is used to indicate the existence of the current Provider. For example, in NetworkImage, this method returns a SynchronousFuture

(this), which is NetworkImage itself. And the resulting key, in the ImageProvider, is used as a key for the memory cache.

In NetworkImage, runtimeType, URL, and scale are used to determine whether two NetworkImages are equal. Therefore, in addition to the URL, the scale of the image also affects the cached object.

2, the load (T key)

The load method, as its name implies, is loaded, and the key used in this method is, of course, provided by the obtainKey method above.

The load method returns the ImageStreamCompleter abstract object, which is used to manage and notify the Dart: uI.image obtained in ImageStream, Such as in NetworkImage subclass MultiFrameImageStreamCompleter, more than it can handle frame animation, if the picture only a needle, so will perform a all over.

3, resolve

The key to ImageProvider is the resolve method, which is called in the Image lifecycle callback methods didChangeDependencies, didUpdateWidget, and ReAssemble. As you can see from the source code below, obtainKey and load are called here

Here’s an interesting object, Zone!

Because synchronous exceptions can be caught by a try-catch in a Flutter, asynchronous exceptions such as the Future cannot be caught directly by the current try-catch.

So the Zone concept in Dart is that you can assign a Zone to an execution object, sort of providing a sandbox environment in which you can capture, block, or modify code behavior, such as all unhandled exceptions.

Resolve method if NaZhu USES PaintingBinding. Instance. ImageCache. PutIfAbsent (key, () = > load (key), PaintingBinding is a kind of glue, This is mostly done by Mixins sticking to WidgetsFlutterBinding, which we explained in the previous chapter is the executor of our startup method runApp.

So image cache is in PaintingBinding. Instance. ImageCache singleton maintenance.

If the ImageStreamCompleter is returned, the ImageStreamCompleter is returned. If the ImageStreamCompleter is returned, the ImageStreamCompleter is returned.

The ImageStreamCompleter returned does not mean that the image has been loaded, so if the image is loaded for the first time, the _PendingImage will be used to indicate that the image is in the loading state. And add a listener to replace the _CacheImage with a cache once the image is loaded.

This is a slightly different concept from Cache. Previously, we Cache key-bitmap objects, that is, the actual drawing data. In Flutter, we Cache only ImageStreamCompleter objects. Instead of actually drawing the object Dart: UI.image.

3, ImageStreamCompleter

ImageStreamCompleter is an abstract object that is used to manage and notify the ImageStream. After processing the Image data, the resulting object ImageInfo contains dart: UI.Image.

Next we see NetworkImage MultiFrameImageStreamCompleter ImageStreamCompleter implementation in the class. Code shown in the diagram below, MultiFrameImageStreamCompleter render data obtained through the analysis of the codec parameters, and the data source is obtained by _loadAsync method, this method is mainly through HTTP download images, Image data is encoded by PaintingBinding ImageCodec, and the image is converted into data that can be drawn by the engine.

And inside the MultiFrameImageStreamCompleter, UI. The Codec will be UI. Image, through the ImageInfo encapsulated, and gradually to return back to _ImageState, The data is then passed inside the RenderImage via setState to draw.

Well, now go back to the flow chart at the beginning, is there any sense of clarity?

Local image cache

Flutter implements memory caching of images, but does not implement local caching of images. Therefore, we should start with ImageProvider.

From the NetworkImage analysis above, we know that the image is downloaded from the _loadAsync method via HTTP, so the simplest is to CV a code from NetworkImage, Modified _loadAsync to support READING local cache before HTTP download and saving data locally after download.

With the Flutter_cache_manager plug-in, you can quickly and easily implement local image caching as shown in the following code:

 Future<ui.Codec> _loadAsync(NetworkImage key) async {
    assert(key == this);

    /// add this start
    /// flutter_cache_manager DefaultCacheManager
    final fileInfo = await DefaultCacheManager().getFileFromCache(key.url);
    if(fileInfo ! = null && fileInfo.file ! = null) { final Uint8List cacheBytes = await fileInfo.file.readAsBytes();if(cacheBytes ! = null) {returnPaintingBinding.instance.instantiateImageCodec(cacheBytes); } } /// add this end 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) throw Exception('HTTP request failed, statusCode: ${response? .statusCode}, $resolved');

    final Uint8List bytes = await consolidateHttpClientResponseBytes(response);
    if (bytes.lengthInBytes == 0)
      throw Exception('NetworkImage is an empty file: $resolved');
    
    /// add this start
    await DefaultCacheManager().putFile(key.url, bytes);
    /// add this edn

    return PaintingBinding.instance.instantiateImageCodec(bytes);
  }
Copy the code

Other supplements

1. Number of caches

In the article on memory analysis of Flutter online applications, there is a detailed analysis of memory problems associated with image loading, including ImageCache problems.

One of the problems with caching asynchronous loading objects is that you don’t know how much memory will be consumed until the image is loaded and decoded, and a lot of image loading will result in a lot of IO for the decoding task.

The default cache size for ImageCache in Flutter is

const int _kDefaultSize = 1000; const int _kDefaultSizeBytes = 100 << 20; / / 100Copy the code

So simple and crude way is: PaintingBinding. Instance. ImageCache. MaximumSize = 100; Pause images when the page is not visible.

2, 9 figure

In the Image, the centerSlice configuration parameter can be set.

From here, the tenth chapter is finally over! (/ / / del / / /)

Resources to recommend

  • Making: github.com/CarGuo/
  • Open Source Flutter complete project:Github.com/CarGuo/GSYG…
  • Open Source Flutter Multi-case learning project:Github.com/CarGuo/GSYF…
  • Open Source Fluttre Combat Ebook Project:Github.com/CarGuo/GSYF…
Full open source project recommendation:
  • GSYGithubApp Flutter
  • GSYGithubApp React Native
  • GSYGithubAppWeex