• 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,
           );
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    我们看到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,
          );
        }
       
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    从上面的代码来看,ImageHandler也是个马甲,最终还是调用framework类里Image类。

    那我们来看看Image类做了什么
    在didChangeDependencies方法中,我们看到了一个比较重要的方法_resolveImage();

     void _resolveImage() {
        final ScrollAwareImageProvider provider = ScrollAwareImageProvider(
          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!;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    从源码可以看出_resolveImage方法主要做的是

    将ImageProvider 和 ImageStream产生了关联。然后注册一个图片流监听事件
    就是ImageStreamListener这个类。当图片数据获取到之后就会通过监听回调,然后setState将图片渲染出来。
    
    • 1
    • 2

    在 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);
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    从上面的源码可以看出,新建了一个ImageStream对象和图片的key。然后将生成的ImageStreamCompleter对象存入到PaintingBinding的imageCache

    ImageCache类里的三个变量

    final Map _pendingImages = {};

    final Map _cache = {};

    final Map _liveImages = {};

    _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: {'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: {
              '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;
      }
      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    上面的那个方法就是将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);
              }
              
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这部分代码才是真正加载图片数据的处理
    loadImage
    load
    loadBuffer
    这三个方法都是ImageProvider的方法,不同的子类有不同的实现
    ImageProvider的子类有

    FileImage
    MemoryImage
    ExactAssetImage
    NetworkImage
    
    • 1
    • 2
    • 3
    • 4

    还可以自己继承ImageProvider,就拿CacheManagerImage这个框架来说,它的CachedNetworkImageProvider就是继承了ImageProvider类。

    class CachedNetworkImageProvider
    extends ImageProvider {}
    
    • 1
    • 2

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

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

    
      @override
      ImageStreamCompleter loadBuffer(image_provider.CachedNetworkImageProvider key,
          DecoderBufferCallback decode) {
        final chunkEvents = StreamController();
        return MultiImageStreamCompleter(
          codec: _loadBufferAsync(key, chunkEvents, decode),
          chunkEvents: chunkEvents.stream,
          scale: key.scale,
          informationCollector: () sync* {
            yield DiagnosticsProperty(
              'Image provider: $this \n Image key: $key',
              this,
              style: DiagnosticsTreeStyle.errorProperty,
            );
          },
        );
      }
    
    MultiImageStreamCompleter这个类主要继承了ImageStreamCompleter
    
    接着看_loadBufferAsync方法
    
      Stream _loadBufferAsync(
        image_provider.CachedNetworkImageProvider key,
        StreamController 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 loadBufferAsync(
          String url,
          String? cacheKey,
          StreamController chunkEvents,
          DecoderBufferCallback decode,
          BaseCacheManager cacheManager,
          int? maxHeight,
          int? maxWidth,
          Map? 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 _load(
        String url,
        String? cacheKey,
        StreamController chunkEvents,
        _FileDecoderCallback decode,
        BaseCacheManager cacheManager,
        int? maxHeight,
        int? maxWidth,
        Map? 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();
        }
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133

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

    cacheManager.getFileStream(url,
                    maxHeight: maxHeight,
                    maxWidth: maxWidth,
                    withProgress: true,
                    headers: headers,
                    key: cacheKey)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    Stream getFileStream(String url,
            {String? key, Map? headers, bool withProgress = false}) {
          key ??= url;
          final streamController = StreamController();
          _pushFileToStream(streamController, url, key, headers, withProgress);
          return streamController.stream;
        }
    
    
       Future _pushFileToStream(
            StreamController streamController,
            String url,
            String? key,
            Map? 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();
          }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

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

      @override
      Future getFileFromCache(String key,
              {bool ignoreMemCache = false}) =>
          _store.getFile(key, ignoreMemCache: ignoreMemCache);
    
     Future 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,
        );
      }
      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    首先从缓存里面拿文件数据 cacheFile = await getFileFromCache(key);

    如果有的话直接添加到流控制器中 streamController.add(cacheFile);

    如果缓存对象是空的话,就调用_webHelper.downloadFile(url, key: key, authHeaders: headers)从网络获取数据,然后再存入文件缓存里。

     Stream _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);
          }
        });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    这段代码主要职责是从网络获取的文件对象,先移除旧文件,然后缓存到本地文件上。

    三、对图片数据进行解码

    获取到图片对象后,需要对图片进行解码,接下来就是图片的解码操作

     _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 Function(ui.ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling});
    
    
     (bytes) async {
              final buffer = await ImmutableBuffer.fromUint8List(bytes);
              return decode(buffer);
            },
    
      这段代码主要是先将Uint8List 转换成ImmutableBuffer类型。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    我们看一下CachedNetworkImageProvider类里的loadBuffer方法,这个类是继承ImageProvider。
    然后实现了ImageProvider的loadBuffer方法

      @override
      ImageStreamCompleter loadBuffer(image_provider.CachedNetworkImageProvider key,
          DecoderBufferCallback decode) {
        final chunkEvents = StreamController();
        return MultiImageStreamCompleter(
          codec: _loadBufferAsync(key, chunkEvents, decode),
          chunkEvents: chunkEvents.stream,
          scale: key.scale,
          informationCollector: () sync* {
            yield DiagnosticsProperty(
              'Image provider: $this \n Image key: $key',
              this,
              style: DiagnosticsTreeStyle.errorProperty,
            );
          },
        );
      }
    
    
      MultiFrameImageStreamCompleter({
        required Future codec,
        required double scale,
        String? debugLabel,
        Stream? chunkEvents,
        InformationCollector? informationCollector,
      }) : _informationCollector = informationCollector,
           _scale = scale {
        this.debugLabel = debugLabel;
        codec.then(_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,
              );
            },
          );
        }
      }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    我们看一下这段代码

    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 _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 localListeners =
            List.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,
            );
          }
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    从上面可以清晰的看到,最终调用了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);
          }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    这里的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;
         }
         
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    我们知道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,
      );
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
  • 相关阅读:
    Flink之KeyedState
    Flink-常用算子、自定义函数以及分区策略
    linux之美
    基于SpringBoot的“农机电招平台”的设计与实现(源码+数据库+文档+PPT)
    Pod 生命周期、重启策略、健康检查、服务可用性检查
    网络层详解
    LeetCode ❀ 35.搜索插入位置 / python实现二分法
    config设置训练参数 - image_resizer
    行为型设计模式---访问者模式
    SQ4840EY-T1_GE3具有低导通电阻和低电压降 汽车级 N沟道功率MOSFET
  • 原文地址:https://blog.csdn.net/hjjdehao/article/details/138165359