本文基於1.12.13+hotfix.8版本源碼分析。緩存
點擊進入源碼,能夠看到Image繼承自StatefulWidget,那麼重點天然在State裏面。跟着生命週期走,能夠發如今didUpdateWidget中調用了這個方法:網絡
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 (_isListeningToStream) _imageStream.addListener(_getListener()); } ImageStreamListener _getListener([ImageLoadingBuilder loadingBuilder]) { loadingBuilder ??= widget.loadingBuilder; return ImageStreamListener( _handleImageFrame, onChunk: loadingBuilder == null ? null : _handleImageChunk, ); }
在這裏調用了image(ImageProvider)的resolve方法獲取到一個ImageStream,並給這個流設置了監聽器。從名字上,不難猜出這是個圖片數據流,在listener拿到數據後會調用setState(() {})方法進行rebuild,這裏再也不貼代碼。異步
在上面咱們看到了Image是須要接收圖片數據進行繪製的,那麼,這個數據是在哪裏解碼的?又是哪裏發送過來的?async
帶着疑問,咱們先進到ImageProvider的源碼,能夠發現其實這個類很是簡單,代碼量也很少,能夠看看resolve方法的核心部分:ide
Future<T> key; try { key = obtainKey(configuration); } catch (error, stackTrace) { handleError(error, stackTrace); return; } key.then<void>((T key) { obtainedKey = key; final ImageStreamCompleter completer = PaintingBinding.instance.imageCache.putIfAbsent( key, () => load(key, PaintingBinding.instance.instantiateImageCodec), onError: handleError, ); if (completer != null) { stream.setCompleter(completer); } }).catchError(handleError);
能夠看到,這裏會異步獲取到一個key,而後從管理在PaintingBinding中的緩存池查找圖片流。繼續看關鍵的obtainKey和load方法,去到定義的地方,能夠發現這兩個都是子類實現的。從註釋中能夠看到,obtainKey的功能就是根據傳入的ImageConfiguration生成一個獨一無二的key(廢話),而load方法則是將key轉換成爲一個ImageStreamCompleter對象並開始加載圖片。函數
那麼,咱們從最簡單的MemoryImage入手,首先看看obtainKey:源碼分析
@override Future<MemoryImage> obtainKey(ImageConfiguration configuration) { return SynchronousFuture<MemoryImage>(this); }
能夠看到,就只是把本身包了一層再返回,並無什麼特殊。接着看load:ui
@override ImageStreamCompleter load(MemoryImage key, DecoderCallback decode) { return MultiFrameImageStreamCompleter( codec: _loadAsync(key, decode), scale: key.scale, ); } Future<ui.Codec> _loadAsync(MemoryImage key, DecoderCallback decode) { assert(key == this); return decode(bytes); }
一樣很是簡單,就是建立了一個ImageStreamCompleter的子類對象,同時傳入了一個包裝瞭解碼器的Future(這個解碼器是PaintingBinding.instance.instantiateImageCodec,內部調用native方法進行圖片解碼)。this
看到這裏,相信基本有猜測了,數據和解碼器都提供了,看來ImageStreamCompleter就是咱們要看的數據源提供者。url
廢話很少說,直接看MultiFrameImageStreamCompleter,能夠看到直接在構造函數中獲取codec對象,在獲取到之後就會去獲取解碼數據,下面是簡化的代碼片斷:
// 構造函數中獲取codec codec.then<void>(_handleCodecReady, onError: (dynamic error, StackTrace stack) {// 略}); void _handleCodecReady(ui.Codec codec) { _codec = codec; assert(_codec != null); if (hasListeners) { // 拿到codec以後解碼數據 _decodeNextFrameAndSchedule(); } } Future<void> _decodeNextFrameAndSchedule() async { try { _nextFrame = await _codec.getNextFrame(); } catch (exception, stack) { // 略 return; } if (_codec.frameCount == 1) { // 發送數據 _emitFrame(ImageInfo(image: _nextFrame.image, scale: _scale)); return; } _scheduleAppFrame(); }
看到這裏,終於找到了發送數據的地方,_emitFrame裏面會調用setImage,然後在setImage中會找到listener並將數據發送,而listener就是widgets.Image註冊的監聽器。
看完了加載流程,咱們看看緩存池的緩存邏輯,回到ImageProvider的resolve方法,這裏有個關鍵點,傳給PaintingBinding的是個建立方法,而非實體。進入其源碼能夠看到是先檢測cache中是否存在該對象,存在則直接返回,不存在纔會調用load方法進行建立:
final _CachedImage image = _cache.remove(key); if (image != null) { // 有緩存就直接返回 _cache[key] = image; return image.completer; } try { // 沒找到緩存就調外面傳入的loader()進行建立 result = loader(); } // catch部分省略
而且,在剛建立時緩存中的對象是個PendingImage,這東西能夠理解爲相似一個佔位符的做用,當圖片數據加載完畢後才替換成實際數據對象CacheImage:
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(); } // 這裏建立了一個PendingImage插入緩存 if (maximumSize > 0 && maximumSizeBytes > 0) { final ImageStreamListener streamListener = ImageStreamListener(listener); _pendingImages[key] = _PendingImage(result, streamListener); // 監聽加載狀態,result就是ImageStreamCompleter result.addListener(streamListener); }
看完最基本的圖片數據加載,接下來看看NetworkImage如何加載網絡圖片。看核心的load方法:
ImageStreamCompleter load(image_provider.NetworkImage key, image_provider.DecoderCallback decode) { final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>(); return MultiFrameImageStreamCompleter( // 關鍵點1,加載、解析數據 codec: _loadAsync(key, chunkEvents, decode), // 關鍵點2,分塊下載事件流傳給completer用 chunkEvents: chunkEvents.stream, scale: key.scale, ); }
直接進入關鍵方法,看NetworkImage的_loadAsync方法:
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) // 能夠看到,圖片下載失敗是會拋異常的 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); } finally { chunkEvents.close(); } }
這裏有2個點:
(1)經過HttpClient進行圖片下載,下載失敗或者數據爲空都會拋異常,這裏要作好異常處理。另外,從上面的圖片緩存邏輯能夠看到,flutter默認是隻有內存緩存的,磁盤緩存須要本身處理,能夠在這裏入手處理;
(2)經過consolidateHttpClientResponseBytes接收數據,並將下載進度轉成ImageChunkEvent發送出去。能夠看看MultiFrameImageStreamCompleter對ImageChunkEvent的處理:
if (chunkEvents != null) { chunkEvents.listen( (ImageChunkEvent event) { if (hasListeners) { // 把這個事件傳遞給ImageStreamListener的onChunk方法 final List<ImageChunkListener> localListeners = _listeners .map<ImageChunkListener>((ImageStreamListener listener) => listener.onChunk) .where((ImageChunkListener chunkListener) => chunkListener != null) .toList(); for (ImageChunkListener listener in localListeners) { listener(event); } } } ); }
順着_listeners的來源,一路往上找,最後能夠看到onChunk方法是這裏傳進來的:
ImageStreamListener _getListener([ImageLoadingBuilder loadingBuilder]) { loadingBuilder ??= widget.loadingBuilder; return ImageStreamListener( _handleImageFrame, onChunk: loadingBuilder == null ? null : _handleImageChunk, ); }
widget.loadingBuilder即自定義loading狀態的方法。