欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Flutter系統(tǒng)網(wǎng)絡(luò)圖片加載流程解析

 更新時(shí)間:2022年05月23日 11:18:49   作者:ijinfeng  
這篇文章主要介紹了Flutter系統(tǒng)網(wǎng)絡(luò)圖片加載流程,從構(gòu)造函數(shù)開始說(shuō)起,我們以最簡(jiǎn)單的調(diào)用方式舉例,當(dāng)我們使用Image.network(imageUrl)這種方式來(lái)顯示圖片時(shí),Image組件內(nèi)部image屬性就會(huì)被賦值NetworkImage,具體操作步驟跟隨小編一起看看吧

Flutter原生支持在Image組件上顯示網(wǎng)絡(luò)圖片,最簡(jiǎn)單的使用方式如下,調(diào)用Image的命名構(gòu)造方法Image.network即可實(shí)現(xiàn)網(wǎng)絡(luò)圖片的下載顯示。

Widget image = Image.network(imageUrl);

那么,它內(nèi)部是如何實(shí)現(xiàn)的呢?是否有做緩存處理或其他優(yōu)化操作呢?帶著疑問(wèn),我們一起來(lái)看下它的底層究竟是如何實(shí)現(xiàn)的。

一、從構(gòu)造函數(shù)開始

我們以最簡(jiǎn)單的調(diào)用方式舉例,當(dāng)我們使用Image.network(imageUrl)這種方式來(lái)顯示圖片時(shí),Image組件內(nèi)部image屬性就會(huì)被賦值NetworkImage

// 此為簡(jiǎn)化過(guò)的Image組件類結(jié)構(gòu)
class Image extends StatefulWidget {
	Image.network(
    String src,
	) : image = NetworkImage(src);
	
// 圖片數(shù)據(jù)處理的基類
	final ImageProvider image;
}

這里引出了一個(gè)類叫NetworkImage,它是ImageProvider的子類,專門實(shí)現(xiàn)網(wǎng)絡(luò)圖片的下載和解析邏輯。當(dāng)然你直接點(diǎn)進(jìn)去看到的其實(shí)是個(gè)抽象類,并不是真正實(shí)現(xiàn)下載邏輯的地方,真正實(shí)現(xiàn)網(wǎng)絡(luò)圖片下載解析的在'_network_image_io.dart’這個(gè)文件下。構(gòu)造函數(shù)知道這些就夠了。接下來(lái)就看Image是在何時(shí)觸發(fā)網(wǎng)絡(luò)圖片的下載的。

二、圖片下載入口

Image是一個(gè)StatefulWidget,它又一個(gè)對(duì)應(yīng)的State_ImageState。在這個(gè)_ImageState的生命周期中,控制著圖片的下載過(guò)程。

State的生命周期可以簡(jiǎn)單的分為:構(gòu)造函數(shù) → initState → didChangeDependencies → build

因此,我們順著這個(gè)順序找,很快看到一個(gè)可疑的地方,didChangeDependencies中的_resolveImage方法。而TickerMode則是用于控制動(dòng)畫的,在這里被用于判斷是否禁用了動(dòng)畫。關(guān)于TickerMode的相關(guān)介紹,可以看下這篇文章

// 完整源碼
@override
  void didChangeDependencies() {
    _updateInvertColors();
// 處理圖片的入口
    _resolveImage();

// 當(dāng)動(dòng)畫被禁用時(shí),圖片也是無(wú)法顯示的,這個(gè)
    if (TickerMode.of(context))
// 添加圖片流處理的監(jiān)聽
      _listenToStream();
    else
      _stopListeningToStream(keepStreamAlive: true);

    super.didChangeDependencies();
  }

我們進(jìn)入到_resolveImage方法中去。

void _resolveImage() {
// ScrollAwareImageProvider包裝了我們的NetworkImage
    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,
      ));
    assert(newStream != null);
// 更新圖片流
    _updateSourceStream(newStream);
  }

_resolveImage方法就做了三件事。

1、用ScrollAwareImageProvider包裝了NetworkImage

2、創(chuàng)建圖片流對(duì)象ImageStream

3、更新圖片流

2.1、ScrollAwareImageProvider

ScrollAwareImageProvider也是ImageProvider的子類,它的作用很簡(jiǎn)單,就是防止在快速滑動(dòng)的時(shí)候加載圖片,當(dāng)存在快速滑動(dòng)時(shí),會(huì)將解析圖片的工作放到下一幀處理。至于具體如何實(shí)現(xiàn),我們放在后面再提。

2.2、ImageConfiguration

ImageConfiguration由方法createLocalImageConfiguration創(chuàng)建,保存了圖片的基本配置信息,如Bundle,屏幕項(xiàng)目比devicePixelRatio,本地化local,尺寸size,平臺(tái)platform等。

2.3、ImageStream

表示一個(gè)圖片流,可以添加觀察者ImageStreamCompleter來(lái)監(jiān)聽圖片是否處理完成。一個(gè)圖片流可以添加多個(gè)觀察者。

ImageStreamproviderresolve方法調(diào)用后創(chuàng)建。通過(guò)源碼可知,此處的provider就是ScrollAwareImageProvider對(duì)象。但是它內(nèi)部并沒(méi)有實(shí)現(xiàn)resolve方法,因此此處調(diào)用的是父類ImageProviderresolve方法。

三、圖片流和Key

以下代碼截取自ImageProvider,并且刪減了無(wú)關(guān)代碼。

ImageStream resolve(ImageConfiguration configuration) {
// 創(chuàng)建流,這里直接調(diào)用了ImageStream的構(gòu)造函數(shù),并沒(méi)有用到configuration
    final ImageStream stream = createStream(configuration);
// 關(guān)鍵在這里,這里會(huì)根據(jù)configuration創(chuàng)建一個(gè)唯一key
    _createErrorHandlerAndKey(
      configuration,
// 成功的回調(diào)
      (T key, ImageErrorListener errorHandler) {
        resolveStreamForKey(configuration, stream, key, errorHandler);
      },
// 下面是錯(cuò)誤回調(diào),可以不關(guān)注
      (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;
        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;
  }

resolve方法的作用是創(chuàng)建圖片流對(duì)象ImageStream,并根據(jù)傳入的圖片配置信息configuration,創(chuàng)建對(duì)應(yīng)的Key,這個(gè)Key用于圖片緩存。

那么這個(gè)key到底是怎么創(chuàng)建的呢,我們進(jìn)入到_createErrorHandlerAndKey方法中查看。關(guān)鍵代碼如下,已刪除無(wú)關(guān)代碼。

Future<T> key;
      try {
        key = obtainKey(configuration);
      } catch (error, stackTrace) {
        handleError(error, stackTrace);
        return;
      }
      key.then<void>((T key) {
        obtainedKey = key;
        try {
          successCallback(key, handleError);
        } catch (error, stackTrace) {
          handleError(error, stackTrace);
        }
      }).catchError(handleError);

可以看到方法實(shí)現(xiàn)中調(diào)用了ImageProviderobtainKey方法,而這個(gè)方法在ImageProvider并沒(méi)有具體實(shí)現(xiàn),需要子類完成對(duì)應(yīng)的實(shí)現(xiàn)。

Future<T> obtainKey(ImageConfiguration configuration);

還記得上文的分析不,我們說(shuō)傳入的imageProvider實(shí)例是ScrollAwareImageProvider對(duì)象,因此對(duì)應(yīng)的實(shí)現(xiàn)也要到這個(gè)類中去查找。很快,我們找到obtainKey方法的實(shí)現(xiàn),可以看到它做了個(gè)透?jìng)鳎唧w是由它包裝的類也就是NetworkImage來(lái)實(shí)現(xiàn)的。

@override
  Future<T> obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration);

那么,我們就去NetworkImageobtainKey。

注意下真正的NetworkImage實(shí)現(xiàn)是在_network_image_io.dart文件下的。

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

到這,我們就知道了NetworkImagekeySynchronousFuture

獲取到key后的下一步就是調(diào)用_createErrorHandlerAndKey方法的successCallback回調(diào)。從而觸發(fā)了下一個(gè)流程resolveStreamForKey

_createErrorHandlerAndKey(
      configuration,
      (T key, ImageErrorListener errorHandler) {
// 拿到Key之后的回調(diào)
        resolveStreamForKey(configuration, stream, key, errorHandler);
      }
)

四、根據(jù)key來(lái)處理圖片流

還是回到子類ScrollAwareImageProvider中,它重寫了父類的resolveStreamForKey方法,前文提到,ScrollAwareImageProvider是用來(lái)防止列表在快速滑動(dòng)的時(shí)候來(lái)加載圖片的,那么它是如何實(shí)現(xiàn)的?我們就從resolveStreamForKey這個(gè)方法中來(lái)一探究竟。

// 以下代碼已去掉無(wú)關(guān)邏輯
@override
  void resolveStreamForKey(
    ImageConfiguration configuration,
    ImageStream stream,
    T key,
    ImageErrorListener handleError,
  ) {
// 滑動(dòng)過(guò)快
    if (Scrollable.recommendDeferredLoadingForContext(context.context!)) {
      SchedulerBinding.instance!.scheduleFrameCallback((_) {
// 放入下一幀再嘗試處理,如果下一幀還是過(guò)快,那么將一直被推遲
        scheduleMicrotask(() => resolveStreamForKey(configuration, stream, key, handleError));
      });
      return;
    }
// 當(dāng)前可以加載,那么透?jìng)鹘o包裝的imageProvider來(lái)處理,這里是NetworkImage
    imageProvider.resolveStreamForKey(configuration, stream, key, handleError);
  }

Scrollable用于滑動(dòng)組件,它有個(gè)方法叫recommendDeferredLoadingForContext,表示是否建議延遲加載。內(nèi)部最終是根據(jù)滑動(dòng)速度和當(dāng)前設(shè)備的最大物理尺寸的邊去比較,如果大于,表示速度過(guò)快,那么就建議延遲。具體邏輯在scroll_physics.dart文件下。這里不多做介紹。

一旦當(dāng)前應(yīng)用處于滑動(dòng)狀態(tài),并且速度過(guò)快,那么,圖片的加載將會(huì)被推遲到下一幀再進(jìn)行嘗試。因此我們說(shuō),當(dāng)處于快速滑動(dòng)時(shí),圖片是無(wú)法加載的。

當(dāng)判斷可以加載圖片時(shí),操作流將會(huì)被移交給被包裝類imageProvider,這里是NetworkImage來(lái)處理。但是,NetworkImage沒(méi)有實(shí)現(xiàn)resolveStreamForKey方法,因此最終還是跑到了ImageProvider類中的resolveStreamForKey方法下。

@protected
  void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) {
// 第一次進(jìn)來(lái)還沒(méi)有設(shè)置completer,因此不會(huì)進(jìn)入這個(gè)分支中
    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,
      () => load(key, PaintingBinding.instance!.instantiateImageCodec),
      onError: handleError,
    );
    if (completer != null) {
      stream.setCompleter(completer);
    }
  }

當(dāng)?shù)谝淮渭虞d網(wǎng)絡(luò)圖的時(shí)候,會(huì)直接走到下面這個(gè)邏輯中。這里涉及到一個(gè)很重要的類,ImageCache。它是做圖片緩存用的。

final ImageStreamCompleter? completer = PaintingBinding.instance!.imageCache!.putIfAbsent(
      key,
      () => load(key, PaintingBinding.instance!.instantiateImageCodec),
      onError: handleError,
    );

4.1、ImageCache

圖片緩存類,只做了內(nèi)存緩存。它由PaintingBinding持有,是一個(gè)單利。它的內(nèi)部通過(guò)三個(gè)Map來(lái)緩存圖片。

// 加載中的圖片
final Map<Object, _PendingImage> _pendingImages = <Object, _PendingImage>{};
// 緩存中的圖片
  final Map<Object, _CachedImage> _cache = <Object, _CachedImage>{};
// 正在使用的圖片
  final Map<Object, _LiveImage> _liveImages = <Object, _LiveImage>{}

從圖片緩存器中獲取圖片的邏輯集中在putIfAbsent方法中。以下代碼已經(jīng)去掉無(wú)關(guān)代碼。

ImageStreamCompleter? putIfAbsent(Object key, ImageStreamCompleter Function() loader, { ImageErrorListener? onError }) {
    TimelineTask? timelineTask;
    TimelineTask? listenerTask;
    ImageStreamCompleter? result = _pendingImages[key]?.completer;
// 正在加載,直接返回
    if (result != null) {
      return result;
    }
    // 這邊有個(gè)小知識(shí),dart中的Map是有順序的,因此利用這點(diǎn)可以實(shí)現(xiàn)LRU算法。
// 最近用到了這圖片,因此刪除對(duì)應(yīng)鍵值對(duì),并更新,就能讓它的位置處于前面
    final _CachedImage? image = _cache.remove(key);
    if (image != null) {
// 更新 _liveImages
      _trackLiveImage(
        key,
        image.completer,
        image.sizeBytes,
      );
      _cache[key] = image;
      return image.completer;
    }
    final _LiveImage? liveImage = _liveImages[key];
    if (liveImage != null) {
// 更新 _cache,這里還會(huì)根據(jù)最大緩存數(shù)量和大小來(lái)最限制
      _touch(
        key,
        _CachedImage(
          liveImage.completer,
          sizeBytes: liveImage.sizeBytes,
        ),
        timelineTask,
      );
      return liveImage.completer;
    }
// 加載流程,這是個(gè)回調(diào),由各ImageProvider子類來(lái)實(shí)現(xiàn)
    try {
      result = loader();
// 下載完更新 _liveImages
      _trackLiveImage(key, result, null);
    } catch (error, stackTrace) {
      if (onError != null) {
        onError(error, stackTrace);
        return null;
      } else {
        rethrow;
      }
    }
    bool listenedOnce = false;
    _PendingImage? untrackedPendingImage;
// 設(shè)置圖片加載監(jiān)聽,一旦加載完畢,那么會(huì)刪除_pendingImages下對(duì)應(yīng)的圖片,并移除監(jiān)聽。同時(shí)更新_cache和_liveImages
    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 (untrackedPendingImage == null) {
        _touch(key, image, listenerTask);
      } else {
        image.dispose();
      }
      final _PendingImage? pendingImage = untrackedPendingImage ?? _pendingImages.remove(key);
      if (pendingImage != null) {
        pendingImage.removeListener();
      }
      listenedOnce = true;
    }
// 設(shè)置加載監(jiān)聽,主要用來(lái)管理_pendingImages
    final ImageStreamListener streamListener = ImageStreamListener(listener);
    if (maximumSize > 0 && maximumSizeBytes > 0) {
      _pendingImages[key] = _PendingImage(result, streamListener);
    } else {
      untrackedPendingImage = _PendingImage(result, streamListener);
    }
    result.addListener(streamListener);
    return result;
  }

4.2、 load

一旦在ImageCache中找不到緩存的圖片,就會(huì)通過(guò)loader回調(diào)出來(lái),走真正的下載流程。

final ImageStreamCompleter? completer = PaintingBinding.instance!.imageCache!.putIfAbsent(
      key,
// 本地圖片找不到,需要去對(duì)應(yīng)的ImageProvider子類里實(shí)現(xiàn)加載邏輯
      () => load(key, PaintingBinding.instance!.instantiateImageCodec),
      onError: handleError,
    );

還是先看ScrollAwareImageProvider類,里面實(shí)現(xiàn)了load方法,并透?jìng)鹘o了NetworkImage來(lái)實(shí)現(xiàn)。

@override
  ImageStreamCompleter load(T key, DecoderCallback decode) => imageProvider.load(key, decode);

NetworkImage下,可以找到對(duì)應(yīng)的load方法實(shí)現(xiàn)。里面有個(gè)_loadAsync方法,它就是我們要找的圖片下載核心代碼。

@override
  ImageStreamCompleter load(image_provider.NetworkImage key, image_provider.DecoderCallback decode) {
    final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
// 多幀圖片流管理器
    return MultiFrameImageStreamCompleter(
// 核心異步加載邏輯
      codec: _loadAsync(key as NetworkImage, chunkEvents, decode),
      chunkEvents: chunkEvents.stream,
      scale: key.scale,
      debugLabel: key.url,
      informationCollector: () => <DiagnosticsNode>[
        DiagnosticsProperty<image_provider.ImageProvider>('Image provider', this),
        DiagnosticsProperty<image_provider.NetworkImage>('Image key', key),
      ],
    );
  }

五、圖片下載

饒了一大圈,終于來(lái)到了下載圖片的地方了。可以看到下載圖片的邏輯很簡(jiǎn)單,創(chuàng)建一個(gè)下載的http請(qǐng)求,設(shè)置header,下載圖片。一旦下載成功,就會(huì)通過(guò)decode這個(gè)回調(diào)將圖片的二進(jìn)制數(shù)據(jù)返回出去decode(bytes)

Future<ui.Codec> _loadAsync(
    NetworkImage key,
    StreamController<ImageChunkEvent> chunkEvents,
    image_provider.DecoderCallback decode,
  ) async {
    try {
      assert(key == this);
      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) {
        // The network may be only temporarily unavailable, or the file will be
        // added on the server later. Avoid having future calls to resolve
        // fail to check the network again.
        await response.drain<List<int>>(<int>[]);
        throw image_provider.NetworkImageLoadException(statusCode: response.statusCode, uri: resolved);
      }
      final Uint8List bytes = await consolidateHttpClientResponseBytes(
        response,
        onBytesReceived: (int cumulative, int? total) {
          chunkEvents.add(ImageChunkEvent(
            cumulativeBytesLoaded: cumulative,
            expectedTotalBytes: total,
          ));
        },
      );
      if (bytes.lengthInBytes == 0)
        throw Exception('NetworkImage is an empty file: $resolved');
      return decode(bytes);
    } catch (e) {
      // Depending on where the exception was thrown, the image cache may not
      // have had a chance to track the key in the cache at all.
      // Schedule a microtask to give the cache a chance to add the key.
      scheduleMicrotask(() {
        PaintingBinding.instance!.imageCache!.evict(key);
      });
      rethrow;
    } finally {
      chunkEvents.close();
    }
  }

而回調(diào)出去的這些二進(jìn)制數(shù)據(jù),是在MultiFrameImageStreamCompleter中被處理的。MultiFrameImageStreamCompleterImageStreamCompleter的子類,用于管理多幀圖片的加載。

MultiFrameImageStreamCompleter的構(gòu)造方法中,我們可以看到它對(duì)codec做了回調(diào)處理。而這個(gè)codec就是前面提到的_loadAsync異步方法。

MultiFrameImageStreamCompleter({
    required Future<ui.Codec> codec,
    required double scale,
    String? debugLabel,
    Stream<ImageChunkEvent>? chunkEvents,
    InformationCollector? informationCollector,
  }) : assert(codec != null),
       _informationCollector = informationCollector,
       _scale = scale {
    this.debugLabel = debugLabel;
// 這里處理了_loadAsync的回調(diào)
    codec.then<void>(_handleCodecRead);

_handleCodecRead方法中回判斷是否有觀察者,有就進(jìn)入解碼流程。

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

    if (hasListeners) {
// 存在觀察者,開始解碼
      _decodeNextFrameAndSchedule();
    }
  }

_decodeNextFrameAndSchedule方法可以看成是圖片的解碼方法,當(dāng)然實(shí)際解碼的地方位于更底層的Native。圖片解碼后會(huì)將信息保存在FrameInfo中,由_nextFrame持有。這里我們只考慮單幀圖片,不考慮gif圖。解碼后的信息會(huì)被封裝在ImageInfo中,其中image就是真正的圖片數(shù)據(jù)。并調(diào)用_emitFrame方法,更新圖片信息。而_emitFrame方法則主要是調(diào)用了setImage來(lái)通知觀察者更新。我們直接看setImage方法即可。

Future<void> _decodeNextFrameAndSchedule() async {
    _nextFrame?.image.dispose();
    _nextFrame = null;
    try {
// 解碼得到一幀圖片信息,保存在FrameInfo中
      _nextFrame = await _codec!.getNextFrame();
    } catch (exception, stack) {
      return;
    }
// 當(dāng)幀圖片就將數(shù)據(jù)封裝在ImageInfo中回調(diào)出去
    if (_codec!.frameCount == 1) {
      if (!hasListeners) {
        return;
      }
      _emitFrame(ImageInfo(
        image: _nextFrame!.image.clone(),
        scale: _scale,
        debugLabel: debugLabel,
      ));
      _nextFrame!.image.dispose();
      _nextFrame = null;
      return;
    }
// 多幀則繼續(xù)往下走
    _scheduleAppFrame();
  }

通過(guò)ImageStreamListener通知更新,刷新界面展示。

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 {
// 設(shè)置新圖篇,通知更新界面展示
        listener.onImage(image.clone(), false);
      } catch (exception, stack) {
        reportError(
          context: ErrorDescription('by an image listener'),
          exception: exception,
          stack: stack,
        );
      }
    }
  }

說(shuō)到這里,我們好像還沒(méi)提到過(guò)什么時(shí)候設(shè)置的觀察者,好,我們?cè)俅位氐阶畛醯娜肟冢?code>_ImageState組件的didChangeDependencies方法中。

六、添加觀察者實(shí)現(xiàn)界面更新

這個(gè)觀察者就是通過(guò)_listenToStream方法添加的。

@override
  void didChangeDependencies() {
    _updateInvertColors();
    _resolveImage();

    if (TickerMode.of(context))
// 添加觀察者
      _listenToStream();
    else
      _stopListeningToStream(keepStreamAlive: true);

    super.didChangeDependencies();
  }

并且在創(chuàng)建觀察者ImageStreamListener的時(shí)候,設(shè)置了onImage的回調(diào)。

// 這里是獲取觀察者的入口
ImageStreamListener _getListener({bool recreateListener = false}) {
    if(_imageStreamListener == null || recreateListener) {
      _lastException = null;
      _lastStack = null;
      _imageStreamListener = ImageStreamListener(
// 這個(gè)就是onImage的回調(diào)
        _handleImageFrame,
        onChunk: widget.loadingBuilder == null ? null : _handleImageChunk,
        onError: widget.errorBuilder != null || kDebugMode
            ? (Object error, StackTrace? stackTrace) {
                setState(() {
                  _lastException = error;
                  _lastStack = stackTrace;
                });
              }
            : null,
      );
    }
    return _imageStreamListener!;
  }

onImage的入?yún)⒈辉O(shè)置了_handleImageFrame,因此當(dāng)下載完圖片后調(diào)用的就是_handleImageFrame方法。

void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) {
    setState(() {
// 更新圖片信息,實(shí)現(xiàn)圖片加載
      _replaceImage(info: imageInfo);
      _loadingProgress = null;
      _lastException = null;
      _lastStack = null;
      _frameNumber = _frameNumber == null ? 0 : _frameNumber! + 1;
      _wasSynchronouslyLoaded = _wasSynchronouslyLoaded | synchronousCall;
    });
  }

void _replaceImage({required ImageInfo? info}) {
    _imageInfo?.dispose();
    _imageInfo = info;
  }

到此,圖片下載和更新的流程已經(jīng)都串起來(lái)了。下載完的圖片存放在ImageInfo中,在setState后,會(huì)被設(shè)置進(jìn)RawImage組件中實(shí)現(xiàn)渲染。

總結(jié)

網(wǎng)絡(luò)圖片的加載邏輯可以分為以下幾個(gè)步驟:

1、根據(jù)圖片類型,生成對(duì)應(yīng)的key

2、根據(jù)key去全局的ImageCache下查找圖片緩存,命中則直接返回刷新

3、圖片緩存沒(méi)有命中,調(diào)用Http去下載圖片

4、下載完圖片后,將圖片的二進(jìn)制數(shù)據(jù)回調(diào)出去觸發(fā)界面刷新,同時(shí)會(huì)做內(nèi)存緩存

5、在RawImage中顯示網(wǎng)絡(luò)圖片

到此這篇關(guān)于Flutter系統(tǒng)網(wǎng)絡(luò)圖片加載過(guò)程解析的文章就介紹到這了,更多相關(guān)Flutter圖片加載流程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論