溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Flutter MultiFrameImageStreamCompleter是什么

發(fā)布時(shí)間:2023-04-20 11:32:04 來(lái)源:億速云 閱讀:117 作者:iii 欄目:開(kāi)發(fā)技術(shù)

本篇內(nèi)容主要講解“Flutter MultiFrameImageStreamCompleter是什么”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Flutter MultiFrameImageStreamCompleter是什么”吧!

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) MultiFrameImageStreamCompleteraddListener() 方法被調(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) MultiFrameImageStreamCompleterdispose() 方法被調(diào)用時(shí),它會(huì)將其所有子 ImageStreamCompleterdispose() 方法依次調(diào)用,以釋放所有資源,并取消所有未處理的幀請(qǐng)求。同時(shí),它還會(huì)確保在釋放資源之前將所有錯(cuò)誤通知給其監(jiān)聽(tīng)器。

_handleCodecReady

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)有地方可以展示它。

_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();
}
  • 這個(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ù)器。

_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 類的一個(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 屬性中。如果 _codecframeCount 屬性為 1,說(shuō)明這是一個(gè)靜態(tài)圖像,直接使用 _emitFrame() 方法發(fā)布該幀圖像;否則,調(diào)用 _scheduleAppFrame() 方法安排下一幀的發(fā)布。

_emitFrame(重要方法, 通知監(jiān)聽(tīng)器觸發(fā)回調(diào),更新UI)

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ì)劃下一幀的顯示并等待下一幀的解碼。

_scheduleAppFrame

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)。

_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ù),用于處理多幀圖像的邏輯。下面是對(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)度下一幀的繪制。

addListener

void addListener(ImageStreamListener listener) {
  if (!hasListeners &amp;&amp; _codec != null &amp;&amp; (_currentImage == null || _codec!.frameCount &gt; 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)器列表中。

removeListener

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í) hasListenersfalse,說(shuō)明沒(méi)有任何監(jiān)聽(tīng)器,就會(huì)取消 _timer 定時(shí)器,以便釋放資源。

_maybeDispose

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í)!

向AI問(wèn)一下細(xì)節(jié)

免責(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)容。

AI