Flutter加載圖片流程之ImageProvider源碼示例解析
加載網(wǎng)絡(luò)圖片
Image.network()
是Flutter提供的一種從網(wǎng)絡(luò)上加載圖片的方法,它可以從指定的URL加載圖片,并在加載完成后將其顯示在應(yīng)用程序中。本節(jié)內(nèi)容,我們從源碼出發(fā),探討下圖片的加載流程。
ImageProvider
ImageProvider
是Flutter中一個(gè)抽象類,它定義了一種用于加載圖片的通用接口,可以用于加載本地圖片、網(wǎng)絡(luò)圖片等各種類型的圖片。
ImageProvider
類包含兩個(gè)核心方法:obtainKey
和loadBuffer
。
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; }
根據(jù)文檔解釋,我們可以了解到以下幾點(diǎn):
1、使用給定的`configuration`解析該圖片提供器,返回一個(gè) [ImageStream]。
2、這是 [ImageProvider] 類層次結(jié)構(gòu)的公共入口點(diǎn)。
3、子類應(yīng)該實(shí)現(xiàn) [obtainKey] 和 [load] 方法,這兩個(gè)方法將被該方法使用。
4、如果子類需要更改使用的 [ImageStream] 的實(shí)現(xiàn),則應(yīng)該重寫 [createStream] 方法。
5、 如果子類需要管理實(shí)際的圖像分辨率,則應(yīng)該重寫 [resolveStreamForKey] 方法。
閱讀resolve
方法的實(shí)現(xiàn)。我們可以知道:
1、它使用給定的configuration
參數(shù)創(chuàng)建一個(gè)ImageStream
對(duì)象(createStream
)。然后調(diào)用_createErrorHandlerAndKey
方法,該方法會(huì)異步獲取圖片的唯一標(biāo)識(shí)符,并設(shè)置一個(gè)錯(cuò)誤處理區(qū)域,以防圖片加載過(guò)程中發(fā)生錯(cuò)誤。
2、如果獲取唯一標(biāo)識(shí)符的過(guò)程中出現(xiàn)異常,則會(huì)將錯(cuò)誤信息封裝成一個(gè)_ErrorImageCompleter
對(duì)象,并將其設(shè)置為ImageStream
的completer
屬性,表示圖片加載失敗。
3、如果唯一標(biāo)識(shí)符獲取成功,則會(huì)調(diào)用resolveStreamForKey
方法來(lái)解析圖片,并將圖片數(shù)據(jù)存儲(chǔ)到ImageStream
對(duì)象中,供后續(xù)使用。
4、該方法是ImageProvider
類層次結(jié)構(gòu)的公共入口點(diǎn),因?yàn)樗撬袌D片提供器的解析方法。子類只需要實(shí)現(xiàn)obtainKey
和load
方法來(lái)獲取圖片的唯一標(biāo)識(shí)符和加載圖片的數(shù)據(jù),而不需要重寫resolve
方法。
5、如果子類需要更改使用的ImageStream
的實(shí)現(xiàn)方式,則可以重寫createStream
方法。如果子類需要管理實(shí)際的圖像分辨率,則可以重寫resolveStreamForKey
方法。例如,AssetImage
類中的createStream
方法返回一個(gè)AssetBundleImageStreamCompleter
對(duì)象,該對(duì)象用于從應(yīng)用程序資源中加載圖片數(shù)據(jù)。而NetworkImage
類中的resolveStreamForKey
方法使用HTTP客戶端從網(wǎng)絡(luò)上加載圖片數(shù)據(jù)。
6、這段代碼中還有一些調(diào)試信息,例如將圖片提供器、圖片配置和圖片唯一標(biāo)識(shí)符添加到調(diào)試信息中,以便在出現(xiàn)錯(cuò)誤時(shí)進(jìn)行調(diào)試。
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<T> obtainKey(ImageConfiguration configuration);
這段注釋是關(guān)于obtainKey
方法的說(shuō)明。該方法是ImageProvider
的子類應(yīng)該實(shí)現(xiàn)的方法之一,用于將ImageProvider
的設(shè)置及ImageConfiguration
轉(zhuǎn)換為一個(gè)可以唯一標(biāo)識(shí)圖片的key
。
不同的ImageProvider
根據(jù)相同的構(gòu)造函數(shù)參數(shù)和ImageConfiguration
對(duì)象應(yīng)該返回相等的key
,以便于后續(xù)加載和緩存圖片。key
的類型由子類確定,它應(yīng)該是一個(gè)值,可以唯一地標(biāo)識(shí)出要加載的圖片(包括其縮放比例)。
在實(shí)現(xiàn)obtainKey
方法時(shí),子類可以考慮使用自定義的類來(lái)表示key
,并實(shí)現(xiàn)==
方法以保證唯一性。
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) { /// 關(guān)鍵是解析并設(shè)置ImageStreamCompleter對(duì)象 stream.setCompleter(completer); } }
官方文檔解釋:
- 該方法是
ImageProvider
的子類應(yīng)該實(shí)現(xiàn)的方法之一,用于根據(jù)key
來(lái)解析圖片。 resolveStreamForKey
方法是由resolve
方法調(diào)用的,其參數(shù)包括ImageConfiguration
、ImageStream
、key
和errorHandler
。子類可以通過(guò)實(shí)現(xiàn)resolveStreamForKey
方法來(lái)管理圖片的實(shí)際解析過(guò)程,同時(shí)也可以通過(guò)調(diào)用errorHandler
來(lái)處理解析過(guò)程中可能發(fā)生的錯(cuò)誤。- 實(shí)現(xiàn)
resolveStreamForKey
方法時(shí),子類可以考慮使用key
與ImageCache
交互,例如調(diào)用ImageCache.putIfAbsent
方法,并向stream
通知監(jiān)聽(tīng)器。默認(rèn)實(shí)現(xiàn)已經(jīng)使用key
與ImageCache
交互,子類可以選擇調(diào)用super.resolveStreamForKey
方法或不調(diào)用。
從上面的源碼,我們可以知道以下幾點(diǎn):
- 1、如果
stream
對(duì)象已經(jīng)有了completer
(即已經(jīng)有了可以加載圖片的方式),則將completer
添加到ImageCache
中,實(shí)現(xiàn)緩存功能,并直接返回。 - 2、如果
stream
對(duì)象還沒(méi)有completer
,則調(diào)用loadBuffer
方法加載圖片,并將其返回的ImageStreamCompleter
對(duì)象添加到ImageCache
中,同時(shí)設(shè)置到stream
對(duì)象的completer
中。 - 3、如果
loadBuffer
方法出現(xiàn)了異常,則會(huì)將異常交給onError
回調(diào)處理,以便在異常處理時(shí)能夠提供詳細(xì)的錯(cuò)誤信息。 - 4、關(guān)鍵是解析并設(shè)置
ImageStreamCompleter
對(duì)象 - 5、
PaintingBinding.instance.imageCache.putIfAbsent
方法在內(nèi)部將ImageStreamListener
對(duì)象添加到ImageStreamCompleter
對(duì)象的_listeners
數(shù)組中了。
PaintingBinding.instance.imageCache.putIfAbsent( key, () => 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
方法即可。
- 這個(gè)方法是ImageProvider的一個(gè)protected方法,用于從緩存中加載指定的圖片。
- 它接受兩個(gè)參數(shù):一個(gè)是唯一標(biāo)識(shí)圖片的key,另一個(gè)是一個(gè)用于解碼圖片數(shù)據(jù)的回調(diào)函數(shù)decode。
- 這個(gè)方法調(diào)用了load方法,然后返回一個(gè)ImageStreamCompleter對(duì)象,它表示加載過(guò)程中的一個(gè)數(shù)據(jù)流。
- 在load方法中,使用傳入的decode回調(diào)函數(shù)從緩存或網(wǎng)絡(luò)中獲取圖片數(shù)據(jù)并解碼,然后將解碼后的圖片數(shù)據(jù)傳遞給ImageStreamCompleter對(duì)象,以便它可以生成一個(gè)帶有正確圖片數(shù)據(jù)的ImageInfo對(duì)象,這個(gè)ImageInfo對(duì)象可以被傳遞到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'); }
從注釋可知:
這個(gè)方法被廢棄了,現(xiàn)在已經(jīng)不再建議使用了。如果需要更快的圖像加載,請(qǐng)實(shí)現(xiàn) [loadBuffer] 方法。在 [load] 和 [loadBuffer] 方法中只需要實(shí)現(xiàn)其中一個(gè),而且 [loadBuffer] 更受推薦。
[decode] 回調(diào)提供了獲取圖像編解碼器的邏輯。
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<void>((bool success) { /// if (success) { /// debugPrint('removed image!'); /// } /// }); /// } /// } /// ``` /// {@end-tool} Future<bool> evict({ ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty }) async { cache ??= imageCache; final T key = await obtainKey(configuration); return cache.evict(key); }
這是一個(gè)名為evict
的異步方法,它的作用是從圖像緩存中刪除給定配置下的圖片。它有兩個(gè)可選參數(shù):cache
和configuration
。如果cache
參數(shù)為null,則默認(rèn)使用全局的imageCache
。configuration
參數(shù)是一個(gè)圖像配置,它用于獲取將要從緩存中刪除的圖片的鍵值。這個(gè)方法返回一個(gè)Future<bool>
對(duì)象,表示刪除是否成功。如果緩存中沒(méi)有找到要?jiǎng)h除的圖片,則返回false
。
列表快速滑動(dòng),內(nèi)存暴增時(shí),可以用這個(gè)方法做些事情。
總結(jié)
ImageProvider
是Flutter中一個(gè)用于提供圖像數(shù)據(jù)的抽象類,它定義了如何從不同的數(shù)據(jù)源(如文件系統(tǒng)、網(wǎng)絡(luò)、內(nèi)存)中獲取圖像數(shù)據(jù),并將其轉(zhuǎn)換成ImageStreamCompleter
對(duì)象,以供Image
組件使用。
在Flutter中,使用Image
組件來(lái)加載和顯示圖像,需要先提供一個(gè)ImageProvider
對(duì)象作為其image
屬性的值。ImageProvider
類包含了兩個(gè)關(guān)鍵的方法:obtainKey
和load
。
obtainKey
方法用于獲取一個(gè)用于唯一標(biāo)識(shí)圖像數(shù)據(jù)的ImageProvider
對(duì)象,這個(gè)對(duì)象可以用來(lái)緩存圖像數(shù)據(jù),以便在需要重新加載圖像時(shí)能夠快速獲取到它。例如,AssetImage
類使用圖片資源的路徑作為其ImageProvider
對(duì)象的標(biāo)識(shí)符,以便在需要重新加載該資源時(shí)能夠快速地從內(nèi)存或磁盤緩存中獲取到它。
load
方法用于獲取一個(gè)ImageStreamCompleter
對(duì)象,該對(duì)象包含了用于繪制圖像的圖像數(shù)據(jù)。在Flutter 2.5之前,load
方法是一個(gè)抽象方法,必須由子類實(shí)現(xiàn)。但是從Flutter 2.5開(kāi)始,load
方法已被廢棄,取而代之的是resolve
方法。resolve
方法接受一個(gè)ImageConfiguration
參數(shù),并返回一個(gè)Future<ImageStreamCompleter>
對(duì)象。它與load
方法的功能類似,都是用于獲取圖像數(shù)據(jù),并將其轉(zhuǎn)換成ImageStreamCompleter
對(duì)象,以供Image
組件使用。
使用ImageProvider
類加載和顯示圖像的流程如下:
- 創(chuàng)建一個(gè)
ImageProvider
對(duì)象,該對(duì)象提供了圖像數(shù)據(jù)的來(lái)源和標(biāo)識(shí)符。 - 使用
ImageProvider
對(duì)象作為Image
組件的image
屬性的值。 Image
組件會(huì)調(diào)用obtainKey
方法獲取一個(gè)用于唯一標(biāo)識(shí)圖像數(shù)據(jù)的ImageProvider
對(duì)象。Image
組件會(huì)調(diào)用resolve
方法獲取一個(gè)Future<ImageStreamCompleter>
對(duì)象。- 當(dāng)圖像數(shù)據(jù)加載完成后,
ImageStreamCompleter
對(duì)象會(huì)將其通知給Image
組件,Image
組件會(huì)將其渲染到屏幕上。
總的來(lái)說(shuō),ImageProvider
類是Flutter中一個(gè)非常重要的類,它提供了一種方便的方式來(lái)加載和顯示圖像。雖然load
方法已被廢棄,但是resolve
方法提供了更好的替代方案,可以用于獲取圖像數(shù)據(jù)并將其轉(zhuǎn)換成ImageStreamCompleter
對(duì)象。
參考鏈接
Flutter系統(tǒng)網(wǎng)絡(luò)圖片加載流程解析_Android
Flutter | Image 源碼分析與優(yōu)化方式
困惑解答
第一次加載圖片時(shí),stream
對(duì)象通常沒(méi)有completer
。在第一次調(diào)用resolveStreamForKey
時(shí),會(huì)將stream
對(duì)象的completer
與對(duì)應(yīng)的ImageCache
的ImageStreamCompleter
進(jìn)行綁定,并且completer
會(huì)被設(shè)置為ImageStreamCompleter
。
以上就是Flutter加載圖片流程之ImageProvider源碼示例解析的詳細(xì)內(nèi)容,更多關(guān)于Flutter加載圖片ImageProvider的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android開(kāi)發(fā)手冊(cè)Button按鈕實(shí)現(xiàn)點(diǎn)擊音效
這篇文章主要為大家介紹了Android開(kāi)發(fā)手冊(cè)Button按鈕實(shí)現(xiàn)點(diǎn)擊音效示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Android App調(diào)用MediaRecorder實(shí)現(xiàn)錄音功能的實(shí)例
這篇文章主要介紹了Android App調(diào)用MediaRecorder實(shí)現(xiàn)錄音功能的實(shí)例,MediaRecorder非常強(qiáng)大,不僅能夠用來(lái)錄制音頻還可以錄制視頻,需要的朋友可以參考下2016-04-04Android中SwipeBack實(shí)現(xiàn)右滑返回效果
這篇文章主要介紹了Android中SwipeBack實(shí)現(xiàn)右滑返回效果的相關(guān)資料,需要的朋友可以參考下2016-02-02RecyclerView+PagerSnapHelper實(shí)現(xiàn)抖音首頁(yè)翻頁(yè)的Viewpager效果
這篇文章主要為大家詳細(xì)介紹了RecyclerView+PagerSnapHelper實(shí)現(xiàn)抖音首頁(yè)翻頁(yè)的Viewpager效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10Android端權(quán)限隱私的合規(guī)化處理實(shí)戰(zhàn)記錄
大家應(yīng)該都發(fā)現(xiàn)了,現(xiàn)在很多應(yīng)用市場(chǎng)都要求應(yīng)用上架需要用戶協(xié)議,這篇文章主要給大家介紹了關(guān)于Android端權(quán)限隱私合規(guī)化處理的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-08-08Android開(kāi)發(fā)Compose remember原理解析
這篇文章主要為大家介紹了Android開(kāi)發(fā)Compose remember原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07Android幀率監(jiān)測(cè)與優(yōu)化技巧
Android 應(yīng)用的性能優(yōu)化是開(kāi)發(fā)過(guò)程中至關(guān)重要的一環(huán),而幀率(Frame Rate)是評(píng)估應(yīng)用性能的一個(gè)關(guān)鍵指標(biāo),在本文中,我們將深入探討如何監(jiān)測(cè) Android 應(yīng)用的幀率,以及如何通過(guò)代碼示例來(lái)優(yōu)化應(yīng)用的性能,需要的朋友可以參考下2023-10-10