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

 
相关推荐
LawrenceLan13 小时前
Flutter 零基础入门(九):构造函数、命名构造函数与 this 关键字
开发语言·flutter·dart
一豆羹14 小时前
macOS 环境下 ADB 无线调试连接失败、Protocol Fault 及端口占用的深度排查
flutter
行者9614 小时前
OpenHarmony上Flutter粒子效果组件的深度适配与实践
flutter·交互·harmonyos·鸿蒙
行者9617 小时前
Flutter与OpenHarmony深度集成:数据导出组件的实战优化与性能提升
flutter·harmonyos·鸿蒙
小雨下雨的雨17 小时前
Flutter 框架跨平台鸿蒙开发 —— Row & Column 布局之轴线控制艺术
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨17 小时前
Flutter 框架跨平台鸿蒙开发 —— Center 控件之完美居中之道
flutter·ui·华为·harmonyos·鸿蒙
小雨下雨的雨18 小时前
Flutter 框架跨平台鸿蒙开发 —— Icon 控件之图标交互美学
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨18 小时前
Flutter 框架跨平台鸿蒙开发 —— Placeholder 控件之布局雏形美学
flutter·ui·华为·harmonyos·鸿蒙系统
行者9619 小时前
OpenHarmony Flutter弹出菜单组件深度实践:从基础到高级的完整指南
flutter·harmonyos·鸿蒙
前端不太难19 小时前
Flutter / RN / iOS,在长期维护下的性能差异本质
flutter·ios