Funktions-Flags und Canary-Modus

Media3 bietet neue Optimierungen, Verhaltensverbesserungen und Architekturverbesserungen in allen Bibliotheken. Diese Änderungen werden vor der Veröffentlichung gründlich getestet und überprüft. Media3 enthält jedoch eine zentrale Infrastruktur für Funktions-Flags (androidx.media3.common.Flags), die sich auf den Canary-Modus konzentriert. So können Anwendungen steuern, wie und wann neue Verhaltensweisen übernommen werden.

Auf dieser Seite wird erläutert, wie der Canary-Modus und Funktions-Flags funktionieren und wie Sie sie in Ihrer Anwendung verwenden können, um Upgraderisiken bei Releases zu reduzieren, kontrollierte Roll-outs auszuführen und Notfall-Rollbacks zu konfigurieren.

Übersicht

Im Canary-Modus werden neu eingeführte, verifizierte Bibliotheksverbesserungen unter einer einheitlichen, globalen Ein/Aus-Schaltfläche zusammengefasst:

  • Standardmäßig aktiviert: In Standard-Media3-Releases ist der Canary-Modus standardmäßig aktiviert (true). So profitiert Ihre App automatisch von sicheren Leistungsverbesserungen, Optimierungen und neuen Funktionen.
  • Deaktivierung und Rollback: Anwendungen können den Canary-Modus global deaktivieren oder bestimmte Funktions-Flags zur Laufzeit einzeln überschreiben.
  • Keine komplexe Verkabelung: Flags werden über die zentrale Klasse Flags im Modul common verwaltet. Dadurch sind keine benutzerdefinierten Setter oder Konfigurationsobjekte für verschiedene Komponenten erforderlich.

Warum sollten Sie Flags in Ihrer App verwenden?

Der Canary-Modus ist standardmäßig aktiviert. Die Einbindung von Flags in die Konfiguration oder das Experimentier-Framework Ihrer App bietet jedoch mehrere Vorteile:

1. Risiko von Versionsupgrades minimieren

Das Upgrade von Haupt- oder Nebenversionen der Bibliothek in der Produktion kann zu unerwarteten Interaktionen auf bestimmten OEM-Geräten oder benutzerdefinierten Medienpipelines führen. Wenn Sie Media3 aktualisieren, können Sie den Canary-Modus und die Remote-Konfiguration für einen sichereren Upgrade-Ablauf über Releases hinweg verwenden:

  1. Update mit deaktiviertem Canary-Modus veröffentlichen: Wenn Sie eine neue Media3-Version mit deaktiviertem Canary-Modus veröffentlichen, bleibt der Funktionsumfang der vorherigen Version (mit aktiviertem Canary-Modus) weitgehend erhalten. So können Sie die grundlegende Stabilität der Bibliothek und allgemeine Fehlerkorrekturen in der Produktion überprüfen, bevor Sie neue Verhaltensweisen übernehmen.
  2. Canary-Modus schrittweise aktivieren: Verwenden Sie die Remote-Konfiguration, um den Canary-Modus schrittweise für Nutzerkohorten zu aktivieren (z. B. 5 % → 25 % → 100 %) oder führen Sie A/B-Tests durch, um die Auswirkungen auf die Messwerte zu messen.
  3. Bei Bedarf zurücksetzen: Wenn auf bestimmten Geräten eine Regression auftritt, können Sie den Canary-Modus oder die entsprechende Flag-ID per Remotezugriff deaktivieren, ohne dass eine Notfallversion der App erforderlich ist.
  4. Als Standard übernehmen: Lassen Sie den Canary-Modus nach der Überprüfung standardmäßig aktiviert, bis das nächste Versionsupgrade erfolgt. Dann werden die zuvor überprüften Canary-Funktionen dauerhaft übernommen.

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. Kontrollierte gestaffelte Releases und A/B-Tests

Anstatt 100 % Ihrer Nutzer auf einmal mit neuen Bibliotheksverhalten zu konfrontieren, können Sie Flags mit einem Remote-Konfigurations- oder Experimentiersystem verbinden.

Du kannst den Canary-Modus schrittweise aktivieren (z. B. 5 % der Nutzer → 25 % → 100 %) oder A/B-Tests durchführen, um die Auswirkungen neuer Optimierungen auf wichtige Wiedergabemesswerte wie Startlatenz, Rebuffering-Rate und Wiedergabefehler zu messen.

3. Remote-Kill-Switch und schnelles Rollback

Wenn in der Produktion auf einem bestimmten Gerätemodell oder einer bestimmten Betriebssystemversion eine Regression auftritt, kannst du den Canary-Modus (oder die entsprechende Flag-ID) über die serverseitige Konfiguration remote deaktivieren. Dadurch wird die Bibliothek für betroffene Nutzer sofort auf das stabile, alte Fallback-Verhalten zurückgesetzt, ohne dass ein Notfall-Hotfix oder ein Binär-Release für die App erforderlich ist.

Canary-Modus und Flags konfigurieren

Die Flags API bietet Methoden zum Steuern des globalen Canary-Modus und einzelner Flag-Überschreibungen.

Globaler Canary-Modus

So aktivieren oder deaktivieren Sie alle Optimierungen, die im Canary-Modus enthalten sind:

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

Einzelne Flags überschreiben

Sie können bestimmte Flags auch unabhängig von der globalen Canary-Modus-Einstellung explizit aktivieren oder deaktivieren:

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

Explizite Überschreibungen haben immer Vorrang vor der globalen Canary-Modus-Standardeinstellung.

Flag-Status prüfen

So prüfen Sie, ob der Canary-Modus oder ein bestimmtes Flag in Ihrem Code aktiv ist:

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

Reihenfolge der Flag-Auflösung

Wenn Media3 prüft, ob eine Funktion zur Laufzeit aktiviert ist, wird der Status in der folgenden Reihenfolge aufgelöst:

  1. Explizites Überschreiben der Laufzeit: Wenn enableFlag oder disableFlag für die Flag-ID aufgerufen wurde, wird dieser Überschreibungswert zurückgegeben.
  2. Statischer Bibliotheksstatus: Wenn das Flag statisch deaktiviert ist (z. B. ein Feature in der frühen Entwicklungsphase, das noch nicht für den Canary-Modus bereit ist) oder statisch aktiviert ist (ein Feature, das den Canary-Modus verlassen hat und einen temporären Opt-out-Fallback beibehält), wird dieser statische Wert zurückgegeben.
  3. Canary-Modus-Status: Andernfalls folgt das Flag der globalen Canary-Modus-Einstellung (aktiviert, wenn der Canary-Modus true ist, deaktiviert, wenn er false ist).

Anforderungen an die Initialisierung

Konfiguriere Flags, bevor du Media3-Komponenten wie ExoPlayer, Transformer oder MediaSession instanziierst:

  • Frühe Einrichtung: Konfigurieren Sie Flags während des Anwendungsstarts (z. B. in Application.onCreate) oder vor der Initialisierung der Medienebene Ihrer App.
  • Threadsicherheit: Media3-Komponenten lesen Flags während der Instanziierung und geben diese Werte an interne Wiedergabe- und Verarbeitungsthreads weiter. Achten Sie darauf, dass die Flag-Konfiguration vor dem Erstellen von Media3-Komponenten erfolgt. Das Ändern von Flags, während Player oder Transformer aktiv ausgeführt werden, wird nicht unterstützt.

In Remote-Konfiguration einbinden

Eine empfohlene Architektur besteht darin, drei Parameter auf Ihrer Remote-Konfigurationsplattform bereitzustellen:

  • media3_canary_mode_enabled (boolesch): Globale Ein-/Aus-Schaltfläche für den Canary-Modus (Standardwert ist true).
  • media3_force_enable_flags (Liste von Ganzzahlen): Bestimmte Flag-IDs, die für gezielte Tests zwingend aktiviert werden sollen.
  • media3_force_disable_flags (Liste von Ganzzahlen): Bestimmte Flag-IDs, die zwangsweise deaktiviert werden sollen, wenn ein Problem erkannt wird.

Wenden Sie diese Werte beim Start an, bevor Sie Player-Instanzen erstellen:

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);
  }
}

Mit Flags testen

Wenn Sie Unit-Tests oder Robolectric-Tests für Codepfade schreiben, die von Funktions-Flags betroffen sind, verwenden Sie androidx.media3.test.utils.Media3FlagsRule (verfügbar im Modul media3-test-utils), um zu verhindern, dass sich die Tests gegenseitig im statischen Zustand beeinflussen.

Automatische Bereinigung mit Media3FlagsRule

Wenn Sie Media3FlagsRule hinzufügen, werden alle Flag-Überschreibungen und Canary-Modus-Änderungen nach jedem Test automatisch zurückgesetzt:

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...
  }
}

Parametrisierte Tests

Sie können Media3FlagsRule mit den Annotationen @BindFlag und @BindCanaryMode kombinieren, um parametrisierte Tests für verschiedene Flag-Zustände auszuführen:

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.
  }
}

Diagnose und Logging

Media3 bietet Diagnosetools, mit denen du aktive Flags und Canary-Modus-Status während der Wiedergabeinitialisierung und zur Laufzeit prüfen kannst.

Initialisierungslogs

Wenn eine ExoPlayer-Instanz initialisiert wird, werden der aktive Canary-Status und alle expliziten Überschreibungen zusammen mit der Bibliotheksversion in logcat ausgegeben:

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

Wenn Überschreibungen aktiv sind, werden nur die Abweichungen vom Standardstatus aufgeführt:

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

Programmatische Prüfung

Sie können den formatierten Diagnosestring oder die Listen mit Abfrageüberschreibungen programmatisch abrufen, um beispielsweise den Flag-Status in die Absturzberichte oder Analysen Ihrer App aufzunehmen:

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();

Lebenszyklus von Flags

Flags in Media3 sind temporär:

  1. Entwicklung: Eine neue Optimierung oder Funktion wird hinter einer Flag-ID in Flags.java hinzugefügt.
  2. Canary-Modus: Sobald die Funktion verifiziert und für die allgemeine Verwendung bereit ist, ist sie im Canary-Modus für alle Releases aktiv.
  3. Hochstufung und Bereinigung: Nach einer Testphase, in der die Stabilität in der Produktion nachgewiesen wurde, wird das Feature in der Bibliothek dauerhaft aktiviert. Die Flag-Konstante, die Fallback-Codepfade und die bedingten Prüfungen werden entfernt.

Wenn Sie ein Problem mit einem Funktions-Flag feststellen, melden Sie es bitte im AndroidX Media3 GitHub Issue Tracker, damit das Team es beheben kann, bevor die Funktion dauerhaft aktiviert wird.