Flutter加載圖片流程MultiFrameImageStreamCompleter解析
MultiFrameImageStreamCompleter
MultiFrameImageStreamCompleter
是一個可組合的 ImageStreamCompleter
類,用于將多個 ImageStreamCompleter
對象合并為一個單獨的 ImageStream
對象,通常用于動畫效果。每當(dāng)子 ImageStreamCompleter
接收到一個新的 ImageInfo
對象,它會立即通知其所有的監(jiān)聽器,并將對象傳遞給它們。
當(dāng) MultiFrameImageStreamCompleter
的 addListener()
方法被調(diào)用時,它將傳入的 ImageStreamListener
添加到其內(nèi)部的子 ImageStreamCompleter
的監(jiān)聽器列表中。如果 MultiFrameImageStreamCompleter
本身接收到一個 ImageInfo
對象,它會將它傳遞給其所有的監(jiān)聽器。但是,它不會自己管理這些幀,而是委托給每個子 ImageStreamCompleter
來完成。
MultiFrameImageStreamCompleter
還支持漸進(jìn)式 JPEG,并實現(xiàn)了 addListener()
、removeListener()
和 dispose()
方法,以及一個名為 getNextFrame()
的方法,用于從圖像流中獲取下一幀。
當(dāng)所有幀都加載完畢后,MultiFrameImageStreamCompleter
將使用 dart:ui.Codec
解碼器將它們合并為一個單獨的 dart:ui.Image
對象,并將其傳遞給 setImage()
方法。最后,它將通知所有監(jiān)聽器,并將它們傳遞給 ImageStreamListener.onImage()
回調(diào)函數(shù),以通知它們新的 ImageInfo
已經(jīng)可用。
當(dāng) MultiFrameImageStreamCompleter
的 dispose()
方法被調(diào)用時,它會將其所有子 ImageStreamCompleter
的 dispose()
方法依次調(diào)用,以釋放所有資源,并取消所有未處理的幀請求。同時,它還會確保在釋放資源之前將所有錯誤通知給其監(jiān)聽器。
_handleCodecReady
void _handleCodecReady(ui.Codec codec) { _codec = codec; assert(_codec != null); if (hasListeners) { _decodeNextFrameAndSchedule(); } }
在_handleCodecReady
方法中,首先將傳入的codec
對象賦值給成員變量_codec
,然后使用assert
語句來確保該變量不為空。接著,如果當(dāng)前對象有監(jiān)聽器,則調(diào)用_decodeNextFrameAndSchedule
方法來解碼下一幀并將其調(diào)度執(zhí)行。這里的目的是為了盡早地開始解碼下一幀圖像,以盡快展示出完整的動畫效果。如果沒有監(jiān)聽器,則不需要解碼下一幀圖像,因為沒有地方可以展示它。
_decodeNextFrameAndSchedule
Future<void> _decodeNextFrameAndSchedule() async { // This will be null if we gave it away. If not, it's still ours and it // must be disposed of. _nextFrame?.image.dispose(); _nextFrame = null; try { _nextFrame = await _codec!.getNextFrame(); } catch (exception, stack) { reportError( context: ErrorDescription('resolving an image frame'), exception: exception, stack: stack, informationCollector: _informationCollector, silent: true, ); return; } if (_codec!.frameCount == 1) { // ImageStreamCompleter listeners removed while waiting for next frame to // be decoded. // There's no reason to emit the frame without active listeners. if (!hasListeners) { return; } // This is not an animated image, just return it and don't schedule more // frames. _emitFrame(ImageInfo( image: _nextFrame!.image.clone(), scale: _scale, debugLabel: debugLabel, )); _nextFrame!.image.dispose(); _nextFrame = null; return; } _scheduleAppFrame(); }
- 這個方法的作用是獲取下一幀并在獲取成功后調(diào)度下一幀的解碼,如果幀數(shù)為1,即這是一個靜態(tài)圖片,則只需返回該幀,并在沒有監(jiān)聽器時直接返回,如果幀數(shù)大于1,則調(diào)度下一幀的解碼。
- 在獲取下一幀之前,方法會清除上一幀并將_nextFrame置為null,以便準(zhǔn)備下一幀。
- 如果解碼下一幀時發(fā)生異常,則會記錄錯誤并返回。如果在等待下一幀的解碼期間移除了監(jiān)聽器,則在沒有活動的監(jiān)聽器時不會發(fā)出幀,否則會發(fā)出幀并調(diào)度下一幀的解碼。
_emitFrame
方法的作用是向 ImageStreamCompleter
發(fā)送新的 ImageInfo
。具體實現(xiàn)是通過調(diào)用 setImage
方法將 ImageInfo
設(shè)置為 ImageStreamCompleter
的當(dāng)前值,同時更新 _framesEmitted
計數(shù)器。
_codec!.getNextFrame()
_nextFrame = await _codec!.getNextFrame();
/// Fetches the next animation frame. /// /// Wraps back to the first frame after returning the last frame. /// /// The returned future can complete with an error if the decoding has failed. /// /// The caller of this method is responsible for disposing the /// [FrameInfo.image] on the returned object. Future<FrameInfo> getNextFrame() async { final Completer<FrameInfo> completer = Completer<FrameInfo>.sync(); final String? error = _getNextFrame((_Image? image, int durationMilliseconds) { if (image == null) { completer.completeError(Exception('Codec failed to produce an image, possibly due to invalid image data.')); } else { completer.complete(FrameInfo._( image: Image._(image, image.width, image.height), duration: Duration(milliseconds: durationMilliseconds), )); } }); if (error != null) { throw Exception(error); } return completer.future; } /// Returns an error message on failure, null on success. String? _getNextFrame(void Function(_Image?, int) callback) native 'Codec_getNextFrame';
getNextFrame()
是 Codec
類的一個方法,用于獲取解碼后的幀。具體來說,它會在 Codec
內(nèi)部解碼圖像幀,返回一個 FrameInfo
對象,其中包含了解碼后的 Image
對象以及該幀的時間戳和持續(xù)時間等信息。由于 Codec
可能會支持動畫圖像,因此 getNextFrame()
方法可能會返回多個幀。
在 MultiFrameImageStreamCompleter
中,_decodeNextFrameAndSchedule()
方法會調(diào)用 _codec.getNextFrame()
方法獲取下一幀圖像,然后將其保存在 _nextFrame
屬性中。如果 _codec
的 frameCount
屬性為 1,說明這是一個靜態(tài)圖像,直接使用 _emitFrame()
方法發(fā)布該幀圖像;否則,調(diào)用 _scheduleAppFrame()
方法安排下一幀的發(fā)布。
_emitFrame(重要方法, 通知監(jiān)聽器觸發(fā)回調(diào),更新UI)
void _emitFrame(ImageInfo imageInfo) { setImage(imageInfo); _framesEmitted += 1; }
這個方法在 _decodeNextFrameAndSchedule
中被調(diào)用,用于處理已解碼的下一幀圖像。如果當(dāng)前幀是非動畫圖像,它會直接調(diào)用 setImage
方法更新 ImageStreamCompleter
的值,如果是動畫圖像,它會計劃下一幀的顯示并等待下一幀的解碼。
_scheduleAppFrame
void _scheduleAppFrame() { if (_frameCallbackScheduled) { return; } _frameCallbackScheduled = true; SchedulerBinding.instance.scheduleFrameCallback(_handleAppFrame); }
函數(shù) _scheduleAppFrame()
的作用是調(diào)度一個Flutter引擎幀回調(diào),在回調(diào)中會調(diào)用 _handleAppFrame()
函數(shù)。
具體來說,這個函數(shù)的實現(xiàn)包含以下步驟:
1、檢查 _frameCallbackScheduled
標(biāo)志,如果為 true,則說明幀回調(diào)已經(jīng)被調(diào)度過,直接返回。
2、將 _frameCallbackScheduled
標(biāo)志設(shè)置為 true,表示幀回調(diào)已經(jīng)被調(diào)度。
3、調(diào)用 SchedulerBinding.instance.scheduleFrameCallback()
函數(shù),向Flutter引擎注冊一個幀回調(diào)。回調(diào)函數(shù)為 _handleAppFrame()
。
4、在 _handleAppFrame()
函數(shù)中,將會根據(jù)當(dāng)前動畫播放的幀率和幀數(shù),計算下一幀應(yīng)該在何時被顯示,然后再次調(diào)用 _decodeNextFrameAndSchedule()
函數(shù),以獲取并顯示下一幀圖像。這樣就完成了一次動畫播放的循環(huán)。
_handleAppFrame
void _handleAppFrame(Duration timestamp) { _frameCallbackScheduled = false; if (!hasListeners) { return; } assert(_nextFrame != null); if (_isFirstFrame() || _hasFrameDurationPassed(timestamp)) { _emitFrame(ImageInfo( image: _nextFrame!.image.clone(), scale: _scale, debugLabel: debugLabel, )); _shownTimestamp = timestamp; _frameDuration = _nextFrame!.duration; _nextFrame!.image.dispose(); _nextFrame = null; final int completedCycles = _framesEmitted ~/ _codec!.frameCount; if (_codec!.repetitionCount == -1 || completedCycles <= _codec!.repetitionCount) { _decodeNextFrameAndSchedule(); } return; } final Duration delay = _frameDuration! - (timestamp - _shownTimestamp); _timer = Timer(delay * timeDilation, () { _scheduleAppFrame(); }); }
函數(shù) _handleAppFrame
是 MultiFrameImageStreamCompleter 的核心函數(shù),用于處理多幀圖像的邏輯。下面是對該函數(shù)的詳細(xì)解讀:
1、
_frameCallbackScheduled = false;
- 將
_frameCallbackScheduled
設(shè)為 false,表示下一幀還沒有調(diào)度。
- 將
2、
if (!hasListeners) { return; }
- 如果沒有監(jiān)聽器,則直接返回。
3、
assert(_nextFrame != null);
- 斷言
_nextFrame
不為空。
- 斷言
4、
_isFirstFrame() || _hasFrameDurationPassed(timestamp)
- 如果是第一幀或者幀時間已經(jīng)超過了
_frameDuration
,則進(jìn)行以下操作:
- 如果是第一幀或者幀時間已經(jīng)超過了
5、
_emitFrame(ImageInfo(image: _nextFrame!.image.clone(), scale: _scale, debugLabel: debugLabel));
- 發(fā)出
ImageInfo
事件,將_nextFrame
的圖像信息作為參數(shù)傳入。
- 發(fā)出
6、
_shownTimestamp = timestamp;
- 更新
_shownTimestamp
為當(dāng)前時間戳。
- 更新
7、
_frameDuration = _nextFrame!.duration;
- 更新
_frameDuration
為_nextFrame
的幀間隔時間。
- 更新
8、
_nextFrame!.image.dispose(); _nextFrame = null;
- 釋放
_nextFrame
的圖像資源并將_nextFrame
設(shè)為 null。
- 釋放
9、
final int completedCycles = _framesEmitted ~/ _codec!.frameCount;
- 計算已經(jīng)完成的循環(huán)次數(shù)。
10、
_codec!.repetitionCount == -1 || completedCycles <= _codec!.repetitionCount
- 如果循環(huán)次數(shù)為 -1(表示無限循環(huán))或者已經(jīng)完成的循環(huán)次數(shù)小于等于
_codec
的循環(huán)次數(shù),則進(jìn)行以下操作:
- 如果循環(huán)次數(shù)為 -1(表示無限循環(huán))或者已經(jīng)完成的循環(huán)次數(shù)小于等于
11、
_decodeNextFrameAndSchedule();
- 解碼下一幀并調(diào)度下一幀的繪制。
12、
final Duration delay = _frameDuration! - (timestamp - _shownTimestamp);
- 計算下一幀需要延遲的時間。
13、
_timer = Timer(delay * timeDilation, () { _scheduleAppFrame(); });
- 使用計時器來實現(xiàn)下一幀的延遲繪制。延遲時間為
delay
乘以timeDilation
(可以通過調(diào)用timeDilation = x
來改變時間流逝的速度)。當(dāng)計時器觸發(fā)時,將調(diào)用_scheduleAppFrame
來調(diào)度下一幀的繪制。
- 使用計時器來實現(xiàn)下一幀的延遲繪制。延遲時間為
addListener
void addListener(ImageStreamListener listener) { if (!hasListeners && _codec != null && (_currentImage == null || _codec!.frameCount > 1)) { _decodeNextFrameAndSchedule(); } super.addListener(listener); }
這個方法是 ImageStreamCompleter
類的方法,用于向 ImageStreamCompleter
添加監(jiān)聽器。當(dāng)?shù)谝粋€監(jiān)聽器被添加到 ImageStreamCompleter
上時,會檢查 _codec
是否為 null,如果不為 null 并且有多幀圖像或者當(dāng)前圖像為 null,則會調(diào)用 _decodeNextFrameAndSchedule()
方法開始解碼下一幀圖像并計劃渲染。這樣做是為了確保在第一個監(jiān)聽器被添加到 ImageStreamCompleter
上時就開始解碼下一幀圖像并在下一幀渲染完成后通知所有監(jiān)聽器。如果 _codec
為 null 或者當(dāng)前圖像為單幀圖像,則不會調(diào)用 _decodeNextFrameAndSchedule()
方法。在這個方法中,調(diào)用了 super.addListener(listener)
將監(jiān)聽器添加到監(jiān)聽器列表中。
removeListener
void removeListener(ImageStreamListener listener) { super.removeListener(listener); if (!hasListeners) { _timer?.cancel(); _timer = null; } }
removeListener
方法用于從 MultiFrameImageStreamCompleter
中移除給定的 ImageStreamListener
。當(dāng)移除后,如果該對象不再有任何監(jiān)聽器,就會取消定時器 _timer
。
具體來說,該方法會先調(diào)用父類的 removeListener
方法,將該監(jiān)聽器從監(jiān)聽器列表中移除。接著,如果此時 hasListeners
為 false
,說明沒有任何監(jiān)聽器,就會取消 _timer
定時器,以便釋放資源。
_maybeDispose
void _maybeDispose() { super._maybeDispose(); if (_disposed) { _chunkSubscription?.onData(null); _chunkSubscription?.cancel(); _chunkSubscription = null; } }
_maybeDispose()
是一個用來釋放資源的方法,當(dāng)圖片流不再被監(jiān)聽時調(diào)用。它首先調(diào)用父類的_maybeDispose()
方法,以處理父類中的一些釋放資源的邏輯。然后它會檢查_disposed
屬性是否為true,如果是,則取消并置空_chunkSubscription
,這個對象是用來訂閱圖像數(shù)據(jù)塊的流。這樣做是為了釋放相關(guān)的資源,以防止內(nèi)存泄漏。
總結(jié)
MultiFrameImageStreamCompleter 是 Flutter 中用于處理多幀圖片的類,主要用于將多幀動畫圖片的每一幀渲染到屏幕上。
該類內(nèi)部主要維護(hù)了一個 Codec 對象,用于解碼圖片,同時也有一個 ImageInfo 對象用于存儲當(dāng)前幀的信息,并且該類也實現(xiàn)了 ImageStreamCompleter 類,可以作為 Image 對象的 ImageStream。
在 MultiFrameImageStreamCompleter 的初始化過程中,會創(chuàng)建 Codec 對象,并且在該對象準(zhǔn)備好后進(jìn)行處理,并且在添加監(jiān)聽器時,如果該類當(dāng)前沒有監(jiān)聽器,并且已經(jīng)獲取了第一幀圖像,那么該類會進(jìn)行后續(xù)幀的解碼和渲染。如果該類被銷毀,則會清空 Codec 對象。
在該類的主要方法中,_handleCodecReady() 方法會在初始化時進(jìn)行調(diào)用,用于設(shè)置解碼后的 Codec 對象,并在有監(jiān)聽器的情況下開始解碼和渲染下一幀圖片。
_decodeNextFrameAndSchedule() 方法用于解碼和渲染下一幀圖片,通過 _codec!.getNextFrame() 方法獲取下一幀圖像,并進(jìn)行渲染處理,如果當(dāng)前只有一幀圖片,則直接渲染該幀圖片并停止。
_handleAppFrame() 方法用于處理渲染下一幀圖片的邏輯,會根據(jù)時間戳計算出下一幀圖片的渲染時間,并設(shè)置延時定時器,定時調(diào)用該方法。
addListener() 和 removeListener() 方法用于添加和刪除監(jiān)聽器,并在沒有監(jiān)聽器時停止解碼和渲染。
最后,_maybeDispose() 方法會在該類被銷毀時進(jìn)行調(diào)用,用于清空內(nèi)部緩存。
參考鏈接
以上就是Flutter加載圖片流程MultiFrameImageStreamCompleter解析的詳細(xì)內(nèi)容,更多關(guān)于Flutter MultiFrameImageStreamCompleter的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
GridView基于pulltorefresh實現(xiàn)下拉刷新 上拉加載更多功能(推薦)
原理和listview一樣 ,都是重寫Android原生控件。下面小編通過實例代碼給大家分享GridView基于pulltorefresh實現(xiàn)下拉刷新 上拉加載更多功能,非常不錯,一起看看吧2016-11-11Kotlin基礎(chǔ)學(xué)習(xí)之Deprecated與Suppress注解使用
這篇文章主要給大家介紹了關(guān)于Kotlin基礎(chǔ)學(xué)習(xí)之Deprecated與Suppress注解使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Kotlin具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08android工程下不能運行java main程序的解決方法
這篇文章主要介紹了android工程下不能運行java main程序的解決方法,需要的朋友可以參考下2014-05-05Android實現(xiàn)Reveal圓形Activity轉(zhuǎn)場動畫的完整步驟
這篇文章主要給大家介紹了關(guān)于Android Reveal圓形Activity轉(zhuǎn)場動畫的實現(xiàn)過程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11Android自定義view實現(xiàn)有header和footer作為layout使用的滾動控件
這篇文章主要介紹了Android自定義view實現(xiàn)有header和footer的滾動控件,可以在XML中當(dāng)Layout使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-11-11在Ubuntu下搭建Android開發(fā)環(huán)境
對一個程序猿來說,裝好系統(tǒng)之后的第一件事,一定是搭建開發(fā)環(huán)境,已經(jīng)安裝各種開發(fā)工具,以便之后能方便順利地進(jìn)行程序的開發(fā)。簡單的介紹下在Ubuntu環(huán)境下搭建Android開發(fā)環(huán)境,雖然基本上和在Windows下沒有太大差別,但有些細(xì)節(jié)上還是很值得注意的。2014-07-07Android仿微信調(diào)用第三方地圖應(yīng)用導(dǎo)航(高德、百度、騰訊)
這篇文章主要介紹了Android仿微信調(diào)用第三方地圖應(yīng)用導(dǎo)航,包括高德、百度、騰訊地圖,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10