Flutter系列之Image加載原理

一、前言

最近在做的項(xiàng)目中,總是用到Image組件,所以就了解了一下Image的源碼,順便記錄下來,和大家分享一下。
本文是基于1.12.13+hotfix.8的源碼,以加載網(wǎng)路圖片為例進(jìn)行解讀。畢竟自己還是個(gè)小白,如果有解讀不對(duì)的地方,歡迎指正。

二、Image

Image繼承了StatefulWidget,是用于顯示圖片的 Widget,最后通過內(nèi)部的 RenderImage 繪制。
先看看Image結(jié)構(gòu),以Image.network為例:


image.png

先簡單介紹一下這些類,后續(xù)我們會(huì)一一詳細(xì)介紹。

  • Image用來顯示圖片。
  • _ImageState處理生命周期,生成Widget。
  • ImageProvider用來加載圖片,生成key。
  • NetWorkImage是具體執(zhí)行下載的,將下載的圖片轉(zhuǎn)化成ui.Codec,然后由ImageStreamCompleter去處理。
  • ImageStreamCompleter用來逐幀解析圖片。
  • ImageStream是存儲(chǔ)加載結(jié)果監(jiān)聽器List的。
  • MultiFrameImageStreamCompleter是多幀圖片解析器。
  • ImageStreamListener 實(shí)際監(jiān)聽加載結(jié)果

下面我們開始看下源碼。

構(gòu)造函數(shù)
Image.network(
    String src,{
    Key key,
    @required this.image,
    this.frameBuilder,
    this.loadingBuilder,
    ...
    this.filterQuality = FilterQuality.low,
  }): image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, NetworkImage(src, scale: scale, headers: headers)),
   // ...
    super(key: key);

Image.network以命名構(gòu)造函數(shù)創(chuàng)建Image對(duì)象時(shí),會(huì)同時(shí)初始化實(shí)例變量image。

  • src:圖片的url
  • image:必選參數(shù),一個(gè)ImageProvide對(duì)象,圖片的提供者,在調(diào)用的時(shí)候已經(jīng)實(shí)例化,稍后會(huì)具體介紹。
//ImageProvider初始化
class ResizeImage extends ImageProvider<_SizeAwareCacheKey> {
   ...
  static ImageProvider<dynamic> resizeIfNeeded(int cacheWidth, int cacheHeight, ImageProvider<dynamic> provider) {
    if (cacheWidth != null || cacheHeight != null) {
      return ResizeImage(provider, width: cacheWidth, height: cacheHeight);
    }
    return provider;
  }
}
State

作為一個(gè)StatefulWidget,最重要的當(dāng)然是State了。

@override
  _ImageState createState() => _ImageState();

Image的主要構(gòu)成就是兩部分,\color{#FF0000}{ImageProvider}\color{#FF0000}{ImageState}
接下來我們分別介紹一下這兩部分。

三、_ImageState

Image是一個(gè)StatefulWidget,狀態(tài)由_ImageState控制。_ImageState繼承自State,其生命周期方法包括initState()、didChangeDependencies()、build()、dispose()、didUpdateWidget()等。我們先來看看_ImageState中都做了些什么。

成員變量
class _ImageState extends State<Image> with WidgetsBindingObserver {  
  ImageStream _imageStream; 
  ImageInfo _imageInfo;
  bool _isListeningToStream = false;
  ···
}
  • _imageStream
    處理Image Resource的,ImageStream里存儲(chǔ)著圖片加載完畢的監(jiān)聽回調(diào)
  • _imageInfo
    Image的數(shù)據(jù)源信息:width和height以及ui.Image。 將ImageInfo里的ui.Image設(shè)置給RawImage就可以展示了。RawImage就是我們真正渲染的對(duì)象
生命周期函數(shù)
  • initState
 @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);//監(jiān)聽生命周期
  }
  • didChangeDependencies
 @override
  void didChangeDependencies() {
    ...
    _resolveImage();

    if (TickerMode.of(context))
      _listenToStream();
    else
      _stopListeningToStream();

    super.didChangeDependencies();
  }

_resolveImage()方法是核心,我們來分析一下。

  void _resolveImage() {
    final ImageStream newStream =
      widget.image.resolve(createLocalImageConfiguration(
        context,
        size: widget.width != null && widget.height != null ? Size(widget.width, widget.height) : null,
      ));
    assert(newStream != null);
    _updateSourceStream(newStream);
  }

 void _updateSourceStream(ImageStream newStream) {
    if (_imageStream?.key == newStream?.key)
      return;

    if (_isListeningToStream)
      _imageStream.removeListener(_getListener());

    if (!widget.gaplessPlayback)
      setState(() { _imageInfo = null; });

    setState(() {
      _loadingProgress = null;
      _frameNumber = null;
      _wasSynchronouslyLoaded = false;
    });

    _imageStream = newStream;
    if (_isListeningToStream)
      _imageStream.addListener(_getListener());
  }

 ImageStreamListener _getListener([ImageLoadingBuilder loadingBuilder]) {
      loadingBuilder ??= widget.loadingBuilder;
      return ImageStreamListener(
        _handleImageFrame,
        onChunk: loadingBuilder == null ? null : _handleImageChunk,
    );
  }

1、 通過ImageProvider得到ImageStream 對(duì)象
2、 然后 _ImageState 利用 ImageStream 添加監(jiān)聽,等待圖片數(shù)據(jù)

  • didUpdateWidget
 @override
  void didUpdateWidget(Image oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (_isListeningToStream &&
        (widget.loadingBuilder == null) != (oldWidget.loadingBuilder == null)) {
      _imageStream.removeListener(_getListener(oldWidget.loadingBuilder));
      _imageStream.addListener(_getListener());
    }
    if (widget.image != oldWidget.image)
      _resolveImage();
  }
  • build
 @override
  Widget build(BuildContext context) {
    Widget result = RawImage(
         image: _imageInfo?.image,
         ...
    );

    if (!widget.excludeFromSemantics) {
      result = Semantics(
       ...
      );
    }
    ...
    return result;
  }

四、ImageProvider

ImageProvider是一個(gè)抽象類,提供圖片數(shù)據(jù)獲取和加載的的接口,NetworkImage 、AssetImage 等均實(shí)現(xiàn)了這個(gè)接口。
它主要有兩個(gè)功能:

  • 提供圖片數(shù)據(jù)源
  • 緩存圖片
abstract class ImageProvider<T> {
  //接收ImageConfiguration參數(shù),返回ImageStream-圖片數(shù)據(jù)流
  ImageStream resolve(ImageConfiguration configuration) {
   ...
  }
  //清除指定key對(duì)應(yīng)的圖片緩存
  Future<bool> evict({ ImageCache cache,ImageConfiguration configuration = ImageConfiguration.empty }) async {
   ...
  }
 //需要ImageProvider子類實(shí)現(xiàn),不同的ImageProvider對(duì)key的定義邏輯不同
  Future<T> obtainKey(ImageConfiguration configuration); 
 // 需ImageProvider子類實(shí)現(xiàn),加載圖片數(shù)據(jù)
  @protected
  ImageStreamCompleter load(T key); 
}
  • resolve
    獲取數(shù)據(jù)流
  • evict
    清除緩存
  • obtainKey
    配合實(shí)現(xiàn)圖片緩存
  • load
    加載圖片數(shù)據(jù)源

4.1 resolve方法解析

#ImageProvider
ImageStream resolve(ImageConfiguration configuration) {
  //1、創(chuàng)建圖片數(shù)據(jù)流
  final ImageStream stream = ImageStream();
  T obtainedKey; //
  //2、錯(cuò)誤處理
  Future<void> handleError(dynamic exception, StackTrace stack) async {
    ... 
    stream.setCompleter(imageCompleter);
    imageCompleter.setError(...);
  }
   //3、創(chuàng)建一個(gè)新Zone,用來處理發(fā)生的錯(cuò)誤,不干擾MainZone
    final Zone dangerZone = Zone.current.fork(
      specification: ZoneSpecification(
        handleUncaughtError: (Zone zone, ZoneDelegate delegate, Zone parent, Object error, StackTrace stackTrace) {
          handleError(error, stackTrace);
        }
      )
    );
    dangerZone.runGuarded(() {
      // 4、判斷是否有緩存的相關(guān)邏輯
      Future<T> key;
      try {
        // 5、生成key,后續(xù)會(huì)用此key判斷是否有緩存
        key = obtainKey(configuration);
      } catch (error, stackTrace) {
        handleError(error, stackTrace);
        return;
      }
      key.then<void>((T key) {
        // 6、緩存處理邏輯
        obtainedKey = key;
        final ImageStreamCompleter completer = PaintingBinding.instance.imageCache.putIfAbsent(
          key,
          () => load(key, PaintingBinding.instance.instantiateImageCodec),
          onError: handleError,
        );
        if (completer != null) {
          //7、stream設(shè)置ImageStreamCompleter對(duì)象
          stream.setCompleter(completer);
        }
      }).catchError(handleError);
    });
    return stream;
  }

這段代碼中,我們需要重點(diǎn)看四個(gè)點(diǎn),

  • ImageStream
  • ImageCache
  • obtainKey 方法
  • ImageStreamCompleter
ImageStream

存儲(chǔ)ImageStreamCompleter,監(jiān)聽圖片加載結(jié)果。

ImageCache

在resolve 方法中調(diào)用了PaintingBinding.instance.imageCache.putIfAbsent方法(注釋6處),這里的PaintingBinding.instance.imageCache 是 ImageCache的一個(gè)實(shí)例。PaintingBinding.instance和imageCache是單例的,所以說圖片緩存是項(xiàng)目全局的。

const int _kDefaultSize = 1000;// 最大緩存數(shù)量,默認(rèn)1000
const int _kDefaultSizeBytes = 100 << 20;   // 最大緩存容量,默認(rèn)100 MB
class ImageCache {
  // 正在加載中的圖片隊(duì)列
  final Map<Object, _PendingImage> _pendingImages = <Object, _PendingImage>{};
  // 緩存隊(duì)列
  final Map<Object, _CachedImage> _cache = <Object, _CachedImage>{};
  // 最大緩存數(shù)量,默認(rèn)1000
  int _maximumSize = _kDefaultSize;
  // 最大緩存容量,默認(rèn)100 MB
  int _maximumSizeBytes = _kDefaultSizeBytes;
  ... // 省略部分代碼
  // 清除全部緩存
  void clear() {
     ...
  }
  // 根據(jù)key清楚緩存
  bool evict(Object key) {
   // ...省略代碼
  }
  //重點(diǎn)方法
  // 參數(shù) key用來獲取緩存,loader()加載回調(diào)方法,onError加載失敗回調(diào)
  ImageStreamCompleter putIfAbsent(Object key, ImageStreamCompleter loader(), { ImageErrorListener onError }) {
   //_pendingImage 用于標(biāo)示該key的圖片處于加載中的狀態(tài) 
    ImageStreamCompleter result = _pendingImages[key]?.completer;
    // 圖片還未加載成功,直接返回
    if (result != null)
      return result;
    // 先移除緩存,拿到移除的緩存對(duì)象
    final _CachedImage image = _cache.remove(key);
    //把最近一次使用過的緩存在_map中
    if (image != null) {
      _cache[key] = image;
      return image.completer;
    }
   //沒有緩存,使用loader()方法加載
    try {
      result = loader();
    } catch (error, stackTrace) {
      ...
    }
    void listener(ImageInfo info, bool syncCall) {
      final int imageSize = info?.image == null ? 0 : info.image.height * info.image.width * 4;
      final _CachedImage image = _CachedImage(result, imageSize);
      // 緩存處理的邏輯
      if (maximumSizeBytes > 0 && imageSize > maximumSizeBytes) {
        _maximumSizeBytes = imageSize + 1000;
      }
      _currentSizeBytes += imageSize;
      final _PendingImage pendingImage = _pendingImages.remove(key);
      if (pendingImage != null) {
        pendingImage.removeListener();
      }

      _cache[key] = image;
      _checkCacheSize();
    }
    if (maximumSize > 0 && maximumSizeBytes > 0) {
      final ImageStreamListener streamListener = ImageStreamListener(listener);
      _pendingImages[key] = _PendingImage(result, streamListener);
      // Listener is removed in [_PendingImage.removeListener].
      result.addListener(streamListener);
    }
    return result;
  }

  // 當(dāng)超過緩存最大數(shù)量或最大緩存容量,調(diào)用此方法清理到緩存,保持著最大數(shù)量和容量
  void _checkCacheSize() {
   while (_currentSizeBytes > _maximumSizeBytes || _cache.length > _maximumSize) {
      final Object key = _cache.keys.first;
      final _CachedImage image = _cache[key];
      _currentSizeBytes -= image.sizeBytes;
      _cache.remove(key);
    }
    ... 
  }
}

putIfAbsent方法主要是先通過 key 判斷內(nèi)存中正在緩存的對(duì)象或者是否有緩存,如果有就返回該對(duì)象的ImageStreamCompleter ,否則就調(diào)用 loader 去加載并返回ImageStreamCompleter。
這里提醒大家兩個(gè)地方:

  • 圖片緩存是在內(nèi)存中,沒有進(jìn)行本地存儲(chǔ)。
  • 應(yīng)用生命周期內(nèi),如果緩存沒有超過上限,相同的圖片(key相同)只會(huì)被下載一次。
ImageStreamCompleter

putIfAbsent的返回值返回了ImageStreamCompleter,而resolve方法中,最后調(diào)用了ImageStream的setCompleter的方法,給ImageStream設(shè)置一個(gè)ImageStreamCompleter對(duì)象。

  #ImageStream
  void setCompleter(ImageStreamCompleter value) {
    assert(_completer == null);
    _completer = value;
    if (_listeners != null) {
      final List<ImageStreamListener> initialListeners = _listeners;
      _listeners = null;
      initialListeners.forEach(_completer.addListener);
    }
  }

ImageStreamCompleter是一個(gè)抽象類,定義了管理圖片加載過程的一些接口,Image Widget中正是通過它來監(jiān)聽圖片加載狀態(tài)的。每一個(gè)ImageStream對(duì)象只能設(shè)置一次,ImageStreamCompleter是為了輔助ImageStream解析和管理Image圖片幀的,并且判斷是否有初始化監(jiān)聽器,可以做一些初始化回調(diào)工作。

abstract class ImageStreamCompleter extends Diagnosticable {
  final List<_ImageListenerPair> _listeners = <_ImageListenerPair>[];
  ImageInfo _currentImage;
  FlutterErrorDetails _currentError;
  void addListener(ImageListener listener, { ImageErrorListener onError }) {...}
  void removeListener(ImageListener listener) {... }
  void reportError(...) {... }
  @protected
  void setImage(ImageInfo image) {
    _currentImage = image;
    if (_listeners.isEmpty)
      return;
    // Make a copy to allow for concurrent modification.
    final List<ImageStreamListener> localListeners = List<ImageStreamListener>.from(_listeners);
    for (ImageStreamListener listener in localListeners) {
      try {
        listener.onImage(image, false);
      } catch (exception, stack) {
        reportError(
         ...
        );
 }}}}

4.2 obtainKey

key是圖片緩存的一個(gè)唯一標(biāo)識(shí),也是判斷該圖片是否應(yīng)該被緩存的唯一條件。這個(gè)key就是ImageProvider.obtainKey()方法的返回值,不同類型的ImageProvider對(duì)key的定義邏輯會(huì)不同,所以此方法需要ImageProvider子類去重寫。我們以NetworkImage為例,看一下它的obtainKey()實(shí)現(xiàn):

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

其實(shí)就是創(chuàng)建一個(gè)future,然后將NetworkImage自身做為key返回。
那么又是如何判斷key是否相等的呢?

 #NetworkImage
 @override
  bool operator ==(dynamic other) {
    if (other.runtimeType != runtimeType)
      return false;
    final NetworkImage typedOther = other;
    return url == typedOther.url
        && scale == typedOther.scale;
  }

在NetworkImage中,是將url+ scale(縮放比例)作為緩存中的key。只有url和scale相等,才算是有緩存。也就是說如果兩張圖片的url或scale只要有一個(gè)不同,便會(huì)重新下載并分別緩存。

4.3 load(T key)方法解析

load()是ImageProvider加載圖片數(shù)據(jù)源的接口,不同ImageProvider的數(shù)據(jù)源加載方法不同,每個(gè)ImageProvider的子類必須實(shí)現(xiàn)它。比如NetworkImage類和AssetImage類,它們都是ImageProvider的子類,NetworkImage是從網(wǎng)絡(luò)來加載圖片數(shù)據(jù),AssetImage則是從最終的應(yīng)用包里來加載。
我們以NetworkImage為例,看看其load方法的實(shí)現(xiàn):

#NetworkImage
@override
ImageStreamCompleter load(image_provider.NetworkImage key) {

  final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();

  return MultiFrameImageStreamCompleter(
    codec: _loadAsync(key, chunkEvents), //調(diào)用_loadAsync
    chunkEvents: chunkEvents.stream,
    scale: key.scale,
    ... 
  );
}

MultiFrameImageStreamCompleter 是一個(gè)多幀圖片管理器,是ImageStreamCompleter的一個(gè)子類。
MultiFrameImageStreamCompleter 需要一個(gè)Future<ui.Codec>類型的參數(shù)——codec。Codec 是處理圖片編解碼的類的一個(gè)handler,是一個(gè)flutter engine API 的包裝類。圖片的編解碼的邏輯不是在Dart 代碼部分實(shí)現(xiàn),而是在flutter engine中實(shí)現(xiàn)的。

  MultiFrameImageStreamCompleter({
    @required Future<ui.Codec> codec,
    @required double scale,
    Stream<ImageChunkEvent> chunkEvents,
    InformationCollector informationCollector,
  }) : assert(codec != null),
       _informationCollector = informationCollector,
       _scale = scale {
    codec.then<void>(_handleCodecReady, onError: (dynamic error, StackTrace stack) {
      reportError(...);
    });
    if (chunkEvents != null) {
      chunkEvents.listen(
        (ImageChunkEvent event) {
          if (hasListeners) {
            // Make a copy to allow for concurrent modification.
            final List<ImageChunkListener> localListeners = _listeners
                .map<ImageChunkListener>((ImageStreamListener listener) => listener.onChunk)
                .where((ImageChunkListener chunkListener) => chunkListener != null)
                .toList();
            for (ImageChunkListener listener in localListeners) {
              listener(event);
            }
          }
        }, onError: (dynamic error, StackTrace stack) {//...},
      );
    }
  }

Codec類部分定義如下:

@pragma('vm:entry-point')
class Codec extends NativeFieldWrapperClass2 {
  // 此類由flutter engine創(chuàng)建,不應(yīng)該手動(dòng)實(shí)例化此類或直接繼承此類。
  @pragma('vm:entry-point')
  Codec._();
  /// 圖片中的幀數(shù)(動(dòng)態(tài)圖會(huì)有多幀)
  int get frameCount native 'Codec_frameCount';
  /// 動(dòng)畫重復(fù)的次數(shù),0 -只執(zhí)行一次,-1-循環(huán)執(zhí)行
  int get repetitionCount native 'Codec_repetitionCount';
  /// 獲取下一個(gè)動(dòng)畫幀
  Future<FrameInfo> getNextFrame() {
    return _futurize(_getNextFrame);
  }
  String _getNextFrame(_Callback<FrameInfo> callback) native 'Codec_getNextFrame';
}

我們可以看到Codec最終的結(jié)果是一個(gè)或多個(gè)(動(dòng)圖)幀,而這些幀最終會(huì)繪制到屏幕上。
MultiFrameImageStreamCompleter 的 codec參數(shù)值為_loadAsync方法的返回值,我們繼續(xù)看_loadAsync方法的實(shí)現(xiàn):

Future<ui.Codec> _loadAsync(
    NetworkImage key,
    StreamController<ImageChunkEvent> chunkEvents,
  ) async {
    try {
      //下載圖片
      final Uri resolved = Uri.base.resolve(key.url);
      final HttpClientRequest request = await _httpClient.getUrl(resolved);
      headers?.forEach((String name, String value) {
        request.headers.add(name, value);
      });
      final HttpClientResponse response = await request.close();
      if (response.statusCode != HttpStatus.ok)
        throw Exception(...);
      // 接收?qǐng)D片數(shù)據(jù) 
      final Uint8List bytes = await consolidateHttpClientResponseBytes(
        response,
        onBytesReceived: (int cumulative, int total) {
          chunkEvents.add(ImageChunkEvent(
            // 下載進(jìn)度
            cumulativeBytesLoaded: cumulative,
            expectedTotalBytes: total,
          ));
        },
      );
      if (bytes.lengthInBytes == 0)
        throw Exception('NetworkImage is an empty file: $resolved');
      // 對(duì)圖片數(shù)據(jù)進(jìn)行解碼
      return decode(bytes);//PaintingBinding.instance.instantiateImageCodec(bytes)
    } finally {
      chunkEvents.close();
    }
  }

_loadAsync方法主要做了兩件事:

  • 下載圖片。
  • 對(duì)下載的圖片數(shù)據(jù)進(jìn)行解碼。

下載邏輯比較簡單:通過HttpClient從網(wǎng)上下載圖片,另外下載請(qǐng)求會(huì)設(shè)置一些自定義的header,開發(fā)者可以通過NetworkImage的headers命名參數(shù)來傳遞。

在圖片下載完成后調(diào)用了PaintingBinding.instance.instantiateImageCodec(bytes)對(duì)圖片進(jìn)行解碼,instantiateImageCodec(...)也是一個(gè)Native API的包裝,會(huì)調(diào)用Flutter engine的instantiateImageCodec方法,源碼如下:

String _instantiateImageCodec(Uint8List list, _Callback<Codec> callback, _ImageInfo imageInfo, int targetWidth, int targetHeight)
  native 'instantiateImageCodec';

codec的異步方法執(zhí)行完成后會(huì)調(diào)用_handleCodecReady函數(shù)。

//MultiFrameImageStreamCompleter
  void _handleCodecReady(ui.Codec codec) {
    _codec = codec;
    assert(_codec != null);

    if (hasListeners) {
      _decodeNextFrameAndSchedule();
    }
  }

該方法將codec對(duì)象保存起來,然后解碼圖片幀

#MultiFrameImageStreamCompleter
  Future<void> _decodeNextFrameAndSchedule() async {
    try {
      _nextFrame = await _codec.getNextFrame();
    } catch (exception, stack) {
      reportError(...);
      return;
    }
    if (_codec.frameCount == 1) {
      // This is not an animated image, just return it and don't schedule more frames.
      _emitFrame(ImageInfo(image: _nextFrame.image, scale: _scale));
      return;
    }
    _scheduleAppFrame();
  }

如果只有一幀,則執(zhí)行_emitFrame函數(shù)。從幀數(shù)據(jù)中拿到圖片幀對(duì)象根據(jù)縮放比例創(chuàng)建ImageInfo對(duì)象,然后設(shè)置顯示的圖片信息

#MultiFrameImageStreamCompleter
  void _emitFrame(ImageInfo imageInfo) {
    setImage(imageInfo);
    _framesEmitted += 1;
  }

#ImageStreamCompleter
@protected
  void setImage(ImageInfo image) {
    _currentImage = image;
    if (_listeners.isEmpty)
      return;
    // Make a copy to allow for concurrent modification.
    final List<ImageStreamListener> localListeners =
        List<ImageStreamListener>.from(_listeners);
    for (ImageStreamListener listener in localListeners) {
      try {
        listener.onImage(image, false);
      } catch (exception, stack) {
        reportError(...);
      }
    }
  }

五、Image加載流程總結(jié)

整個(gè)流程大概如下:

  • Image構(gòu)造函數(shù)先實(shí)例化一個(gè)ImageProvider
  • 在_ImageState的didChangeDependencies方法中通過ImageProvider的resolve方法創(chuàng)建ImageStream對(duì)象,并關(guān)聯(lián)一個(gè)ImageStreamCompleter,之后添加用于監(jiān)聽加載流程的ImageStreamListener1。
  • 在獲取ImageStreamCompleter的過程中,如果有緩存,就從緩存中獲取ImageStreamCompleter,如果沒有緩存,就調(diào)用ImageProvider的load方法去加載圖片并返回ImageStreamCompleter對(duì)象,然后給ImageStreamCompleter添加ImageStreamListener2。
  • load方法執(zhí)行中會(huì)通過 http 下載圖片,再經(jīng)過PaintingBinding 編碼轉(zhuǎn)化后,得到ui.Codec可繪制對(duì)象,然后MultiFrameImageStreamCompleter調(diào)用_handleCodecReady方法把ui.Codec封裝成ImageInfo。
  • 接著MultiFrameImageStreamCompleter會(huì)調(diào)用setImage方法,此方法觸發(fā)加載監(jiān)聽ImageStreamListener1和ImageStreamListener2。
  • ImageStreamListener1回調(diào)到_ImageState,將ImageInfo保存, ImageStreamListener2的回調(diào)會(huì)把Image緩存下來。
  • _ImageState的 build方法中的會(huì)根據(jù)ImageInfo構(gòu)建一個(gè) RawImage 對(duì)象。
  • 最后 RawImage中的 RenderImage 通過paint方法繪制Widget。

六、如何減輕圖片帶來的內(nèi)存壓力?

//修改緩存最大值
const int _kDefaultSize = 100;
const int _kDefaultSizeBytes = 50 << 20;  

//退出頁面清除緩存
  @override
  void dispose() {
    PaintingBinding.instance.imageCache.clear();
    super.dispose();
  }

七、添加磁盤緩存

上面我們已經(jīng)知道,Image只有內(nèi)存緩存,沒有本地緩存。那么我們?nèi)绾翁砑颖镜鼐彺婺兀科鋵?shí)只需要改進(jìn)NetWorkImage的_loadAsync方法。

Future<ui.Codec> _loadAsync(NetworkImage key,StreamController<ImageChunkEvent> chunkEvents, image_provider.DecoderCallback decode,) async {
    try {
      assert(key == this);
   //--------新增代碼1 begin--------------
   // 判斷是否有本地緩存
    final Uint8List cacheImageBytes = await ImageCacheUtil.getImageBytes(key.url);
    if(cacheImageBytes != null) {
      return decode(cacheImageBytes);
    }
   //--------新增代碼1 end--------------

    //...省略
      if (bytes.lengthInBytes == 0)
        throw Exception('NetworkImage is an empty file: $resolved');

        //--------新增代碼2 begin--------------
       // 緩存圖片數(shù)據(jù)到本地,需要定制具體的緩存策略
       await ImageCacheUtil.saveImageBytesToLocal(key.url, bytes);
       //--------新增代碼2 end--------------

      return decode(bytes);
    } finally {
      chunkEvents.close();
    }
  }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。