您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“Flutter MultiFrameImageStreamCompleter是什么”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Flutter MultiFrameImageStreamCompleter是什么”吧!
MultiFrameImageStreamCompleter
是一個(gè)可組合的 ImageStreamCompleter
類,用于將多個(gè) ImageStreamCompleter
對(duì)象合并為一個(gè)單獨(dú)的 ImageStream
對(duì)象,通常用于動(dòng)畫(huà)效果。每當(dāng)子 ImageStreamCompleter
接收到一個(gè)新的 ImageInfo
對(duì)象,它會(huì)立即通知其所有的監(jiān)聽(tīng)器,并將對(duì)象傳遞給它們。
當(dāng) MultiFrameImageStreamCompleter
的 addListener()
方法被調(diào)用時(shí),它將傳入的 ImageStreamListener
添加到其內(nèi)部的子 ImageStreamCompleter
的監(jiān)聽(tīng)器列表中。如果 MultiFrameImageStreamCompleter
本身接收到一個(gè) ImageInfo
對(duì)象,它會(huì)將它傳遞給其所有的監(jiān)聽(tīng)器。但是,它不會(huì)自己管理這些幀,而是委托給每個(gè)子 ImageStreamCompleter
來(lái)完成。
MultiFrameImageStreamCompleter
還支持漸進(jìn)式 JPEG,并實(shí)現(xiàn)了 addListener()
、removeListener()
和 dispose()
方法,以及一個(gè)名為 getNextFrame()
的方法,用于從圖像流中獲取下一幀。
當(dāng)所有幀都加載完畢后,MultiFrameImageStreamCompleter
將使用 dart:ui.Codec
解碼器將它們合并為一個(gè)單獨(dú)的 dart:ui.Image
對(duì)象,并將其傳遞給 setImage()
方法。最后,它將通知所有監(jiān)聽(tīng)器,并將它們傳遞給 ImageStreamListener.onImage()
回調(diào)函數(shù),以通知它們新的 ImageInfo
已經(jīng)可用。
當(dāng) MultiFrameImageStreamCompleter
的 dispose()
方法被調(diào)用時(shí),它會(huì)將其所有子 ImageStreamCompleter
的 dispose()
方法依次調(diào)用,以釋放所有資源,并取消所有未處理的幀請(qǐng)求。同時(shí),它還會(huì)確保在釋放資源之前將所有錯(cuò)誤通知給其監(jiān)聽(tīng)器。
void _handleCodecReady(ui.Codec codec) { _codec = codec; assert(_codec != null); if (hasListeners) { _decodeNextFrameAndSchedule(); } }
在_handleCodecReady
方法中,首先將傳入的codec
對(duì)象賦值給成員變量_codec
,然后使用assert
語(yǔ)句來(lái)確保該變量不為空。接著,如果當(dāng)前對(duì)象有監(jiān)聽(tīng)器,則調(diào)用_decodeNextFrameAndSchedule
方法來(lái)解碼下一幀并將其調(diào)度執(zhí)行。這里的目的是為了盡早地開(kāi)始解碼下一幀圖像,以盡快展示出完整的動(dòng)畫(huà)效果。如果沒(méi)有監(jiān)聽(tīng)器,則不需要解碼下一幀圖像,因?yàn)闆](méi)有地方可以展示它。
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(); }
這個(gè)方法的作用是獲取下一幀并在獲取成功后調(diào)度下一幀的解碼,如果幀數(shù)為1,即這是一個(gè)靜態(tài)圖片,則只需返回該幀,并在沒(méi)有監(jiān)聽(tīng)器時(shí)直接返回,如果幀數(shù)大于1,則調(diào)度下一幀的解碼。
在獲取下一幀之前,方法會(huì)清除上一幀并將_nextFrame置為null,以便準(zhǔn)備下一幀。
如果解碼下一幀時(shí)發(fā)生異常,則會(huì)記錄錯(cuò)誤并返回。如果在等待下一幀的解碼期間移除了監(jiān)聽(tīng)器,則在沒(méi)有活動(dòng)的監(jiān)聽(tīng)器時(shí)不會(huì)發(fā)出幀,否則會(huì)發(fā)出幀并調(diào)度下一幀的解碼。
_emitFrame
方法的作用是向 ImageStreamCompleter
發(fā)送新的 ImageInfo
。具體實(shí)現(xiàn)是通過(guò)調(diào)用 setImage
方法將 ImageInfo
設(shè)置為 ImageStreamCompleter
的當(dāng)前值,同時(shí)更新 _framesEmitted
計(jì)數(shù)器。
_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
類的一個(gè)方法,用于獲取解碼后的幀。具體來(lái)說(shuō),它會(huì)在 Codec
內(nèi)部解碼圖像幀,返回一個(gè) FrameInfo
對(duì)象,其中包含了解碼后的 Image
對(duì)象以及該幀的時(shí)間戳和持續(xù)時(shí)間等信息。由于 Codec
可能會(huì)支持動(dòng)畫(huà)圖像,因此 getNextFrame()
方法可能會(huì)返回多個(gè)幀。
在 MultiFrameImageStreamCompleter
中,_decodeNextFrameAndSchedule()
方法會(huì)調(diào)用 _codec.getNextFrame()
方法獲取下一幀圖像,然后將其保存在 _nextFrame
屬性中。如果 _codec
的 frameCount
屬性為 1,說(shuō)明這是一個(gè)靜態(tài)圖像,直接使用 _emitFrame()
方法發(fā)布該幀圖像;否則,調(diào)用 _scheduleAppFrame()
方法安排下一幀的發(fā)布。
void _emitFrame(ImageInfo imageInfo) { setImage(imageInfo); _framesEmitted += 1; }
這個(gè)方法在 _decodeNextFrameAndSchedule
中被調(diào)用,用于處理已解碼的下一幀圖像。如果當(dāng)前幀是非動(dòng)畫(huà)圖像,它會(huì)直接調(diào)用 setImage
方法更新 ImageStreamCompleter
的值,如果是動(dòng)畫(huà)圖像,它會(huì)計(jì)劃下一幀的顯示并等待下一幀的解碼。
void _scheduleAppFrame() { if (_frameCallbackScheduled) { return; } _frameCallbackScheduled = true; SchedulerBinding.instance.scheduleFrameCallback(_handleAppFrame); }
函數(shù) _scheduleAppFrame()
的作用是調(diào)度一個(gè)Flutter引擎幀回調(diào),在回調(diào)中會(huì)調(diào)用 _handleAppFrame()
函數(shù)。
具體來(lái)說(shuō),這個(gè)函數(shù)的實(shí)現(xiàn)包含以下步驟:
1、檢查 _frameCallbackScheduled
標(biāo)志,如果為 true,則說(shuō)明幀回調(diào)已經(jīng)被調(diào)度過(guò),直接返回。
2、將 _frameCallbackScheduled
標(biāo)志設(shè)置為 true,表示幀回調(diào)已經(jīng)被調(diào)度。
3、調(diào)用 SchedulerBinding.instance.scheduleFrameCallback()
函數(shù),向Flutter引擎注冊(cè)一個(gè)幀回調(diào)?;卣{(diào)函數(shù)為 _handleAppFrame()
。
4、在 _handleAppFrame()
函數(shù)中,將會(huì)根據(jù)當(dāng)前動(dòng)畫(huà)播放的幀率和幀數(shù),計(jì)算下一幀應(yīng)該在何時(shí)被顯示,然后再次調(diào)用 _decodeNextFrameAndSchedule()
函數(shù),以獲取并顯示下一幀圖像。這樣就完成了一次動(dòng)畫(huà)播放的循環(huán)。
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ù),用于處理多幀圖像的邏輯。下面是對(duì)該函數(shù)的詳細(xì)解讀:
1、_frameCallbackScheduled = false;
將 _frameCallbackScheduled
設(shè)為 false,表示下一幀還沒(méi)有調(diào)度。
2、 if (!hasListeners) { return; }
如果沒(méi)有監(jiān)聽(tīng)器,則直接返回。
3、 assert(_nextFrame != null);
斷言 _nextFrame
不為空。
4、 _isFirstFrame() || _hasFrameDurationPassed(timestamp)
如果是第一幀或者幀時(shí)間已經(jīng)超過(guò)了 _frameDuration
,則進(jìn)行以下操作:
5、 _emitFrame(ImageInfo(image: _nextFrame!.image.clone(), scale: _scale, debugLabel: debugLabel));
發(fā)出 ImageInfo
事件,將 _nextFrame
的圖像信息作為參數(shù)傳入。
6、 _shownTimestamp = timestamp;
更新 _shownTimestamp
為當(dāng)前時(shí)間戳。
7、 _frameDuration = _nextFrame!.duration;
更新 _frameDuration
為 _nextFrame
的幀間隔時(shí)間。
8、 _nextFrame!.image.dispose(); _nextFrame = null;
釋放 _nextFrame
的圖像資源并將 _nextFrame
設(shè)為 null。
9、 final int completedCycles = _framesEmitted ~/ _codec!.frameCount;
計(jì)算已經(jīng)完成的循環(huán)次數(shù)。
10、 _codec!.repetitionCount == -1 || completedCycles <= _codec!.repetitionCount
如果循環(huán)次數(shù)為 -1(表示無(wú)限循環(huán))或者已經(jīng)完成的循環(huán)次數(shù)小于等于 _codec
的循環(huán)次數(shù),則進(jìn)行以下操作:
11、 _decodeNextFrameAndSchedule();
解碼下一幀并調(diào)度下一幀的繪制。
12、 final Duration delay = _frameDuration! - (timestamp - _shownTimestamp);
計(jì)算下一幀需要延遲的時(shí)間。
13、_timer = Timer(delay * timeDilation, () { _scheduleAppFrame(); });
使用計(jì)時(shí)器來(lái)實(shí)現(xiàn)下一幀的延遲繪制。延遲時(shí)間為 delay
乘以 timeDilation
(可以通過(guò)調(diào)用 timeDilation = x
來(lái)改變時(shí)間流逝的速度)。當(dāng)計(jì)時(shí)器觸發(fā)時(shí),將調(diào)用 _scheduleAppFrame
來(lái)調(diào)度下一幀的繪制。
void addListener(ImageStreamListener listener) { if (!hasListeners && _codec != null && (_currentImage == null || _codec!.frameCount > 1)) { _decodeNextFrameAndSchedule(); } super.addListener(listener); }
這個(gè)方法是 ImageStreamCompleter
類的方法,用于向 ImageStreamCompleter
添加監(jiān)聽(tīng)器。當(dāng)?shù)谝粋€(gè)監(jiān)聽(tīng)器被添加到 ImageStreamCompleter
上時(shí),會(huì)檢查 _codec
是否為 null,如果不為 null 并且有多幀圖像或者當(dāng)前圖像為 null,則會(huì)調(diào)用 _decodeNextFrameAndSchedule()
方法開(kāi)始解碼下一幀圖像并計(jì)劃渲染。這樣做是為了確保在第一個(gè)監(jiān)聽(tīng)器被添加到 ImageStreamCompleter
上時(shí)就開(kāi)始解碼下一幀圖像并在下一幀渲染完成后通知所有監(jiān)聽(tīng)器。如果 _codec
為 null 或者當(dāng)前圖像為單幀圖像,則不會(huì)調(diào)用 _decodeNextFrameAndSchedule()
方法。在這個(gè)方法中,調(diào)用了 super.addListener(listener)
將監(jiān)聽(tīng)器添加到監(jiān)聽(tīng)器列表中。
void removeListener(ImageStreamListener listener) { super.removeListener(listener); if (!hasListeners) { _timer?.cancel(); _timer = null; } }
removeListener
方法用于從 MultiFrameImageStreamCompleter
中移除給定的 ImageStreamListener
。當(dāng)移除后,如果該對(duì)象不再有任何監(jiān)聽(tīng)器,就會(huì)取消定時(shí)器 _timer
。
具體來(lái)說(shuō),該方法會(huì)先調(diào)用父類的 removeListener
方法,將該監(jiān)聽(tīng)器從監(jiān)聽(tīng)器列表中移除。接著,如果此時(shí) hasListeners
為 false
,說(shuō)明沒(méi)有任何監(jiān)聽(tīng)器,就會(huì)取消 _timer
定時(shí)器,以便釋放資源。
void _maybeDispose() { super._maybeDispose(); if (_disposed) { _chunkSubscription?.onData(null); _chunkSubscription?.cancel(); _chunkSubscription = null; } }
_maybeDispose()
是一個(gè)用來(lái)釋放資源的方法,當(dāng)圖片流不再被監(jiān)聽(tīng)時(shí)調(diào)用。它首先調(diào)用父類的_maybeDispose()
方法,以處理父類中的一些釋放資源的邏輯。然后它會(huì)檢查_disposed
屬性是否為true,如果是,則取消并置空_chunkSubscription
,這個(gè)對(duì)象是用來(lái)訂閱圖像數(shù)據(jù)塊的流。這樣做是為了釋放相關(guān)的資源,以防止內(nèi)存泄漏。
到此,相信大家對(duì)“Flutter MultiFrameImageStreamCompleter是什么”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。