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

