Flag di funzionalità e modalità Canary

Media3 introduce nuove ottimizzazioni, miglioramenti del comportamento e miglioramenti dell'architettura nelle sue librerie. Sebbene queste modifiche vengano testate e verificate accuratamente prima del rilascio, Media3 include un'infrastruttura di flag di funzionalità centralizzata (androidx.media3.common.Flags) incentrata sulla modalità Canary per consentire alle applicazioni di controllare come e quando vengono adottati i nuovi comportamenti.

Questa pagina spiega come funzionano la modalità canary e i flag di funzionalità e come puoi utilizzarli nella tua applicazione per ridurre i rischi di upgrade tra le release, eseguire implementazioni controllate e configurare rollback di emergenza.

Panoramica

La modalità Canary raggruppa i miglioramenti della libreria verificati e appena introdotti in un'unica opzione di attivazione/disattivazione globale:

  • Attivata per impostazione predefinita: nelle release Media3 standard, la modalità Canary è attivata per impostazione predefinita (true). In questo modo, la tua app beneficia automaticamente di miglioramenti delle prestazioni, ottimizzazioni e nuove funzionalità sicuri.
  • Funzionalità di disattivazione e rollback: le applicazioni possono disattivare la modalità Canary a livello globale o eseguire l'override di flag di funzionalità specifici singolarmente in fase di runtime.
  • Nessuna complessità di cablaggio: i flag vengono gestiti tramite la classe centrale Flags nel modulo common, eliminando la necessità di setter o oggetti di configurazione personalizzati tra i diversi componenti.

Perché utilizzare i flag nella tua app?

Sebbene la modalità Canary sia attiva per impostazione predefinita, l'integrazione di Flags con la configurazione o il framework di sperimentazione della tua app offre diversi vantaggi:

1. Mitigazione del rischio di upgrade della versione

L'upgrade delle versioni principali o secondarie della libreria in produzione può introdurre interazioni impreviste su dispositivi OEM specifici o pipeline multimediali personalizzate. Quando aggiorni Media3, puoi utilizzare la modalità canary e la configurazione remota per un flusso di upgrade più sicuro tra le release:

  1. Invia l'aggiornamento con la modalità Canary disattivata: il rilascio di una nuova versione di Media3 con la modalità Canary disattivata conserva approssimativamente l'insieme di funzionalità della release precedente (con Canary attivato), consentendoti di verificare la stabilità della libreria di base e le correzioni di bug generali in produzione prima di adottare nuovi comportamenti.
  2. Attiva progressivamente la modalità Canary: utilizza la configurazione remota per attivare la modalità Canary in più fasi tra le coorti di utenti (ad esempio, 5% → 25% → 100%) o esegui esperimenti A/B per misurare l'impatto delle metriche.
  3. Esegui il rollback se necessario: se si verifica una regressione su dispositivi specifici, disattiva da remoto la modalità Canary o l'ID flag specifico senza richiedere una release di emergenza dell'app.
  4. Adotta come predefinita: una volta completata la verifica, lascia attiva la modalità Canary per impostazione predefinita fino al successivo upgrade della versione, quando le funzionalità Canary verificate in precedenza diventano permanenti.

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. Release temporanee controllate e test A/B

Anziché esporre il 100% degli utenti ai nuovi comportamenti della libreria contemporaneamente, puoi collegare Flags a un sistema di configurazione o sperimentazione remota.

Puoi attivare la modalità Canary in più fasi (ad esempio, 5% degli utenti → 25% → 100%) o eseguire esperimenti A/B per misurare l'impatto delle nuove ottimizzazioni sulle metriche di riproduzione principali, come la latenza di avvio, la frequenza di buffering e gli errori di riproduzione.

3. Kill switch remoto e rollback rapido

Se si verifica una regressione in produzione su un determinato modello di dispositivo o una determinata versione del sistema operativo, puoi disattivare da remoto la modalità Canary (o l'ID flag specifico responsabile) utilizzando la configurazione lato server. In questo modo, la libreria viene immediatamente ripristinata al suo comportamento di riserva stabile e legacy per gli utenti interessati, senza richiedere un hotfix di emergenza dell'app o una release binaria.

Configurazione della modalità e dei flag canary

L'API Flags fornisce metodi per controllare la modalità Canary globale e gli override dei singoli flag.

Modalità canary globale

Per attivare o disattivare tutti i miglioramenti coperti dalla modalità 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);

Override dei singoli flag

Puoi anche attivare o disattivare esplicitamente flag specifici indipendentemente dall'impostazione globale della modalità 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);

Gli override espliciti hanno sempre la precedenza sull'impostazione predefinita della modalità canary globale.

Controllo dello stato della segnalazione

Per verificare se la modalità Canary o un flag specifico sono attivi nel tuo codice:

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

Ordine di risoluzione dei flag

Quando Media3 verifica se una funzionalità è abilitata in fase di runtime, risolve lo stato nel seguente ordine:

  1. Override esplicito del runtime: se è stato chiamato enableFlag o disableFlag per l'ID flag, viene restituito il valore di override.
  2. Stato della libreria statica: se il flag è disattivato staticamente (ad esempio, una funzionalità in fase di sviluppo iniziale non ancora pronta per la modalità Canary) o attivato staticamente (una funzionalità che è passata dalla modalità Canary mantenendo un fallback di disattivazione temporaneo), viene restituito il valore statico.
  3. Stato della modalità canary: in caso contrario, il flag segue l'impostazione globale della modalità canary (attivata se la modalità canary è true, disattivata se è false).

Requisiti di inizializzazione

Configura i flag prima di creare un'istanza di qualsiasi componente Media3 (come ExoPlayer, Transformer o MediaSession):

  • Configurazione anticipata: configura Flags durante l'avvio dell'applicazione (ad esempio, all'interno di Application.onCreate) o prima di inizializzare il livello multimediale dell'app.
  • Thread safety: i componenti Media3 leggono i flag durante la creazione dell'istanza e propagano questi valori ai thread di riproduzione ed elaborazione interni. Assicurati che la configurazione dei flag avvenga prima della creazione dei componenti Media3. La modifica dei flag mentre i player o i transformer sono in esecuzione non è supportata.

Integrazione con la configurazione remota

Un'architettura consigliata consiste nell'esporre tre parametri nella piattaforma di configurazione remota:

  • media3_canary_mode_enabled (booleano): attivazione/disattivazione globale della modalità canary (il valore predefinito è true).
  • media3_force_enable_flags (elenco di numeri interi): ID flag specifici da forzare l'attivazione per le prove mirate.
  • media3_force_disable_flags (elenco di numeri interi): ID flag specifici da disattivare forzatamente se viene rilevato un problema.

Applica questi valori durante l'avvio prima di creare le istanze del player:

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

Test con i flag

Quando scrivi test delle unità o test Robolectric per i percorsi del codice interessati dai flag di funzionalità, utilizza androidx.media3.test.utils.Media3FlagsRule (disponibile nel modulo media3-test-utils) per assicurarti che i test non interferiscano con lo stato statico degli altri.

Pulizia automatica con Media3FlagsRule

L'aggiunta di Media3FlagsRule garantisce che tutti gli override dei flag e le modifiche alla modalità Canary vengano reimpostati automaticamente dopo ogni test:

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

Test con parametri

Puoi combinare Media3FlagsRule con le annotazioni @BindFlag e @BindCanaryMode per eseguire test parametrizzati negli stati dei flag:

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

Diagnostica e logging

Media3 fornisce strumenti di diagnostica per verificare i flag attivi e gli stati della modalità Canary durante l'inizializzazione della riproduzione e in fase di runtime.

Log di inizializzazione

Quando viene inizializzata un'istanza ExoPlayer, stampa lo stato Canary attivo e gli eventuali override espliciti in logcat insieme alla versione della libreria:

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

Se gli override sono attivi, vengono elencate solo le deviazioni dallo stato predefinito:

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

Ispezione programmatica

Puoi recuperare la stringa di diagnostica formattata o gli elenchi di override delle query in modo programmatico (ad esempio, per includere lo stato dei flag nei report sugli arresti anomali o nelle analisi della tua app):

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

Ciclo di vita dei flag

I flag in Media3 sono progettati per essere temporanei:

  1. Sviluppo: una nuova ottimizzazione o funzionalità viene aggiunta dietro un ID flag in Flags.java.
  2. Modalità Canary: una volta verificata e pronta per l'uso generale, la funzionalità è attiva in modalità Canary in tutte le release.
  3. Promozione e pulizia: dopo un periodo di prova che dimostra la stabilità in produzione, la funzionalità viene promossa per essere abilitata in modo permanente nella libreria e la costante del flag, i percorsi del codice di riserva e i controlli condizionali vengono rimossi.

Se riscontri un problema con un flag di funzionalità, invia una segnalazione nel Issue Tracker di GitHub di AndroidX Media3 in modo che il team possa risolverlo prima della promozione permanente.