Przewodnik po migracji na AndroidaX Media3

Aplikacje, które obecnie korzystają z samodzielnej biblioteki com.google.android.exoplayer2 i biblioteki androidx.media, powinny zostać przeniesione do androidx.media3. Użyj skryptu migracji, aby przenieść pliki kompilacji Gradle, pliki źródłowe Java i Kotlin oraz pliki układu XML z platformy ExoPlayer 2.19.1 do AndroidaX Media3 1.1.1.

Przegląd

Przed migracją przeczytaj sekcje poniżej, aby dowiedzieć się więcej o korzyściach związanych z nowymi interfejsami API i interfejsach API do migracji oraz o wymaganiach wstępnych, jakie powinien spełniać projekt aplikacji.

Dlaczego warto przejść na Jetpack Media3

  • To nowa wersja ExoPlayer, natomiast com.google.android.exoplayer2 została wycofana.
  • Uzyskaj dostęp do interfejsu Player API w różnych komponentach/procesach za pomocą MediaBrowser/MediaController.
  • Korzystaj z rozszerzonych funkcji interfejsów API MediaSession i MediaController.
  • Reklamuj możliwości odtwarzania przy użyciu szczegółowej kontroli dostępu.
  • Uprość aplikację, usuwając MediaSessionConnector i PlayerNotificationManager.
  • Zgodność wsteczna z interfejsami API klienta Media-compat (MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat)

Interfejsy API multimediów do migracji na AndroidX Media3

  • ExoPlayer i jego rozszerzenia
    Obejmuje to wszystkie moduły starszej wersji projektu ExoPlayer z wyjątkiem wycofanego modułu mediasession. Aplikacje lub moduły, w zależności od pakietów w com.google.android.exoplayer2, można przenieść za pomocą skryptu migracji.
  • MediaSessionConnector (w zależności od androidx.media.* pakietów androidx.media:media:1.4.3+)
    Usuń MediaSessionConnector i użyj zamiast niego androidx.media3.session.MediaSession.
  • MediaBrowserServiceCompat (w zależności od androidx.media.* pakietów androidx.media:media:1.4.3+)
    Przenieś podklasy androidx.media.MediaBrowserServiceCompat do wersji androidx.media3.session.MediaLibraryService i kod z MediaBrowserCompat.MediaItem do androidx.media3.common.MediaItem.
  • MediaBrowserCompat (w zależności od android.support.v4.media.* pakietów androidx.media:media:1.4.3+)
    Przeprowadź migrację kodu klienta za pomocą MediaBrowserCompat lub MediaControllerCompat, aby użyć androidx.media3.session.MediaBrowser z androidx.media3.common.MediaItem.

Wymagania wstępne

  1. Sprawdzanie, czy projekt jest pod kontrolą źródła

    Upewnij się, że zmiany zastosowane przez narzędzia do migracji oparte na skryptach można łatwo cofnąć. Jeśli nie masz jeszcze kontroli nad źródłem nad swoim projektem, to dobry moment, by zacząć od niego. Jeśli z jakiegoś powodu nie chcesz tego zrobić, przed rozpoczęciem migracji utwórz kopię zapasową projektu.

  2. Aktualizowanie aplikacji

    • Zalecamy zaktualizowanie projektu do najnowszej wersji biblioteki ExoPlayer i usunięcie wszystkich wywołań wycofanych metod. Jeśli zamierzasz użyć skryptu podczas migracji, musisz dopasować wersję, do której przeprowadzasz aktualizację, do wersji obsługiwaną przez skrypt.

    • Zwiększ wartość ComputeSdkVersion aplikacji do co najmniej 32.

    • Uaktualnij Gradle i wtyczkę Android Studio do obsługi Gradle do najnowszej wersji, która obsługuje zaktualizowane zależności wymienione powyżej. Na przykład:

      • Wersja wtyczki Androida do obsługi Gradle: 7.1.0
      • Wersja Gradle: 7.4
    • Zastąp wszystkie instrukcje importu symbolu wieloznacznego, które używają asterix (*), i używaj w pełni kwalifikowanych instrukcji importu: usuń instrukcje importu z symbolem wieloznacznym i użyj Android Studio, aby zaimportować pełne instrukcje (F2 – Alt/Enter, F2 – Alt/Enter, ...).

    • Przeprowadź migrację z com.google.android.exoplayer2.PlayerView do com.google.android.exoplayer2.StyledPlayerView. Jest to konieczne, ponieważ w AndroidzieX Media3 nie ma odpowiednika com.google.android.exoplayer2.PlayerView.

Migracja odtwarzacza ExoPlayer z obsługą skryptów

Skrypt ułatwia przejście z com.google.android.exoplayer2 do nowego pakietu i struktury modułów w androidx.media3. Skrypt stosuje w projekcie kilka testów poprawności, a w przypadku niepowodzenia weryfikacji wyświetla ostrzeżenia. W przeciwnym razie stosuje mapowania klas i pakietów ze zmienioną nazwą w zasobach projektu Gradle Androida napisanego w Javie lub Kotlin.

usage: ./media3-migration.sh [-p|-c|-d|-v]|[-m|-l [-x <path>] [-f] PROJECT_ROOT]
 PROJECT_ROOT: path to your project root (location of 'gradlew')
 -p: list package mappings and then exit
 -c: list class mappings (precedence over package mappings) and then exit
 -d: list dependency mappings and then exit
 -l: list files that will be considered for rewrite and then exit
 -x: exclude the path from the list of file to be changed: 'app/src/test'
 -m: migrate packages, classes and dependencies to AndroidX Media3
 -f: force the action even when validation fails
 -v: print the exoplayer2/media3 version strings of this script
 -h, --help: show this help text

Używanie skryptu migracji

  1. Pobierz skrypt migracji z tagu projektu ExoPlayer na GitHubie odpowiadający wersji, w której została zaktualizowana aplikacja:

    curl -o media3-migration.sh \
      "https://raw.githubusercontent.com/google/ExoPlayer/r2.19.1/media3-migration.sh"
    
  2. Ustaw skrypt jako wykonywalny:

    chmod 744 media3-migration.sh
    
  3. Uruchom skrypt z funkcją --help, aby poznać dostępne opcje.

  4. Uruchom skrypt z poleceniem -l, aby wyświetlić listę plików wybranych do migracji (użyj polecenia -f, aby wymusić wyświetlanie bez ostrzeżeń):

    ./media3-migration.sh -l -f /path/to/gradle/project/root
    
  5. Uruchom skrypt z użyciem funkcji -m, aby zmapować pakiety, klasy i moduły na Media3. Uruchomienie skryptu z opcją -m spowoduje zastosowanie zmian do wybranych plików.

    • Zatrzymaj przy błędzie weryfikacji bez wprowadzania zmian
    ./media3-migration.sh -m /path/to/gradle/project/root
    
    • Wymuszone wykonanie

    Jeśli skrypt wykryje naruszenie wymagań wstępnych, migrację można wymusić za pomocą flagi -f:

    ./media3-migration.sh -m -f /path/to/gradle/project/root
    
 # list files selected for migration when excluding paths
 ./media3-migration.sh -l -x "app/src/test/" -x "service/" /path/to/project/root
 # migrate the selected files
 ./media3-migration.sh -m -x "app/src/test/" -x "service/" /path/to/project/root

Wykonaj te czynności ręcznie po uruchomieniu skryptu z opcją -m:

  1. Sprawdź, jak skrypt zmienił kod: za pomocą narzędzia do porównywania różnic i rozwiąż potencjalne problemy (możesz zgłosić błąd, jeśli uważasz, że w skrypcie występuje ogólny problem, który został wprowadzony bez zastosowania opcji -f).
  2. Utwórz projekt: użyj ./gradlew clean build lub w Android Studio wybierz Plik > Synchronizuj projekt z Gradle Files, a następnie Utwórz > Oczyść projekt i Utwórz > Odbuduj projekt (monitoruj kompilację na karcie „Kompilacja – Kompilacja” w Android Studio;

Zalecane dalsze kroki:

  1. Napraw akceptację błędów związanych z korzystaniem z niestabilnych interfejsów API.
  2. Zastąp wycofane wywołania interfejsu API: użyj sugerowanego zastępczego interfejsu API. Najedź kursorem na ostrzeżenie w Android Studio i zapoznaj się z dokumentem JavaDoc wycofanego symbolu, aby dowiedzieć się, czego należy użyć zamiast danego wywołania.
  3. Posortować instrukcje importu: otwórz projekt w Android Studio, a następnie kliknij prawym przyciskiem myszy węzeł folderu pakietu w przeglądarce projektu i wybierz Optymalizuj importy przy pakietach zawierających zmienione pliki źródłowe.

Zamień MediaSessionConnector na androidx.media3.session.MediaSession

W starszej wersji MediaSessionCompat interfejs MediaSessionConnector odpowiadał za synchronizację stanu odtwarzacza ze stanem sesji i odbieranie poleceń od kontrolerów, które wymagały przekazania dostępu do odpowiednich metod odtwarzacza. W przypadku AndroidaX Media3 ten proces odbywa się bezpośrednio przez MediaSession, bez konieczności używania oprogramowania sprzęgającego.

  1. Usuń wszystkie odwołania i wykorzystanie MediaSessionConnector: jeśli do przeniesienia klas i pakietów ExoPlayer użyto automatycznego skryptu, prawdopodobnie Twój kod pozostawał w stanie nieskompilowanym w związku z MediaSessionConnector, którego nie można rozwiązać. Android Studio wyświetli uszkodzony kod, gdy spróbujesz skompilować lub uruchomić aplikację.

  2. W pliku build.gradle, w którym przechowujesz zależności, dodaj zależność implementacji do modułu sesji AndroidX Media3 i usuń starszą zależność:

    implementation "androidx.media3:media3-session:1.3.1"
    
  3. Zastąp MediaSessionCompat wartością androidx.media3.session.MediaSession.

  4. W witrynie kodu, w której utworzono starszą wersję MediaSessionCompat, użyj polecenia androidx.media3.session.MediaSession.Builder, aby utworzyć MediaSession. Przekaż odtwarzacz, aby utworzyć konstruktor sesji.

    val player = ExoPlayer.Builder(context).build()
    mediaSession = MediaSession.Builder(context, player)
        .setSessionCallback(MySessionCallback())
        .build()
    
  5. Zaimplementuj funkcję MySessionCallback zgodnie z wymaganiami aplikacji. Nie jest to obowiązkowe. Jeśli chcesz zezwolić kontrolerom na dodawanie elementów multimedialnych do odtwarzacza, zaimplementuj MediaSession.Callback.onAddMediaItems(). Obsługuje różne obecne i starsze metody interfejsu API, które dodają elementy multimedialne do odtwarzacza w celu ich odtwarzania. Obejmuje to metody MediaController.set/addMediaItems() kontrolera Media3 oraz metody TransportControls.prepareFrom*/playFrom* starszego interfejsu API. Przykładową implementację funkcji onAddMediaItems znajdziesz w PlaybackService aplikacji demonstracyjnej sesji.

  6. Zwolnij sesję multimediów w witrynie kodu, w której została zniszczona sesję przed migracją:

    mediaSession?.run {
      player.release()
      release()
      mediaSession = null
    }
    

Funkcja MediaSessionConnector w Media3

W tabeli poniżej znajdziesz interfejsy Media3 API, które obsługują funkcje wcześniej wdrożone w MediaSessionConnector.

Oprogramowanie sprzęgające sesji MediaSessionAndroidX Media3
CustomActionProvider MediaSession.Callback.onCustomCommand()/ MediaSession.setCustomLayout()
PlaybackPreparer MediaSession.Callback.onAddMediaItems() (prepare() jest wywoływane wewnętrznie)
QueueNavigator ForwardingPlayer
QueueEditor MediaSession.Callback.onAddMediaItems()
RatingCallback MediaSession.Callback.onSetRating()
PlayerNotificationManager DefaultMediaNotificationProvider/ MediaNotification.Provider

Przenieś MediaBrowserService do MediaLibraryService

AndroidX Media3 wprowadza MediaLibraryService, który zastępuje MediaBrowserServiceCompat. Dokument JavaDoc obiektu MediaLibraryService i jego klasa nadrzędna MediaSessionService stanowią dobre wprowadzenie do interfejsu API i asynchronicznego modelu programowania usługi.

MediaLibraryService jest zgodny wstecznie z MediaBrowserService. Aplikacja kliencka, która używa MediaBrowserCompat lub MediaControllerCompat, nadal działa bez zmian w kodzie po połączeniu z MediaLibraryService. Dla klienta jasno widać, czy aplikacja używa interfejsu MediaLibraryService czy starszej wersji MediaBrowserServiceCompat.

Diagram komponentów aplikacji z usługą, aktywnością i aplikacjami zewnętrznymi.
Rysunek 1. Omówienie komponentu aplikacji do multimediów
  1. Aby zgodność wsteczna działała, musisz zarejestrować oba interfejsy usługi z usługą w AndroidManifest.xml. W ten sposób klient znajdzie Twoją usługę według jej wymaganego interfejsu:

    <service android:name=".MusicService" android:exported="true">
        <intent-filter>
            <action android:name="androidx.media3.session.MediaLibraryService"/>
            <action android:name="android.media.browse.MediaBrowserService" />
        </intent-filter>
    </service>
    
  2. W pliku build.gradle, w którym przechowujesz zależności, dodaj zależność implementacji do modułu sesji AndroidX Media3 i usuń tę zależność:

    implementation "androidx.media3:media3-session:1.3.1"
    
  3. Zmień usługę tak, aby dziedziczyła ją z MediaLibraryService zamiast z MediaBrowserService. Jak już wspomnieliśmy, MediaLibraryService jest zgodny ze starszą wersją MediaBrowserService. W związku z tym ogólny interfejs API, który ta usługa oferuje klientom, pozostaje bez zmian. Możliwe więc, że aplikacja utrzyma większość logiki wymaganej do wdrożenia MediaBrowserService i dostosowania jej do nowej wersji MediaLibraryService.

    Główne różnice w porównaniu ze starszą wersją funkcji MediaBrowserServiceCompat:

    • Zaimplementuj metody cyklu życia usługi: metody, które należy zastąpić w samej usłudze, to onCreate/onDestroy, gdzie aplikacja przydziela/zwalnia sesję biblioteki, odtwarzacz i inne zasoby. Oprócz standardowych metod cyklu życia usługi aplikacja musi zastąpić właściwość onGetSession(MediaSession.ControllerInfo), aby zwrócić MediaLibrarySession, która została skompilowana w onCreate.

    • Implementacja MediaLibraryService.MediaLibrarySessionCallback: utworzenie sesji wymaga modułu MediaLibraryService.MediaLibrarySessionCallback, który implementuje metody interfejsu API domeny. Dlatego zamiast zastępować metody interfejsu API starszej usługi, zastąpisz metody dostępne w metodzie MediaLibrarySession.Callback.

      Wywołanie zwrotne jest następnie używane do utworzenia MediaLibrarySession:

      mediaLibrarySession =
            MediaLibrarySession.Builder(this, player, MySessionCallback())
               .build()
      

      Pełny interfejs API MediaLibrarySessionCallback znajdziesz w dokumentacji interfejsu API.

    • Implementacja MediaSession.Callback.onAddMediaItems(): wywołanie zwrotne onAddMediaItems(MediaSession, ControllerInfo, List<MediaItem>) obsługuje różne bieżące i starsze metody interfejsu API, które dodają elementy multimedialne do odtwarzacza na potrzeby ich odtwarzania w sposób zgodny wstecznie. Obejmuje to metody MediaController.set/addMediaItems() kontrolera Media3 i metody TransportControls.prepareFrom*/playFrom* starszego interfejsu API. Przykładową implementację wywołania zwrotnego znajdziesz w PlaybackService aplikacji demonstracyjnej sesji.

    • AndroidX Media3 używa androidx.media3.common.MediaItem zamiast MediaBrowserCompat.MediaItem i MediaMetadataCompat. Musisz odpowiednio zmienić części kodu powiązane ze starszymi klasami lub zmapować je na element MediaItem Media3.

    • Ogólny model programowania asynchronicznego został zmieniony na Futures w przeciwieństwie do metody Result z odłączanym elementem MediaBrowserServiceCompat. Implementacja Twojej usługi może zwracać asynchroniczną wartość ListenableFuture, zamiast odłączać wynik lub zwracać natychmiastową przyszłość, aby bezpośrednio zwrócić wartość.

Usuń PlayerPowiadomienie Manager

Urządzenie MediaLibraryService automatycznie obsługuje powiadomienia o multimediach, a urządzenie PlayerNotificationManager można usunąć, jeśli używasz MediaLibraryService lub MediaSessionService.

Aplikacja może dostosować powiadomienie, ustawiając w onCreate() niestandardowy parametr MediaNotification.Provider, który zastąpi DefaultMediaNotificationProvider. W razie potrzeby MediaLibraryService uruchomi usługę na pierwszym planie.

Zastąpienie MediaLibraryService.updateNotification() może w ten sposób przejąć pełną własność publikowania powiadomienia oraz, w razie potrzeby, uruchamiać i zatrzymywać usługę na pierwszym planie.

Migracja kodu klienta przy użyciu MediaBrowser

W AndroidzieX Media3 MediaBrowser implementuje interfejsy MediaController/Player i może służyć do sterowania odtwarzaniem multimediów poza przeglądaniem biblioteki multimediów. Jeśli musisz utworzyć elementy MediaBrowserCompat i MediaControllerCompat w starszej wersji, możesz zrobić to samo, używając tylko MediaBrowser w Media3.

Możesz utworzyć MediaBrowser i poczekać na połączenie z usługą:

scope.launch {
    val sessionToken =
        SessionToken(context, ComponentName(context, MusicService::class.java)
    browser =
        MediaBrowser.Builder(context, sessionToken))
            .setListener(BrowserListener())
            .buildAsync()
            .await()
    // Get the library root to start browsing the library.
    root = browser.getLibraryRoot(/* params= */ null).await();
    // Add a MediaController.Listener to listen to player state events.
    browser.addListener(playerListener)
    playerView.setPlayer(browser)
}

Zapoznaj się z sekcją Sterowanie odtwarzaniem w sesji multimediów, aby dowiedzieć się, jak utworzyć komponent MediaController służący do sterowania odtwarzaniem w tle.

Dalsze czynności i czyszczenie

Niestabilne błędy interfejsu API

Po migracji do Media3 mogą pojawić się błędy linta dotyczące niestabilnego użycia interfejsu API. Te interfejsy API są bezpieczne w użyciu, a błędy lintowania to efekt uboczny naszych nowych gwarancji zgodności plików binarnych. Jeśli nie wymagasz ścisłej zgodności plików binarnych, możesz bezpiecznie pominąć te błędy za pomocą adnotacji @OptIn.

Tło

Ani ExoPlayer w wersji 1, ani 2 nie daje ścisłych gwarancji zgodności plików binarnych z kolejnymi wersjami. Interfejs ExoPlayer API jest z założenia bardzo duży, aby umożliwiać aplikacjom dostosowywanie niemal każdego aspektu odtwarzania. W kolejnych wersjach ExoPlayera od czasu do czasu wprowadzane były zmiany nazw symboli lub inne zmiany powodujące niezgodność (np. nowe wymagane metody interfejsów). W większości przypadków awarie zostały złagodzone przez wprowadzenie nowego symbolu i wycofanie starego symbolu w kilku wersjach, aby dać programistom czas na migrację zastosowań, ale nie zawsze było to możliwe.

Te zmiany powodujące niezgodność spowodowały, że użytkownicy bibliotek ExoPlayer w wersjach 1 i 2 napotkali 2 problemy:

  1. Uaktualnienie do wersji ExoPlayer może spowodować przerwanie kompilowania kodu.
  2. Aplikacja, która korzystała z platformy ExoPlayer zarówno bezpośrednio, jak i za pomocą biblioteki pośredniej, musiała sprawdzić, czy obie zależności są tej samej wersji. W przeciwnym razie niezgodności plików binarnych mogą doprowadzić do awarii środowiska wykonawczego.

Ulepszenia w Media3

Media3 gwarantuje zgodność plików binarnych z podzbiorem platformy API. Elementy, które nie gwarantują zgodności z plikami binarnymi, są oznaczone symbolem @UnstableApi. Aby to rozróżnić, użycie niestabilnych symboli interfejsu API generuje błąd lintowania, chyba że jest opatrzony adnotacją @OptIn.

Po migracji z ExoPlayer 2 do Media3 możesz napotkać wiele niestabilnych błędów lintowania interfejsu API. Może się wydawać, że Media3 jest mniej stabilna niż ExoPlayer v2. To nieprawda. „Niestabilne” części interfejsu Media3 API mają taki sam poziom stabilności jak cała powierzchnia interfejsu ExoPlayer v2, a gwarancje stabilnej powierzchni interfejsu Media3 API nie są w ogóle dostępne w ExoPlayer v2. Różni się tylko tym, że błąd lintowania ostrzega teraz o różnych poziomach stabilności.

Obsługa niestabilnych błędów lintowych interfejsu API

Masz 2 możliwości obsługi niestabilnych błędów lintowych interfejsu API:

  • Przejdź na stabilną wersję interfejsu API, który daje ten sam efekt.
  • Nadal korzystaj z niestabilnego interfejsu API i dodaj do niego adnotacje @OptIn.

    import androidx.annotation.OptIn
    import androidx.media3.common.util.UnstableApi
    
    @OptIn(UnstableApi::class)
    fun functionUsingUnstableApi() {
      // Do something useful.
    }
    

    Zwróć uwagę, że istnieje też adnotacja kotlin.OptIn, której nie należy używać. Ważne jest, aby używać w tym celu adnotacji androidx.annotation.OptIn.

    Zrzut ekranu: dodawanie adnotacji dotyczącej zgody
    Rys. 2. Dodawanie adnotacji @androidx.annotations.OptIn w Android Studio.

Można zaakceptować całe pakiety, dodając parametr package-info.java:

@OptIn(markerClass = UnstableApi.class)
package name.of.your.package;

import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;

Można zezwolić na korzystanie z całych projektów, eliminując określony błąd lint w pliku lint.xml. Więcej informacji znajdziesz w dokumentie JavaDoc dotyczącym adnotacji UnstableApi.

Wycofane interfejsy API

Możesz zauważyć, że wywołania wycofanych interfejsów API są przekreślone w Android Studio. Zalecamy zastąpienie takich wywołań odpowiednią alternatywą. Najedź na ten symbol, aby zobaczyć dokument JavaDoc informujący, którego interfejsu API należy użyć.

Zrzut ekranu: jak wyświetlać plik JavaDoc za pomocą alternatywnej metody wycofanej
Rysunek 3: etykietka JavaDoc w Android Studio sugeruje alternatywę dla każdego wycofanego symbolu.

Przykłady kodu i aplikacje demonstracyjne