Flutter 扒一扒图片缓存框架cached_network_image

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

图片处理机制流程

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

一、注册图片流数据监听


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类里了

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();

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

ImageStreamCompleter 的作用包括以下几个方面:
在 Flutter 中,ImageStreamCompleter 是用于处理图像加载和解码的重要组件。它是 ImageProvider 的一部分,负责管理图像的加载、解码和处理过程。

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

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

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

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

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

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

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

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

ImageProvider类的resolve方法

    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 存储当前活动的图像。

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

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类。

class CachedNetworkImageProvider
extends ImageProvider<image_provider.CachedNetworkImageProvider> {}

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

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


  @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();
    }
  }

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

cacheManager.getFileStream(url,
                maxHeight: maxHeight,
                maxWidth: maxWidth,
                withProgress: true,
                headers: headers,
                key: cacheKey)

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

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();
      }

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

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

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

  @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,
          );
        },
      );
    }
  }


我们看一下这段代码

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。

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

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

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,
  );


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/579215.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(bevfusion:多模态融合)报错:AttributeError: module ‘numpy‘ has no attribute ‘long‘

解决办法1&#xff1a;降低numpy版本&#xff08;我的报错版本是1.24.4&#xff09; pip install numpy1.20.3解决办法2&#xff1a;或者将np.long改为np.int64 (由于我的报错在环境内部&#xff0c;不好修改&#xff0c;所以直接降低的numpy版本)

Java中的StringBuilder

为什么要用StringBuilder StringBuilder是一个可变的字符串类&#xff08;StringBuilder对象中的内容可变&#xff09; 为什么不用String拼接呢&#xff1f; 因为拼接字符串会造成前两个字符串的空间浪费 package dayhou40.day44; ​ public class test {public static voi…

Java线程池让使用线程变得更加高效

使用一个线程需要经过创建、运行、销毁三大步骤&#xff0c;如果业务系统每个线程都要经历这个过程&#xff0c;那势必带来过多不必要的资源消耗。线程池就是为了解决这个问题而生&#xff0c;需要时就从池中拿取&#xff0c;使用完毕就放回去&#xff0c;池化思想通过复用对象…

SpringBoot---------Hutool

第一步&#xff1a;引入依赖 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-parent</artifactId><version>5.7.17</version></dependency> 第二步&#xff1a;各种用法 ①生成随机数 //生成验证码 String s …

游戏新手村21:再谈游戏广告页面设计

前文我们说到了网页游戏的LandingPage页面设计中需要遵循的一些规范和注意事项&#xff0c;本章我们重点谈下网络游戏的广告页面设计。 之前在金山的时候&#xff0c;大家习惯或者喜欢称LandingPage为分流页&#xff0c;这个页面需要加入哪些游戏信息才能在短时间内俘获玩家的…

cJSON的使用

文章目录 一、CJSON初识二、CJSON解析器基础三、CJSON解析数据JSON解析基础CJSON解析数组数据CJSON解析嵌套数据 五、创建JSON数据 一、CJSON初识 JSON (JavaScript Object Notation)是一种轻量级的数据交换格式&#xff0c;常用于在网络之间传输数据。它是一种文本格式&#…

Linux计划任务书以及定时任务的编写

一、程序可以通过两种方式执行&#xff1a; 手动执行利用调度任务&#xff0c;依据一定的条件自动执行 自动执行可通过一下两个命令来实现: &#xff08;1&#xff09;At &#xff08;单一工作调度&#xff09; &#xff08;2&#xff09;Cron &#xff08;循环工作调度&a…

微信小程序的开发

1.了解项目的基本组成结构 pages 用来存放所有小程序的页面 utils 用来存放工具性质的模块(例如:格式化时间的自定义模块) app.js 小程序项目的入口文件 app.json 小程序项目的全局配置文件 app.wxss 小程序项目的全局样式文件 project.config.json 项目的配置文件 sitem…

(GEE)2000-2020年黄河流域时序渐变图及高程模型计算 JavaScript版

文章目录 一. 选取目标区域二. NDVI实现三. 高程模型DEM实现四. 时序图五. 植被覆盖类型六. 参考文献 首先推荐吴秋生老师团队开源的便捷构建网站&#xff1a;适用于地理空间应用的Streamlight 吴秋生老师团队的工具请自行探索。本文讲解基于GEE云开发平台实现&#xff0c;基于…

吾日三省吾身---对平常遇到的错误总结

✨个人主页&#xff1a; 不漫游-CSDN博客 前言 本篇文章是对平常练习遇到的问题总结&#xff0c;多吸取经验教训才能避免未来再犯~ Java语法部分 &#xff08;一&#xff09;多态 思考&#xff1a;这道题很明显考察的是多态的知识点&#xff0c;即一个对象可以被赋值给其父类…

11.盛最多水的容器 C++

一开始我最先想到的是暴力解法&#xff0c;就是两个循环嵌套依次遍历&#xff0c;所有情况都过一遍找出最大值&#xff0c;这样示例的结果虽然是正确的&#xff0c;但是超时。所以暴力解法行不通&#xff0c;双指针思考才是正道&#xff0c;双指针一般都是一边一个&#xff0c;…

拉链法解决哈希冲突

1.基本思想: 相同散列地址的记录链成一单链表,m个散列地址就设m个单链表,然后用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构. 例如:一组关键字为{19,14,23,1,68,20,84,27,55,11,10,79},散列函数为:Hash(key)key%13, 就会发现有些元素是同义词,比如14%131,1%131…

江开2024年春《计算机组成原理 060214》第4次计分作业参考答案

答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 单选题 1某计算机字长32位&#xff0c;其存储容量为4GB&am…

MySQL__锁

文章目录 &#x1f60a; 作者&#xff1a;Lion J &#x1f496; 主页&#xff1a; https://blog.csdn.net/weixin_69252724 &#x1f389; 主题&#xff1a; MySQL__锁&#xff09; ⏱️ 创作时间&#xff1a;2024年04月27日 ———————————————— 这里写目录…

Java高阶私房菜-JVM垃圾回收机制及算法原理探究

目录 垃圾回收机制 什么是垃圾回收机制 JVM的自动垃圾回收机制 垃圾回收机制的关键知识点 初步了解判断方法-引用计数法 GCRoot和可达性分析算法 什么是可达性分析算法 什么是GC Root 对象回收的关键知识点 标记对象可回收就一定会被回收吗&#xff1f; 可达性分析算…

阳光电源社招前程无忧智鼎题库及远程包过助攻需要重点考察什么?

阳光电源社招前程无忧智鼎题库及远程包过助攻需要重点考察什么&#xff1f; 结合长期服务大型国有企业校招工作的经验&#xff0c;我们总结出阳光电源社招笔试的典型模式&#xff1a;行政职业能力测试企业应知应会测试心理测评&#xff0c;综合考察候选人的政治素养、文化素养…

VC2022 + protobuf

google这是有私心啊&#xff0c;protobuf从某个版本开始&#xff0c;依赖了一个google自己推出的大型组件集&#xff0c;Abseil&#xff0c;有点类似于Boost了&#xff0c;业内用的人&#xff0c;从个人狭窄的圈子来说&#xff0c;应该是不多的&#xff0c;据说google的众贤用的…

【UnityRPG游戏制作】RPG项目的背包系统商城系统和BOSS大界面

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

【C++】简易二叉搜索树

目录 一、概念&#xff1a; 二、代码实现&#xff1a; 大致结构&#xff1a; 1、遍历&#xff1a; 2、insert 3、find 4、erase 三、总结&#xff1a; 一、概念&#xff1a; 二叉搜索树又称为二叉排序树&#xff0c;是一种具有特殊性质的二叉树&#xff0c;对于每一个节…

springboot+springsecurity+vue前后端分离权限管理系统

有任何问题联系本人QQ: 1205326040 1.介绍 优秀的权限管理系统&#xff0c;核心功能已经实现&#xff0c;采用springbootvue前后端分离开发&#xff0c;springsecurity实现权限控制&#xff0c;实现按钮级的权限管理&#xff0c;非常适合作为基础框架进行项目开发。 2.效果图…