A, problem,

The original image caching mechanism of Flutter is PaintingBinding. Instance! ImageCache is used to manage the cache, which is cached in memory. Every time the APP is reopened or the cache is cleaned, the network request will be made again. Large images are slow and unfriendly to load, and increase the burden on the server.

Second, the train of thought

1. View the named constructor of fadeInImage. assetNetwork, Image.network and other network requests, initialize the ImageProvider.

  FadeInImage.assetNetwork({
    Key key,
    @required String placeholder,
    this.placeholderErrorBuilder,
    @required String image,
    this.imageErrorBuilder,
    AssetBundle bundle,
    double placeholderScale,
    double imageScale = 1.0.this.excludeFromSemantics = false.this.imageSemanticLabel,
    this.fadeOutDuration = const Duration(milliseconds: 300),
    this.fadeOutCurve = Curves.easeOut,
    this.fadeInDuration = const Duration(milliseconds: 700),
    this.fadeInCurve = Curves.easeIn,
    this.width,
    this.height,
    this.fit,
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
    this.matchTextDirection = false.int placeholderCacheWidth,
    int placeholderCacheHeight,
    int imageCacheWidth,
    int imageCacheHeight,
  }) : assert(placeholder ! =null),
       assert(image ! =null), placeholder = placeholderScale ! =null
         ? ResizeImage.resizeIfNeeded(placeholderCacheWidth, placeholderCacheHeight, ExactAssetImage(placeholder, bundle: bundle, scale: placeholderScale))
         : ResizeImage.resizeIfNeeded(placeholderCacheWidth, placeholderCacheHeight, AssetImage(placeholder, bundle: bundle)),
       assert(imageScale ! =null),
       assert(fadeOutDuration ! =null),
       assert(fadeOutCurve ! =null),
       assert(fadeInDuration ! =null),
       assert(fadeInCurve ! =null),
       assert(alignment ! =null),
       assert(repeat ! =null),
       assert(matchTextDirection ! =null),
       image = ResizeImage.resizeIfNeeded(imageCacheWidth, imageCacheHeight, NetworkImage(image, scale: imageScale)),
       super(key: key);
Copy the code
  Image.network(
    String src, {
    Key key,
    double scale = 1.0.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.filterQuality = FilterQuality.low,
    this.isAntiAlias = false.Map<String.String> headers,
    int cacheWidth,
    int cacheHeight,
  }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, NetworkImage(src, scale: scale, headers: headers)),
       assert(alignment ! =null),
       assert(repeat ! =null),
       assert(matchTextDirection ! =null),
       assert(cacheWidth == null || cacheWidth > 0),
       assert(cacheHeight == null || cacheHeight > 0),
       assert(isAntiAlias ! =null),
       super(key: key);
Copy the code

Among them: image = ResizeImage. ResizeIfNeeded (cacheWidth cacheHeight, NetworkImage (SRC, scale: scale, headers: Headers), and create ResizeImage of ImageProvider type using NetworkImage of ImageProvider type.

NetworkImage is an abstract class that inherits ImageProvider.

abstract class NetworkImage extends ImageProvider<NetworkImage> {
  /// Creates an object that fetches the image at the given URL.
  ///
  /// The arguments [url] and [scale] must not be null.
  const factory NetworkImage(String url, { double scale, Map<String.String>? headers }) = network_image.NetworkImage;

  /// The URL from which the image will be fetched.
  String get url;

  /// The scale to place in the [ImageInfo] object of the image.
  double get scale;

  /// The HTTP headers that will be used with [HttpClient.get] to fetch image from network.
  ///
  /// When running flutter on the web, headers are not used.
  Map<String.String>? get headers;

  @override
  ImageStreamCompleter load(NetworkImage key, DecoderCallback decode);
}
Copy the code

Const factory NetworkImage(String URL, {double scale, Map

? headers }) = network_image.NetworkImage;
,>

Go to network_image.NetworkImage and go to _network_image_io.dart.

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.


import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';

import 'binding.dart';
import 'debug.dart';
import 'image_provider.dart' as image_provider;
import 'image_stream.dart';

/// The dart:io implementation of [image_provider.NetworkImage].
@immutable
class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkImage> implements image_provider.NetworkImage {
  /// Creates an object that fetches the image at the given URL.
  ///
  /// The arguments [url] and [scale] must not be null.
  const NetworkImage(this.url, { this.scale = 1.0.this.headers })
    : assert(url ! =null),
      assert(scale ! =null);

  @override
  final String url;

  @override
  final double scale;

  @override
  final Map<String.String>? headers;

  @override
  Future<NetworkImage> obtainKey(image_provider.ImageConfiguration configuration) {
    return SynchronousFuture<NetworkImage>(this);
  }

  @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), ]; }); }// Do not access this field directly; use [_httpClient] instead.
  // We set `autoUncompress` to false to ensure that we can trust the value of
  // the `Content-Length` HTTP header. We automatically uncompress the content
  // in our call to [consolidateHttpClientResponseBytes].
  static finalHttpClient _sharedHttpClient = HttpClient().. autoUncompress =false;

  static HttpClient get _httpClient {
    HttpClient client = _sharedHttpClient;
    assert(() {
      if(debugNetworkImageHttpClientProvider ! =null) client = debugNetworkImageHttpClientProvider! (a);return true; } ());return client;
  }

  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.
        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(); }}@override
  bool operator= = (Object other) {
    if(other.runtimeType ! = runtimeType)return false;
    return other is NetworkImage
        && other.url == url
        && other.scale == scale;
  }

  @override
  int get hashCode => ui.hashValues(url, scale);

  @override
  String toString() => '${objectRuntimeType(this.'NetworkImage')}("$url", scale: $scale) ';
}

Copy the code

The _loadAsync method is modified to achieve local storage and acquisition of pictures.

Three, implementation,

1. Create a file my_LOCAL_CACHE_NETWORK_image. dart and copy the _network_image_io. 2. Contents of all files are as follows (non-empty security version) :

import 'dart:async';
import 'dart:convert' as convert;
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:crypto/crypto.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';

/// The dart:io implementation of [image_provider.NetworkImage].
@immutable
class MyLocalCacheNetworkImage extends ImageProvider<NetworkImage> implements NetworkImage {
  /// Creates an object that fetches the image at the given URL.
  ///
  /// The arguments [url] and [scale] must not be null.
  const MyLocalCacheNetworkImage(
    this.url, {
    this.scale = 1.0.this.headers,
    this.isLocalCache = false,}) :assert(url ! =null),
        assert(scale ! =null);

  @override
  final String url;

  @override
  final double scale;

  @override
  final Map<String.String> headers;

  final bool isLocalCache;

  @override
  Future<NetworkImage> obtainKey(ImageConfiguration configuration) {
    return SynchronousFuture<NetworkImage>(this);
  }

  @override
  ImageStreamCompleter load(NetworkImage key, 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, chunkEvents, decode),
      chunkEvents: chunkEvents.stream,
      scale: key.scale,
      debugLabel: key.url,
      informationCollector: () {
        return <DiagnosticsNode>[
          DiagnosticsProperty<ImageProvider>('Image provider'.this),
          DiagnosticsProperty<NetworkImage>('Image key', key), ]; }); }// Do not access this field directly; use [_httpClient] instead.
  // We set `autoUncompress` to false to ensure that we can trust the value of
  // the `Content-Length` HTTP header. We automatically uncompress the content
  // in our call to [consolidateHttpClientResponseBytes].
  static finalHttpClient _sharedHttpClient = HttpClient().. autoUncompress =false;

  static HttpClient get _httpClient {
    HttpClient client = _sharedHttpClient;
    assert(() {
      if(debugNetworkImageHttpClientProvider ! =null) client = debugNetworkImageHttpClientProvider();
      return true; } ());return client;
  }

  Future<ui.Codec> _loadAsync(
    NetworkImage key,
    StreamController<ImageChunkEvent> chunkEvents,
    DecoderCallback decode,
  ) async {
    try {
      assert(key == this);

      /// If the image is cached locally, return the image directly
      if(isLocalCache ! =null && isLocalCache == true) {
        final Uint8List bytes = await _getImageFromLocal(key.url);
        if(bytes ! =null&& bytes.lengthInBytes ! =null&& bytes.lengthInBytes ! =0) {
          return awaitPaintingBinding.instance.instantiateImageCodec(bytes); }}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.
        throw NetworkImageLoadException(statusCode: response.statusCode, uri: resolved);
      }

      final Uint8List bytes = await consolidateHttpClientResponseBytes(
        response,
        onBytesReceived: (int cumulative, inttotal) { chunkEvents.add(ImageChunkEvent( cumulativeBytesLoaded: cumulative, expectedTotalBytes: total, )); });/// After the network request is complete, the image is cached locally
      if(isLocalCache ! =null && isLocalCache == true&& bytes.lengthInBytes ! =0) {
        _saveImageToLocal(bytes, key.url);
      }

      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(); }}/// The image path is processed by MD5 and cached locally
  void _saveImageToLocal(Uint8List mUInt8List, String name) async {
    String path = await _getCachePathString(name);
    var file = File(path);
    bool exist = await file.exists();
    if (!exist) {
      File(path).writeAsBytesSync(mUInt8List);
    }
  }

  /// Get pictures from local
  Future<Uint8List> _getImageFromLocal(String name) async {
    String path = await _getCachePathString(name);
    var file = File(path);
    bool exist = await file.exists();
    if (exist) {
      final Uint8List bytes = await file.readAsBytes();
      return bytes;
    }
    return null;
  }

  /// Get the image cache path and create
  Future<String> _getCachePathString(String name) async {
    // Get the name of the image
    String filePathFileName = md5.convert(convert.utf8.encode(name)).toString();
    String extensionName = name.split('/').last.split('. ').last;

    // print($name);
    // print('filePathFileName:$filePathFileName');
    // print('extensionName:$extensionName');

    // Generate and obtain the result storage path
    final tempDic = await getTemporaryDirectory();
    Directory directory = Directory(tempDic.path + '/CacheImage/');
    bool isFoldExist = await directory.exists();
    if(! isFoldExist) {await directory.create();
    }
    return directory.path + filePathFileName + '.$extensionName';
  }

  @override
  bool operator= = (Object other) {
    if(other.runtimeType ! = runtimeType)return false;
    return other is NetworkImage && other.url == url && other.scale == scale;
  }

  @override
  int get hashCode => ui.hashValues(url, scale);

  @override
  String toString() => '${objectRuntimeType(this.'NetworkImage')}("$url", scale: $scale) ';
}

Copy the code

The main changes are as follows: 1. Get cache from local and return it

      /// If the image is cached locally, return the image directly
      if(isLocalCache ! =null && isLocalCache == true) {
        final Uint8List bytes = await _getImageFromLocal(key.url);
        if(bytes ! =null&& bytes.lengthInBytes ! =null&& bytes.lengthInBytes ! =0) {
          return awaitPaintingBinding.instance.instantiateImageCodec(bytes); }}Copy the code

2. After the picture network request is finished, it is stored locally

      /// After the network request is complete, the image is cached locally
      if(isLocalCache ! =null && isLocalCache == true&& bytes.lengthInBytes ! =0) {
        _saveImageToLocal(bytes, key.url);
      }
Copy the code

3. Save to local, obtain pictures from local, obtain and create specific implementation of local cache path, mainly in the image network request to obtain bytes and image URL for storage and other operations.

  /// The image path is processed by MD5 and cached locally
  void _saveImageToLocal(Uint8List mUInt8List, String name) async {
    String path = await _getCachePathString(name);
    var file = File(path);
    bool exist = await file.exists();
    if (!exist) {
      File(path).writeAsBytesSync(mUInt8List);
    }
  }

  /// Get pictures from local
  Future<Uint8List> _getImageFromLocal(String name) async {
    String path = await _getCachePathString(name);
    var file = File(path);
    bool exist = await file.exists();
    if (exist) {
      final Uint8List bytes = await file.readAsBytes();
      return bytes;
    }
    return null;
  }

  /// Get the image cache path and create
  Future<String> _getCachePathString(String name) async {
    // Get the name of the image
    String filePathFileName = md5.convert(convert.utf8.encode(name)).toString();
    String extensionName = name.split('/').last.split('. ').last;

    // print($name);
    // print('filePathFileName:$filePathFileName');
    // print('extensionName:$extensionName');

    // Generate and obtain the result storage path
    final tempDic = await getTemporaryDirectory();
    Directory directory = Directory(tempDic.path + '/CacheImage/');
    bool isFoldExist = await directory.exists();
    if(! isFoldExist) {await directory.create();
    }
    return directory.path + filePathFileName + '.$extensionName';
  }
Copy the code

Four, the use of

Copy the above named constructor and create your own named constructor, such as (part of the code) :

class CustomFadeInImage extends StatelessWidget {
  CustomFadeInImage.assetNetwork({
    @required this.image,
    this.placeholder,
    this.width,
    this.height,
    this.fit,
    this.alignment = Alignment.center,
    this.imageScale = 1.0.this.imageCacheWidth,
    this.imageCacheHeight,
  }) : imageProvider = ResizeImage.resizeIfNeeded(
            imageCacheWidth, imageCacheHeight, MyLocalCacheNetworkImage(image, scale: imageScale, isLocalCache: true));
Copy the code

Will ResizeImage. The NetworkImage resizeIfNeeded replaced with MyLocalCacheNetworkImage can.

5. Cache cleaning

Clear the picture in the corresponding cache directory.