Flutter 開発 - 画像の読み込みとキャッシュ ソース コードの分析

Flutter: には画像コンポーネントがありImage、通常はそのImage.network(src), Image.file(src),を使用してImage.asset(src)画像を読み込みます。の一般的なコンストラクターは
次のとおりです。Image

  const Image({
    
    
    super.key,
    required this.image,
    this.frameBuilder,
    this.loadingBuilder,
    this.errorBuilder,
    this.semanticLabel,
    this.excludeFromSemantics = false,
    this.width,
    this.height,
    this.color,
    this.opacity,
    this.colorBlendMode,
    this.fit,
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
    this.centerSlice,
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
    this.isAntiAlias = false,
    this.filterQuality = FilterQuality.low,
  })

その構築方法から、Image コンポーネントには ImageProvider 型の必須パラメーター image があることがわかります。ImageProvider は、画像データの取得と読み込みに関連するインターフェイスを定義する抽象クラスです。これには 2 つの主な責任があります。

  • 1. 画像データソースを提供します。
  • 2. 画像をキャッシュします。

ImageProvider抽象クラス:

abstract class ImageProvider<T extends Object> {
    
    
  const ImageProvider();
  
  ImageStream resolve(ImageConfiguration configuration) {
    
    
    ...
  }
  
  ImageStream createStream(ImageConfiguration configuration) {
    
    
    return ImageStream();
  }

  
  void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) {
    
    
    ...
  }
  
  Future<bool> evict({
    
     ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty }) async {
    
    
    ...
  }
  
  Future<T> obtainKey(ImageConfiguration configuration);
}

上記のソース コードから、画像の読み込みと解析が 、特にそのサブクラスImageProviderによって。class やclassImageProviderなど、多くのサブクラスが派生し、ネットワークから画像データをロードしたり、インストール パッケージ内のリソース ファイルからロードしたりします。NetworkImageAssetImageNetworkImageAssetImage

画像読み込み

画像データ ソースをロードするためのインターフェイスである ImageProvider にはメソッドが提供されておりload()、異なるデータ ソースは異なる方法でロードされます。
ネットワーク イメージの読み込みが使用されImage.network()、対応する ImageProvider はNetworkImage、load() メソッドを実装するクラスです。

  
  ImageStreamCompleter load(FileImage key, DecoderCallback decode) {
    
    
    return MultiFrameImageStreamCompleter(
      codec: _loadAsync(key, null, decode),
      scale: key.scale,
      debugLabel: key.file.path,
      informationCollector: () => <DiagnosticsNode>[
        ErrorDescription('Path: ${
      
      file.path}'),
      ],
    );
  }
  • ロード メソッドの戻り値の型は ImageStreamCompleter で、画像の読み込みプロセスを管理するためのいくつかのインターフェイスを定義する抽象クラスです。画像ウィジェットはこれを使用して画像の読み込みステータスを監視します。
  • MultiFrameImageStreamCompleter は ImageStreamCompleter のサブクラスであり、このクラスを実装すると、ImageStreamCompleter インスタンスをすばやく作成できます。

MultiFrameImageSteamCompleter には、ソース コードでメソッドを呼び出すために使用される codec パラメーターがあり_loadAsync()、メソッドの実装は次のとおりです。

Future<ui.Codec> _loadAsync(
   NetworkImage key,
   StreamController<ImageChunkEvent> chunkEvents,
   image_provider.DecoderBufferCallback? decode,
   image_provider.DecoderCallback? decodeDepreacted,
   ) async {
    
    
 try {
    
    
   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) {
    
    
     await response.drain<List<int>>(<int>[]);
     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 (decode != null) {
    
    
       final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
       return decode(buffer);
     } else {
    
    
       assert(decodeDepreacted != null);
       return decodeDepreacted!(bytes);
     }
}

_loadAsync()ソース コードから、このメソッドが画像のダウンロードを担当し、decode()ダウンロードされた画像データをデコードするメソッドを呼び出していることがわかります。

画像キャッシュ

画像キャッシュの主要な方法は次のとおりです:obtainKey(ImageConfiguration)
この方法は主に画像キャッシュの実現と連携します. ImageProvider がデータ ソースからデータをロードした後, 画像データをグローバル ImageCache にキャッシュします, そして画像データ キャッシュは Map, Map のキーは、このメソッドを呼び出したときの戻り値であり、異なるキーは異なる画像データ キャッシュを表します。
resolveこのメソッドは、ImageProviderImage に公開されるメインのエントリ メソッドであり、ImageConfiguration パラメータを受け取り、ImageStream を返します。

ImageStream resolve(ImageConfiguration configuration) {
    
    
  assert(configuration != null);
  final ImageStream stream = createStream(configuration);
  _createErrorHandlerAndKey(
    configuration,
        (T key, ImageErrorListener errorHandler) {
    
    
      resolveStreamForKey(configuration, stream, key, errorHandler);
    },
       ...
  );
  return stream;
}

ImageConfiguration: イメージとデバイスに関する情報が含まれます。_createErrorHandlerAndKeyキーをロードし、エラー ハンドラーを作成するために内部的に呼び出されます。
resolveStreamForKey方法:


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,
        () => loadBuffer(key, PaintingBinding.instance.instantiateImageCodecFromBuffer),
    onError: handleError,
  );
  if (completer != null) {
    
    
    stream.setCompleter(completer);
  }
}

solve メソッドでは、PaintingBinding.instance.imageCachePaintingBinding のプロパティである ImageCache のインスタンスであるsolveStreamForKey が呼び出されます。PaintingBinding.instance と imageCache は両方ともシングルトンであるため、イメージ キャッシュはグローバルであり、PaintingBinding.instance.imageCache均一に

ImageCache キャッシュの具体的な実装

ImageCache の定義:

const int _kDefaultSize = 1000;
const int _kDefaultSizeBytes = 100 << 20; // 100 MiB
class ImageCache {
    
    
  final Map<Object, _PendingImage> _pendingImages = <Object, _PendingImage>{
    
    };
  final Map<Object, _CachedImage> _cache = <Object, _CachedImage>{
    
    };

  final Map<Object, _LiveImage> _liveImages = <Object, _LiveImage>{
    
    };

  int get maximumSize => _maximumSize;
  int _maximumSize = _kDefaultSize;
  ...
}

ImageCache には 3 つのキャッシュ プールがあります。

  • _pendingImages: ロードおよびデコード中の画像を保存するために使用されます。画像がロードおよびデコードされると、ImageCache は _pendingImages の対応するエントリを自動的に削除します。
  • _cache: 読み込まれたすべての画像を保存するために使用されます。画像キャッシュの数とメモリ使用量が ImageCache の上限を超えない場合、_cache は常にキャッシュ エントリを保持し、超えた場合は LRU に従って解放されます。
  • _liveImages: 使用中の画像を保存するために使用されます。画像ウィジェットが画像を削除または置換するか、画像ウィジェット自体が削除されると、ImageCache は対応するエントリを _liveImages から削除します。ImageCache のみがすべてのキャッシュ プール エントリから同じ画像を解放します。画像は実際に解放されます
    。記憶の中で。

画像キャッシュのキーの生成方法

Map 内の同じキーの値は上書きされるため、つまりキーは画像キャッシュの一意の識別子であり、キーが異なる限り画像データはキャッシュ内に分散されます。では、画像の一意の識別子は何でしょうか? メソッドはソースコードから確認できますImageProvider.obtainKey()が、画像キャッシュで使用するキーはこのメソッドで生成されており、ImageProviderのサブクラスがこのメソッドを書き換えています。
ここに画像の説明を挿入
これは、異なる ImageProvider が異なるキーを定義することを意味します。NetworkImage の getKey() メソッド:

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

SynchronousFuture を作成し、それ自体を返すため、キーを比較するときは==演算子だけを確認してください。

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

ネットワーク画像の場合、キーは実際には URL+スケールです。したがって、2 つの画像の URL と縮尺は同じであるため、1 つのコピーのみがメモリにキャッシュされます。

キャッシュサイズを設定する

ImageCache クラスにはデフォルトのキャッシュ サイズがあります。

const int _kDefaultSize = 1000;//最多1000张
const int _kDefaultSizeBytes = 100 << 20; //最大 100 MiB

次のコードを使用して、カスタム キャッシュ制限を設定することもできます。

 PaintingBinding.instance.imageCache.maximumSize=500; //最多500张
 PaintingBinding.instance.imageCache.maximumSizeBytes = 50 << 20; //最大50M

おすすめ

転載: blog.csdn.net/Memory_of_the_wind/article/details/131350227
おすすめ