Flutter 扒光图片缓存框架cached_network_image

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

图片处理机制流程

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

一、注册图片流数据监听

yaml 复制代码
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类里了

less 复制代码
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();

ini 复制代码
 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 协同工作,确保图像正确地显示在应用程序中。

在 Flutter 中,ImageStreamCompleter 是用于处理图像加载和解码的重要组件。它是 ImageProvider 的一部分,负责管理图像的加载、解码和处理过程。

当您在 Flutter 中使用 Image widget 来显示图像时,Image widget 内部会使用 ImageProvider 来获取图像数据。而 ImageProvider 则使用 ImageStreamCompleter 来管理图像的加载和解码。

ImageStreamCompleter 的主要作用是监听图像加载过程中的各个阶段,并在加载完成后通知 ImageProvider。它负责以下几个任务:

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

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

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

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

图像加载状态管理:ImageStreamCompleter 会跟踪图像加载的各个阶段,并提供相应的状态,如开始加载、加载中、加载完成等,以便 ImageProvider 更新 Image widget 的显示状态。

总之,ImageStreamCompleter 在 ImageProvider 和 Image widget 之间充当了一个桥梁,负责管理图像的加载、解码和处理过程,并提供相应的状态通知。它确保图像能够正确加载并在 Image widget 中显示出来。

ImageProvider类的resolve方法

ImageStream 复制代码
    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 whatnot
          informationCollector: 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 存储当前活动的图像。

ini 复制代码
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缓存起来,接着我们看下面的代码

ini 复制代码
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类。

csharp 复制代码
class CachedNetworkImageProvider
extends ImageProvider<image_provider.CachedNetworkImageProvider> {}

二、从网络获取图片数据,并同时做文件缓存

接下来下面就分析CachedNetworkImageProvider这个类, 我们主要看loadBuffer这个方法,其它方法官方说已经过时了就不看了

typescript 复制代码
  @override
  ImageStreamCompleter 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.ImageLoader


ImageLoader类
 @override
  Stream<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();
    }
  }

我们看这行代码,从方法名称就可以看出是获取文件流的

php 复制代码
cacheManager.getFileStream(url,
                maxHeight: maxHeight,
                maxWidth: maxWidth,
                withProgress: true,
                headers: headers,
                key: cacheKey)

getFileStream 是BaseCacheManager的空方法,由子类实现,他的子类是CacheManager类

dart 复制代码
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 response
                in _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();
      }

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

dart 复制代码
  @override
  Future<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)从网络获取数据,然后再存入文件缓存里。

ini 复制代码
 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是一个函数对象, 就是DecoderBufferCallback

typedef 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方法

php 复制代码
  @override
  ImageStreamCompleter 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,
          );
        },
      );
    }
  }

我们看一下这段代码

scss 复制代码
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。

ini 复制代码
  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方法

ini 复制代码
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类,然后传入解析完的图片数据,进行渲染。

less 复制代码
 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,
);
复制代码
 

 
相关推荐
m0_7482478010 小时前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
迷雾漫步者12 小时前
Flutter组件————PageView
flutter·跨平台·dart
迷雾漫步者19 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
coder_pig1 天前
📝小记:Ubuntu 部署 Jenkins 打包 Flutter APK
flutter·ubuntu·jenkins
捡芝麻丢西瓜1 天前
flutter自学笔记5- dart 编码规范
flutter·dart
恋猫de小郭1 天前
什么?Flutter 可能会被 SwiftUI/ArkUI 化?全新的 Flutter Roadmap
flutter·ios·swiftui
sunly_2 天前
Flutter:导航,tab切换,顶部固定,列表分页滚动
开发语言·javascript·flutter
敲代码的小强2 天前
Flutter项目兼容鸿蒙Next系统
flutter·华为·harmonyos
Zh-jie3 天前
flutter 快速实现侧边栏
前端·javascript·flutter
truemi.733 天前
flutter --no-color pub get 超时解决方法
android·flutter