功能旗標和 Canary 模式

Media3 在其程式庫中導入新的最佳化、行為改善和架構強化功能。雖然這些變更在發布前都經過徹底測試和驗證,但 Media3 包含以Canary 模式為中心的集中式功能旗標基礎架構 (androidx.media3.common.Flags),可讓應用程式控管採用新行為的方式和時間。

本頁面說明初期測試版本模式和功能旗標的運作方式,以及如何在應用程式中使用這些功能,降低各版本發布的升級風險、執行控管式推出作業,以及設定緊急復原。

總覽

Canary 模式會將新推出的經驗證程式庫改善項目,歸入統一的全域切換鈕:

  • 預設啟用:在標準 Media3 版本中,Canary 模式預設為啟用 (true)。這樣一來,應用程式就能自動享有安全效能強化、最佳化和新功能。
  • 選擇不採用和復原功能:應用程式可以在執行階段全面停用 Canary 模式,或個別覆寫特定功能旗標。
  • 完全不需複雜的連結:旗標是透過 common 模組中的中央 Flags 類別管理,因此不需要在不同元件中使用自訂 setter 或設定物件。

為什麼要在應用程式中使用標記?

雖然 Canary 模式預設為啟用,但將 Flags 整合至應用程式的設定或實驗架構,可帶來多項好處:

1. 降低版本升級風險

在正式環境中升級程式庫的主要或次要版本,可能會在特定原始設備製造商 (OEM) 裝置或自訂媒體管道上,引發非預期的互動。更新 Media3 時,您可以使用初期測試模式和遠端設定,在不同版本之間採用更安全的升級流程:

  1. 在停用初期測試模式的情況下推送更新:發布關閉初期測試模式的新 Media3 版本,大致上會保留先前版本 (啟用初期測試模式) 的功能集,讓您在採用新行為前,先驗證正式環境中的基準程式庫穩定性和一般錯誤修正。
  2. 逐步啟用初期測試模式:使用遠端設定,在使用者同類群組中分階段啟用初期測試模式 (例如 5% → 25% → 100%),或執行 A/B 實驗來評估指標影響。
  3. 視需要復原:如果特定裝置發生迴歸,可以從遠端停用 Canary 模式或特定旗標 ID,不必緊急發布應用程式。
  4. 預設採用:驗證完成後,預設啟用初期測試模式,直到下次版本升級,先前驗證的初期測試功能才會永久生效。

Kotlin

// Enable or disable Canary mode globally (defaults to true).
Flags.setCanaryModeEnabled(false)

Java

// Enable or disable Canary mode globally (defaults to true).
Flags.setCanaryModeEnabled(false);

2. 控管階段式推出和 A/B 測試

您可以將 Flags 連接至遠端設定或實驗系統,而不是一次向所有使用者公開新的程式庫行為。

您可以分階段啟用 Canary 模式 (例如 5% 的使用者 → 25% → 100%),或執行 A/B 實驗,評估新最佳化措施對核心播放指標的影響,例如啟動延遲時間、重新緩衝處理率和播放失敗率。

3. 遠端終止參數和快速復原

如果特定裝置型號或 OS 版本的正式版發生迴歸,您可以使用伺服器端設定,從遠端停用 Canary 模式 (或負責的特定旗標 ID)。這會立即將程式庫還原為穩定、舊版的備用行為,適用於受影響的使用者,不需要緊急應用程式 hotfix 或二進位檔版本。

設定初期測試模式和旗標

Flags API 提供控制全域 Canary 模式和個別旗標覆寫的方法。

全域 Canary 模式

如要啟用或停用 Canary 模式涵蓋的所有強化功能,請按照下列步驟操作:

Kotlin

// Enable or disable Canary mode globally (defaults to true).
Flags.setCanaryModeEnabled(false)

Java

// Enable or disable Canary mode globally (defaults to true).
Flags.setCanaryModeEnabled(false);

覆寫個別旗標

您也可以明確啟用或停用特定旗標,不受全域 Canary 模式設定影響:

Kotlin

// Explicitly enable an experimental flag.
Flags.enableFlag(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION)

// Explicitly disable a flag.
Flags.disableFlag(Flags.FLAG_DYNAMIC_SCHEDULING)

Java

// Explicitly enable an experimental flag.
Flags.enableFlag(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION);

// Explicitly disable a flag.
Flags.disableFlag(Flags.FLAG_DYNAMIC_SCHEDULING);

明確覆寫的優先順序一律高於全域初期測試模式預設值。

正在檢查標記狀態

如要檢查程式碼是否啟用 Canary 模式或特定旗標,請按照下列步驟操作:

Kotlin

// Query whether Canary mode is active globally.
val isCanaryActive = Flags.isCanaryModeEnabled()

// Query whether a specific flag is active.
val isFeatureActive = Flags.isEnabled(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION)

Java

// Query whether Canary mode is active globally.
boolean isCanaryActive = Flags.isCanaryModeEnabled();

// Query whether a specific flag is active.
boolean isFeatureActive = Flags.isEnabled(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION);

旗標解析順序

Media3 在執行階段檢查功能是否啟用時,會依下列順序解析狀態:

  1. 明確執行階段覆寫:如果針對旗標 ID 呼叫 enableFlag 或 disableFlag,系統會傳回該覆寫值。
  2. 靜態程式庫狀態:如果旗標已靜態停用 (例如尚未準備好進入 Canary 模式的早期開發功能),或靜態啟用 (從 Canary 模式畢業的功能,保留暫時停用的備用方案),則會傳回該靜態值。
  3. Canary 模式狀態:否則,旗標會遵循全域 Canary 模式設定 (如果 Canary 模式為 true,則啟用;如果為 false,則停用)。

初始化規定

請先設定旗標,再例項化任何 Media3 元件 (例如 ExoPlayer、Transformer 或 MediaSession):

  • 提早設定:在應用程式啟動期間 (例如在 Application.onCreate 內) 或初始化應用程式的媒體層之前,設定 Flags。
  • 執行緒安全:Media3 元件會在例項化期間讀取旗標,並將這些值傳播至內部播放和處理執行緒。請務必先設定旗標,再建立 Media3 元件。不支援在播放器或轉換器執行時修改標記。

與遠端設定整合

建議的架構是在遠端設定平台中公開三個參數:

  • media3_canary_mode_enabled (布林值):Canary 模式的全域切換鈕 (預設為 true)。
  • media3_force_enable_flags (整數清單):要強制啟用指定試用版的特定旗標 ID。
  • media3_force_disable_flags (整數清單):如果發現問題,要強制停用的特定旗標 ID。

請在啟動期間套用這些值,然後再建立播放器執行個體:

Kotlin

fun initializeMedia3Flags(
  canaryModeEnabled: Boolean,
  forceEnabledFlags: List<Int>,
  forceDisabledFlags: List<Int>,
) {
  // 1. Set global Canary mode state.
  Flags.setCanaryModeEnabled(canaryModeEnabled)

  // 2. Apply explicit force-enable overrides.
  for (flagId in forceEnabledFlags) {
    Flags.enableFlag(flagId)
  }

  // 3. Apply explicit force-disable overrides (remote kill switch).
  for (flagId in forceDisabledFlags) {
    Flags.disableFlag(flagId)
  }
}

Java

public static void initializeMedia3Flags(
    boolean canaryModeEnabled,
    List<Integer> forceEnabledFlags,
    List<Integer> forceDisabledFlags) {
  // 1. Set global Canary mode state.
  Flags.setCanaryModeEnabled(canaryModeEnabled);

  // 2. Apply explicit force-enable overrides.
  for (int flagId : forceEnabledFlags) {
    Flags.enableFlag(flagId);
  }

  // 3. Apply explicit force-disable overrides (remote kill switch).
  for (int flagId : forceDisabledFlags) {
    Flags.disableFlag(flagId);
  }
}

使用旗標進行測試

為受功能旗標影響的程式碼路徑編寫單元測試或 Robolectric 測試時,請使用 androidx.media3.test.utils.Media3FlagsRule (位於 media3-test-utils 模組),確保測試不會互相干擾靜態狀態。

使用 Media3FlagsRule 自動清除

新增 Media3FlagsRule 可確保每次測試後,所有旗標覆寫和 Canary 模式修改都會自動重設:

Kotlin

class MyPlayerTest {

  @get:Rule val flagsRule = Media3FlagsRule(this)

  @Test
  fun playback_withFeatureFlagEnabled() {
    Flags.enableFlag(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION)
    // Run test with the flag enabled...
  }
}

Java

public static class MyPlayerTest {

  @Rule public final Media3FlagsRule flagsRule = new Media3FlagsRule(this);

  @Test
  public void playback_withFeatureFlagEnabled() {
    Flags.enableFlag(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION);
    // Run test with the flag enabled...
  }
}

參數化測試

您可以結合 Media3FlagsRule 與 @BindFlag 和 @BindCanaryMode 註解,在不同旗標狀態下執行參數化測試:

Kotlin

class MyParameterizedPlayerTest {

  @BindCanaryMode var canaryMode: Boolean = false

  @BindFlag(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION) var perStreamProgression: Boolean = false

  @get:Rule val flagsRule = Media3FlagsRule(this)

  @Test
  fun playback() {
    // Flags are automatically set for each test parameter combination.
  }
}

Java

public static class MyParameterizedPlayerTest {

  @BindCanaryMode public boolean canaryMode;

  @BindFlag(Flags.FLAG_PER_STREAM_MEDIA_PROGRESSION)
  public boolean perStreamProgression;

  @Rule public final Media3FlagsRule flagsRule = new Media3FlagsRule(this);

  @Test
  public void playback() {
    // Flags are automatically set for each test parameter combination.
  }
}

診斷和記錄

Media3 提供診斷工具,可在播放初始化和執行階段,驗證有效旗標和 Canary 模式狀態。

初始化記錄

ExoPlayer 執行個體初始化時,會將有效的 Canary 狀態和任何明確覆寫值,連同程式庫版本一併輸出至 logcat:

ExoPlayerImpl: Init 59a18ab [AndroidXMedia3/1.10.1] [flame, Pixel 4, Google, 33] [Canary:ON]

如果覆寫值處於啟用狀態,系統只會列出與預設狀態不同的值:

ExoPlayerImpl: Init 59a18ab [AndroidXMedia3/1.10.1] [flame, Pixel 4, Google, 33] [Canary:ON, Overrides: enabled=[1], disabled=[3]]

程式輔助檢查

您可以透過程式輔助方式擷取格式化的診斷字串或查詢覆寫清單 (例如在應用程式的當機報告或數據分析中加入標記狀態):

Kotlin

// Get formatted string representing active Canary state and overrides.
val flagLogString = Flags.getLogString()

// Query explicit override IDs if needed.
val enabledFlags = Flags.getEnabledOverrides()
val disabledFlags = Flags.getDisabledOverrides()

Java

// Get formatted string representing active Canary state and overrides.
String flagLogString = Flags.getLogString();

// Query explicit override IDs if needed.
int[] enabledFlags = Flags.getEnabledOverrides();
int[] disabledFlags = Flags.getDisabledOverrides();

標記生命週期

Media3 中的旗標是暫時性的:

  1. 開發:在 Flags.java 的旗標 ID 後方新增最佳化或功能。
  2. 初期測試模式:功能通過驗證並可供一般使用後,就會在各版本的初期測試模式中啟用。
  3. 升級和清理:經過一段時間的觀察,證明功能在正式環境中穩定運作後,就會升級為永久啟用,並移除旗標常數、備用程式碼路徑和條件檢查。

如果遇到功能旗標相關問題,請在 AndroidX Media3 GitHub Issue Tracker 提交報告,讓團隊在永久升級前解決問題。