首頁 > 軟體

Flutter載入圖片流程之ImageProvider原始碼範例解析

2023-08-28 18:05:43

載入網路圖片

Image.network()是Flutter提供的一種從網路上載入圖片的方法,它可以從指定的URL載入圖片,並在載入完成後將其顯示在應用程式中。本節內容,我們從原始碼出發,探討下圖片的載入流程。

ImageProvider

ImageProvider是Flutter中一個抽象類,它定義了一種用於載入圖片的通用介面,可以用於載入本地圖片、網路圖片等各種型別的圖片。

ImageProvider類包含兩個核心方法:obtainKeyloadBuffer

resolve

/// Resolves this image provider using the given `configuration`, returning
/// an [ImageStream].
///
/// This is the public entry-point of the [ImageProvider] class hierarchy.
///
/// Subclasses should implement [obtainKey] and [load], which are used by this
/// method. If they need to change the implementation of [ImageStream] used,
/// they should override [createStream]. If they need to manage the actual
/// resolution of the image, they should override [resolveStreamForKey].
///
/// See the Lifecycle documentation on [ImageProvider] for more information.
@nonVirtual
ImageStream resolve(ImageConfiguration configuration) {
  assert(configuration != null);
  final ImageStream stream = createStream(configuration);
  // Load the key (potentially asynchronously), set up an error handling zone,
  // and call resolveStreamForKey.
  _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;
      assert(() {
        collector = () => <DiagnosticsNode>[          DiagnosticsProperty<ImageProvider>('Image provider', this),          DiagnosticsProperty<ImageConfiguration>('Image configuration', configuration),          DiagnosticsProperty<T>('Image key', key, defaultValue: null),        ];
        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;
}

根據檔案解釋,我們可以瞭解到以下幾點:

1、使用給定的`configuration`解析該圖片提供器,返回一個 [ImageStream]。  

2、這是 [ImageProvider] 類層次結構的公共入口點。

3、子類應該實現 [obtainKey] 和 [load] 方法,這兩個方法將被該方法使用。 

4、如果子類需要更改使用的 [ImageStream] 的實現,則應該重寫 [createStream] 方法。 

5、 如果子類需要管理實際的影象解析度,則應該重寫 [resolveStreamForKey] 方法。 

閱讀resolve方法的實現。我們可以知道:

1、它使用給定的configuration引數建立一個ImageStream物件(createStream)。然後呼叫_createErrorHandlerAndKey方法,該方法會非同步獲取圖片的唯一識別符號,並設定一個錯誤處理區域,以防圖片載入過程中發生錯誤。

2、如果獲取唯一識別符號的過程中出現異常,則會將錯誤資訊封裝成一個_ErrorImageCompleter物件,並將其設定為ImageStreamcompleter屬性,表示圖片載入失敗。

3、如果唯一識別符號獲取成功,則會呼叫resolveStreamForKey方法來解析圖片,並將圖片資料儲存到ImageStream物件中,供後續使用。

4、該方法是ImageProvider類層次結構的公共入口點,因為它是所有圖片提供器的解析方法。子類只需要實現obtainKeyload方法來獲取圖片的唯一識別符號和載入圖片的資料,而不需要重寫resolve方法。

5、如果子類需要更改使用的ImageStream的實現方式,則可以重寫createStream方法。如果子類需要管理實際的影象解析度,則可以重寫resolveStreamForKey方法。例如,AssetImage類中的createStream方法返回一個AssetBundleImageStreamCompleter物件,該物件用於從應用程式資源中載入圖片資料。而NetworkImage類中的resolveStreamForKey方法使用HTTP使用者端從網路上載入圖片資料。

6、這段程式碼中還有一些偵錯資訊,例如將圖片提供器、圖片設定和圖片唯一識別符號新增到偵錯資訊中,以便在出現錯誤時進行偵錯。

obtainKey

/// Converts an ImageProvider's settings plus an ImageConfiguration to a key
/// that describes the precise image to load.
///
/// The type of the key is determined by the subclass. It is a value that
/// unambiguously identifies the image (_including its scale_) that the [load]
/// method will fetch. Different [ImageProvider]s given the same constructor
/// arguments and [ImageConfiguration] objects should return keys that are
/// '==' to each other (possibly by using a class for the key that itself
/// implements [==]).
Future&lt;T&gt; obtainKey(ImageConfiguration configuration);

這段註釋是關於obtainKey方法的說明。該方法是ImageProvider的子類應該實現的方法之一,用於將ImageProvider的設定及ImageConfiguration轉換為一個可以唯一標識圖片的key

不同的ImageProvider根據相同的建構函式引數和ImageConfiguration物件應該返回相等的key,以便於後續載入和快取圖片。key的型別由子類確定,它應該是一個值,可以唯一地標識出要載入的圖片(包括其縮放比例)。

在實現obtainKey方法時,子類可以考慮使用自定義的類來表示key,並實現==方法以保證唯一性。

resolveStreamForKey

@protected
void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) {
  // This is an unusual edge case where someone has told us that they found
  // the image we want before getting to this method. We should avoid calling
  // load again, but still update the image cache with LRU information.
  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,
    /// 載入
    () => loadBuffer(key, PaintingBinding.instance.instantiateImageCodecFromBuffer),
    onError: handleError,
  );
  if (completer != null) {
    /// 關鍵是解析並設定ImageStreamCompleter物件
    stream.setCompleter(completer);
  }
}

官方檔案解釋:

  • 該方法是ImageProvider的子類應該實現的方法之一,用於根據key來解析圖片。
  • resolveStreamForKey方法是由resolve方法呼叫的,其引數包括ImageConfigurationImageStreamkeyerrorHandler。子類可以通過實現resolveStreamForKey方法來管理圖片的實際解析過程,同時也可以通過呼叫errorHandler來處理解析過程中可能發生的錯誤。
  • 實現resolveStreamForKey方法時,子類可以考慮使用keyImageCache互動,例如呼叫ImageCache.putIfAbsent方法,並向stream通知監聽器。預設實現已經使用keyImageCache互動,子類可以選擇呼叫super.resolveStreamForKey方法或不呼叫。

從上面的原始碼,我們可以知道以下幾點:

  • 1、如果 stream 物件已經有了 completer(即已經有了可以載入圖片的方式),則將 completer 新增到 ImageCache 中,實現快取功能,並直接返回。
  • 2、如果 stream 物件還沒有 completer,則呼叫 loadBuffer 方法載入圖片,並將其返回的 ImageStreamCompleter 物件新增到 ImageCache 中,同時設定到 stream 物件的 completer 中。
  • 3、如果 loadBuffer 方法出現了異常,則會將異常交給 onError 回撥處理,以便在例外處理時能夠提供詳細的錯誤資訊。
  • 4、關鍵是解析並設定ImageStreamCompleter物件
  • 5、PaintingBinding.instance.imageCache.putIfAbsent方法在內部將ImageStreamListener物件新增到ImageStreamCompleter物件的_listeners陣列中了。
   PaintingBinding.instance.imageCache.putIfAbsent(
     key,
     () =&gt; loadBuffer(key, PaintingBinding.instance.instantiateImageCodecFromBuffer),
     onError: handleError,
   )

loadBuffer

/// Converts a key into an [ImageStreamCompleter], and begins fetching the
/// image.
///
/// For backwards-compatibility the default implementation of this method calls
/// through to [ImageProvider.load]. However, implementors of this interface should
/// only override this method and not [ImageProvider.load], which is deprecated.
///
/// The [decode] callback provides the logic to obtain the codec for the
/// image.
///
/// See also:
///
///  * [ResizeImage], for modifying the key to account for cache dimensions.
@protected
ImageStreamCompleter loadBuffer(T key, DecoderBufferCallback decode) {
  return load(key, PaintingBinding.instance.instantiateImageCodec);
}

從原始碼我們知道, [ImageProvider.load], which is deprecated被廢棄了。子類只需要重寫loadBuffer方法即可。

  • 這個方法是ImageProvider的一個protected方法,用於從快取中載入指定的圖片。
  • 它接受兩個引數:一個是唯一標識圖片的key,另一個是一個用於解碼圖片資料的回撥函數decode。
  • 這個方法呼叫了load方法,然後返回一個ImageStreamCompleter物件,它表示載入過程中的一個資料流。
  • 在load方法中,使用傳入的decode回撥函數從快取或網路中獲取圖片資料並解碼,然後將解碼後的圖片資料傳遞給ImageStreamCompleter物件,以便它可以生成一個帶有正確圖片資料的ImageInfo物件,這個ImageInfo物件可以被傳遞到Image widget中用於顯示圖片。

load(被廢棄)

/// Converts a key into an [ImageStreamCompleter], and begins fetching the
/// image.
///
/// This method is deprecated. Implement [loadBuffer] for faster image
/// loading. Only one of [load] and [loadBuffer] must be implemented, and
/// [loadBuffer] is preferred.
///
/// The [decode] callback provides the logic to obtain the codec for the
/// image.
///
/// See also:
///
///  * [ResizeImage], for modifying the key to account for cache dimensions.
@protected
@Deprecated(
  'Implement loadBuffer for faster image loading. '
  'This feature was deprecated after v2.13.0-1.0.pre.',
)
ImageStreamCompleter load(T key, DecoderCallback decode) {
  throw UnsupportedError('Implement loadBuffer for faster image loading');
}

從註釋可知:

這個方法被廢棄了,現在已經不再建議使用了。如果需要更快的影象載入,請實現 [loadBuffer] 方法。在 [load] 和 [loadBuffer] 方法中只需要實現其中一個,而且 [loadBuffer] 更受推薦。

[decode] 回撥提供了獲取影象編解碼器的邏輯。

evict

/// Evicts an entry from the image cache.
///
/// Returns a [Future] which indicates whether the value was successfully
/// removed.
///
/// The [ImageProvider] used does not need to be the same instance that was
/// passed to an [Image] widget, but it does need to create a key which is
/// equal to one.
///
/// The [cache] is optional and defaults to the global image cache.
///
/// The [configuration] is optional and defaults to
/// [ImageConfiguration.empty].
///
/// {@tool snippet}
///
/// The following sample code shows how an image loaded using the [Image]
/// widget can be evicted using a [NetworkImage] with a matching URL.
///
/// ```dart
/// class MyWidget extends StatelessWidget {
///   const MyWidget({
///     super.key,
///     this.url = ' ... ',
///   });
///
///   final String url;
///
///   @override
///   Widget build(BuildContext context) {
///     return Image.network(url);
///   }
///
///   void evictImage() {
///     final NetworkImage provider = NetworkImage(url);
///     provider.evict().then&lt;void&gt;((bool success) {
///       if (success) {
///         debugPrint('removed image!');
///       }
///     });
///   }
/// }
/// ```
/// {@end-tool}
Future&lt;bool&gt; evict({ ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty }) async {
  cache ??= imageCache;
  final T key = await obtainKey(configuration);
  return cache.evict(key);
}

這是一個名為evict的非同步方法,它的作用是從影象快取中刪除給定設定下的圖片。它有兩個可選引數:cacheconfiguration。如果cache引數為null,則預設使用全域性的imageCacheconfiguration引數是一個影象設定,它用於獲取將要從快取中刪除的圖片的鍵值。這個方法返回一個Future<bool>物件,表示刪除是否成功。如果快取中沒有找到要刪除的圖片,則返回false

列表快速滑動,記憶體暴增時,可以用這個方法做些事情。

總結

ImageProvider是Flutter中一個用於提供影象資料的抽象類,它定義瞭如何從不同的資料來源(如檔案系統、網路、記憶體)中獲取影象資料,並將其轉換成ImageStreamCompleter物件,以供Image元件使用。

在Flutter中,使用Image元件來載入和顯示影象,需要先提供一個ImageProvider物件作為其image屬性的值。ImageProvider類包含了兩個關鍵的方法:obtainKeyload

obtainKey方法用於獲取一個用於唯一標識影象資料的ImageProvider物件,這個物件可以用來快取影象資料,以便在需要重新載入影象時能夠快速獲取到它。例如,AssetImage類使用圖片資源的路徑作為其ImageProvider物件的識別符號,以便在需要重新載入該資源時能夠快速地從記憶體或磁碟快取中獲取到它。

load方法用於獲取一個ImageStreamCompleter物件,該物件包含了用於繪製影象的影象資料。在Flutter 2.5之前,load方法是一個抽象方法,必須由子類實現。但是從Flutter 2.5開始,load方法已被廢棄,取而代之的是resolve方法。resolve方法接受一個ImageConfiguration引數,並返回一個Future<ImageStreamCompleter>物件。它與load方法的功能類似,都是用於獲取影象資料,並將其轉換成ImageStreamCompleter物件,以供Image元件使用。

使用ImageProvider類載入和顯示影象的流程如下:

  • 建立一個ImageProvider物件,該物件提供了影象資料的來源和識別符號。
  • 使用ImageProvider物件作為Image元件的image屬性的值。
  • Image元件會呼叫obtainKey方法獲取一個用於唯一標識影象資料的ImageProvider物件。
  • Image元件會呼叫resolve方法獲取一個Future<ImageStreamCompleter>物件。
  • 當影象資料載入完成後,ImageStreamCompleter物件會將其通知給Image元件,Image元件會將其渲染到螢幕上。

總的來說,ImageProvider類是Flutter中一個非常重要的類,它提供了一種方便的方式來載入和顯示影象。雖然load方法已被廢棄,但是resolve方法提供了更好的替代方案,可以用於獲取影象資料並將其轉換成ImageStreamCompleter物件。

參考連結

Flutter系統網路圖片載入流程解析_Android

Flutter入門系列(四)---Flutter圖片快取

Flutter | Image 原始碼分析與優化方式

困惑解答

第一次載入圖片時,stream物件通常沒有completer。在第一次呼叫resolveStreamForKey時,會將stream物件的completer與對應的ImageCacheImageStreamCompleter進行繫結,並且completer會被設定為ImageStreamCompleter

以上就是Flutter載入圖片流程之ImageProvider原始碼範例解析的詳細內容,更多關於Flutter載入圖片ImageProvider的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com