Przewodnik po migracji na AndroidaX Media3

Aplikacje, które obecnie korzystają z samodzielnej biblioteki com.google.android.exoplayer2androidx.media, powinny przejść na androidx.media3. Użyj skryptu migracji, aby przenieść pliki kompilacji Gradle, pliki źródłowe Javy i Kotlin oraz pliki układu XML z ExoPlayera2.19.1 do AndroidX Media31.1.1.

Omówienie

Zanim przeprowadzisz migrację, zapoznaj się z następującymi sekcjami, aby dowiedzieć się więcej o korzyściach płynących z nowych interfejsów API, interfejsach API, które należy przenieść, oraz wymaganiach wstępnych, które musi spełniać projekt aplikacji.

Dlaczego warto przejść na Jetpack Media3

  • Jest to nowa wersja ExoPlayera, a com.google.android.exoplayer2 został wycofany.
  • Uzyskaj dostęp do interfejsu Player API w różnych komponentach i procesach za pomocą interfejsów MediaBrowser i MediaController.
  • Korzystanie z rozszerzonych możliwości interfejsów API MediaSession i MediaController.
  • Reklamowanie możliwości odtwarzania dzięki szczegółowej kontroli dostępu.
  • Uprość aplikację, usuwając MediaSessionConnectorPlayerNotificationManager.
  • Zgodność wsteczna z interfejsami API klienta kompatybilnymi z mediami (MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat)

Interfejsy Media API do migracji do Media3 w AndroidX

  • ExoPlayer i jego rozszerzenia
    Obejmuje to wszystkie moduły starszego projektu ExoPlayer z wyjątkiem modułu mediasession, który został wycofany. Aplikacje lub moduły (w zależności od pakietów w com.google.android.exoplayer2) można przenieść za pomocą skryptu migracyjnego.
  • MediaSessionConnector (w zależności od pakietów androidx.media.* pakietu androidx.media:media:1.4.3+)
    Usuń MediaSessionConnector i użyj androidx.media3.session.MediaSession.
  • MediaBrowserServiceCompat (w zależności od pakietów androidx.media.*androidx.media:media:1.4.3+)
    Przenieś podklasy androidx.media.MediaBrowserServiceCompat do androidx.media3.session.MediaLibraryService i kod korzystający z MediaBrowserCompat.MediaItem do androidx.media3.common.MediaItem.
  • MediaBrowserCompat (w zależności od pakietów android.support.v4.media.*androidx.media:media:1.4.3+)
    Przenieś kod klienta za pomocą pakietu MediaBrowserCompat lub pakietu MediaControllerCompat, aby używać pakietu androidx.media3.session.MediaBrowser z poziomu androidx.media3.common.MediaItem.

Wymagania wstępne

  1. Sprawdzanie, czy projekt jest objęty kontrolą wersji

    Upewnij się, że możesz łatwo cofnąć zmiany wprowadzone przez narzędzia do migracji oparte na skryptach. Jeśli nie masz jeszcze projektu objętego kontrolą wersji, teraz jest dobry moment, aby zacząć. Jeśli z jakiegoś powodu nie chcesz tego zrobić, przed rozpoczęciem migracji utwórz kopię zapasową projektu.

  2. Aktualizowanie aplikacji

    • Zalecamy zaktualizowanie projektu, aby używać najnowszej wersji biblioteki ExoPlayer, i usunięcie wszystkich wywołań wycofanych metod. Jeśli chcesz korzystać ze skryptu do przeprowadzenia migracji, musisz dopasować wersję, na którą chcesz przeprowadzić migrację, do wersji obsługiwanej przez skrypt.

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

    • Uaktualnij Gradle i wtyczkę Android Studio Gradle do najnowszej wersji, która współpracuje ze zaktualizowanymi zależnościami podanymi powyżej. Na przykład:

      • Wersja wtyczki Androida do obsługi Gradle: 7.1.0
      • Wersja Gradle: 7.4
    • Zastąp wszystkie instrukcje importu z użyciem gwiazdki, które używają gwiazdki (*), i użyj w pełni kwalifikowanych instrukcji importu: usuń instrukcje importu z użyciem gwiazdki i użyj Android Studio do zaimportowania w pełni kwalifikowanych instrukcji (F2 – Alt/Enter, F2 – Alt/Enter, …).

    • Przenieś dane z com.google.android.exoplayer2.PlayerView do com.google.android.exoplayer2.StyledPlayerView. Jest to konieczne, ponieważ w AndroidX Media3 nie ma odpowiednika com.google.android.exoplayer2.PlayerView.

Migracja ExoPlayera z obsługą skryptów

Skrypt ułatwia przejście z wersji com.google.android.exoplayer2 na nową strukturę pakietu i modułów w ramach androidx.media3. Skrypt stosuje w projekcie kilka testów weryfikacyjnych i wyświetla ostrzeżenia, jeśli weryfikacja się nie powiedzie. W przeciwnym razie stosowane są mapowania zmienionych klas i pakietów w zasobach projektu Gradle na Androida zapisanego w języku Java 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

Korzystanie ze skryptu migracji

  1. Pobierz skrypt migracji z tagu projektu ExoPlayer na GitHubie odpowiadającego wersji, do 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 opcją --help, aby dowiedzieć się więcej o opcjach.

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

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

    • Zatrzymanie się na błędzie walidacji 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

Po uruchomieniu skryptu za pomocą opcji -m wykonaj te czynności ręcznie:

  1. Sprawdź, jak skrypt zmienił Twój kod: użyj narzędzia do porównywania i rozwiąż potencjalne problemy (rozważ zgłoszenie błędu, jeśli uważasz, że skrypt ma ogólny problem, który został wprowadzony bez opcji -f).
  2. Kompilacja projektu: użyj ./gradlew clean build lub w Android Studio wybierz Plik > Synchronizuj projekt z plikami Gradle, a następnie Kompiluj > Wyczyść projekt, a potem Kompiluj > Odbuduj projekt ponownie (monitoruj kompilację na karcie Kompilacja – Dane wyjściowe kompilacji w Android Studio).

Zalecane dodatkowe czynności:

  1. Rozwiąż problemy z błędami dotyczącymi używania niestabilnych interfejsów API.
  2. Zastąp wycofane wywołania interfejsu API: użyj sugerowanego interfejsu API. Aby dowiedzieć się, czego użyć zamiast danego wywołania, najedź kursorem na ostrzeżenie w Android Studio i zapoznaj się z dokumentem JavaDoc dotyczącym wycofanego symbolu.
  3. Porządkuj instrukcje importu: otwórz projekt w Android Studio, a potem w przeglądarce projektu kliknij prawym przyciskiem myszy węzeł folderu pakietu i w przypadku pakietów zawierających zmienione pliki źródłowe kliknij Optymalizuj importy.

Zastąp MediaSessionConnector tekstem androidx.media3.session.MediaSession

W starszym świecie MediaSessionCompat element MediaSessionConnector odpowiadał za synchronizację stanu odtwarzacza ze stanem sesji oraz odbieranie poleceń od kontrolerów, które wymagały przekazania dostępu do odpowiednich metod odtwarzacza. W przypadku AndroidX Media3 zadanie to wykonuje bezpośrednio MediaSession, bez potrzeby korzystania z oprogramowania sprzęgającego.

  1. Usuń wszystkie odwołania i użycia MediaSessionConnector: jeśli do migracji klas i pakietów ExoPlayera został użyty skrypt automatyczny, prawdopodobnie skrypt pozostawił kod w stanie, w którym nie można go skompilować, w odniesieniu do MediaSessionConnector, którego nie można rozwiązać. Gdy spróbujesz skompilować lub uruchomić aplikację, Android Studio wyświetli uszkodzony kod.

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

    implementation "androidx.media3:media3-session:1.4.1"
    
  3. Zamień MediaSessionCompat na androidx.media3.session.MediaSession.

  4. Na stronie kodu, na której utworzono starszy MediaSessionCompat, użyj androidx.media3.session.MediaSession.Builder, aby utworzyćMediaSession. Prześlij odtwarzacz, aby utworzyć kreator sesji.

    val player = ExoPlayer.Builder(context).build()
    mediaSession = MediaSession.Builder(context, player)
        .setSessionCallback(MySessionCallback())
        .build()
    
  5. Zaimplementuj MySessionCallback zgodnie z wymaganiami aplikacji. Jest to opcjonalne. Jeśli chcesz zezwolić kontrolerom na dodawanie elementów multimedialnych do odtwarzacza, zaimplementuj MediaSession.Callback.onAddMediaItems(). Obsługuje on różne bieżące i stare metody interfejsu API, które dodają do odtwarzacza elementy multimediów w sposób zgodny wstecznie. Dotyczy to metod MediaController.set/addMediaItems() kontrolera Media3 oraz metod 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 sesja została zniszczona przed migracją:

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

Funkcje MediaSessionConnector w Media3

Poniższa tabela zawiera interfejsy Media3 API, które obsługują funkcje wcześniej implementowane w MediaSessionConnector.

MediaSessionConnectorAndroidX Media3
CustomActionProvider MediaSession.Callback.onCustomCommand()/ MediaSession.setCustomLayout()
PlaybackPreparer MediaSession.Callback.onAddMediaItems()(prepare() jest wywoływany wewnętrznie)
QueueNavigator ForwardingPlayer
QueueEditor MediaSession.Callback.onAddMediaItems()
RatingCallback MediaSession.Callback.onSetRating()
PlayerNotificationManager DefaultMediaNotificationProvider/ MediaNotification.Provider

Przenoszenie danych z MediaBrowserService do MediaLibraryService

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

Usługa MediaLibraryService jest zgodna wstecznie z usługą MediaBrowserService. Aplikacja kliencka, która używa MediaBrowserCompat lub MediaControllerCompat, nadal działa bez zmian kodu po połączeniu z MediaLibraryService. Dla klienta jasne jest to, czy aplikacja używa MediaLibraryService czy starszej wersji MediaBrowserServiceCompat.

Diagram komponentu aplikacji z usługą, aktywnością i aplikacjami zewnętrznymi.
Rysunek 1. Omówienie komponentu aplikacji do obsługi multimediów
  1. Aby zapewnić zgodność wsteczną, musisz zarejestrować oba interfejsy usługi w swojej usłudze w AndroidManifest.xml. W ten sposób klient znajdzie Twoją usługę w wymaganym interfejsie usługi:

    <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 Media3 w AndroidX i usuń starszą zależność:

    implementation "androidx.media3:media3-session:1.4.1"
    
  3. Zmień swoją usługę na dziedziczenie z MediaLibraryService zamiast MediaBrowserService Jak wspomnieliśmy wcześniej, usługa MediaLibraryService jest zgodna ze starszą wersją MediaBrowserService. W związku z tym interfejs API, który usługa oferuje klientom, pozostaje taki sam. Prawdopodobnie aplikacja może zachować większość logiki wymaganej do zaimplementowania MediaBrowserServicei dostosować ją do nowego MediaLibraryService.

    Najważniejsze różnice w porównaniu z wersją klasycznąMediaBrowserServiceCompat:

    • Wdróż 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ć metodę onGetSession(MediaSession.ControllerInfo), aby zwrócić metodę MediaLibrarySession utworzoną w onCreate.

    • Zaimplementuj MediaLibraryService.MediaLibrarySessionCallback: tworzenie sesji wymaga MediaLibraryService.MediaLibrarySessionCallback, który implementuje metody interfejsu API domeny. Zamiast zastąpić metody interfejsu API starszej usługi, zastąpisz metody usługi MediaLibrarySession.Callback.

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

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

      W dokumentacji interfejsu API odszukaj pełny interfejs API MediaLibrarySessionCallback.

    • Wdróż MediaSession.Callback.onAddMediaItems(): funkcja wywołania zwrotnegoonAddMediaItems(MediaSession, ControllerInfo, List<MediaItem>) obsługuje różne obecne i starsze metody interfejsu API, które dodają do odtwarzacza elementy multimediów w sposób zgodny z wstecz. Dotyczy to metod MediaController.set/addMediaItems() kontrolera Media3 oraz metod TransportControls.prepareFrom*/playFrom* starszego interfejsu API. Przykładową implementację wywołania zwrotnego znajdziesz PlaybackService aplikacji demonstracyjnej sesji.

    • AndroidX Media3 używa interfejsu androidx.media3.common.MediaItem zamiast interfejsów MediaBrowserCompat.MediaItem i MediaMetadataCompat. Odpowiednio trzeba zmienić te części kodu, które są powiązane ze starszymi klasami, lub zmapować je na Media3 MediaItem.

    • Ogólny model programowania asynchronicznego został zmieniony na Futures, co odróżnia go od podejścia ResultMediaBrowserServiceCompat, w którym można odłączać komponenty. Implementacja usługi może zwrócić asynchroniczny obiekt ListenableFuture zamiast odłączać wynik lub zwrócić natychmiastową przyszłość, aby bezpośrednio zwrócić wartość.

Usuń PlayerNotificationManager

MediaLibraryService automatycznie obsługuje powiadomienia o multimediach, a PlayerNotificationManager można usunąć, gdy używasz MediaLibraryService lub MediaSessionService.

Aplikacja może spersonalizować powiadomienie, ustawiając niestandardowe MediaNotification.ProvideronCreate(), które zastępuje DefaultMediaNotificationProvider. Następnie MediaLibraryService uruchamia usługę na pierwszym planie zgodnie z potrzebami.

Zastępując MediaLibraryService.updateNotification(), aplikacja może przejąć pełną kontrolę nad publikowaniem powiadomień i uruchamianiem/zatrzymywaniem usługi na pierwszym planie w razie potrzeby.

Migracja kodu klienta przy użyciu przeglądarki Media Browser

W AndroidX Media3 interfejs MediaBrowser zawiera interfejsy MediaController/Player, za pomocą których można sterować odtwarzaniem multimediów poza przeglądaniem biblioteki multimediów. Jeśli w starszej wersji musiałeś utworzyć MediaBrowserCompatMediaControllerCompat, możesz to zrobić w Media3, używając tylko MediaBrowser.

Można utworzyć MediaBrowser i zaczekać na nawiązanie połączenia 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)
}

Aby dowiedzieć się, jak utworzyć MediaController do sterowania odtwarzaniem w tle, zapoznaj się z artykułem Sterowanie odtwarzaniem w sesji multimediów.

Dalsze kroki i czyszczenie

Niestabilne błędy interfejsu API

Po migracji do Media3 mogą pojawić się błędy lint dotyczące niestabilnych zastosowań interfejsu API. Te interfejsy API są bezpieczne w użyciu, a błędy lintowania są efektem naszych nowych gwarancji zgodności plików binarnych. Jeśli nie wymagasz ścisłej zgodności binarnej, te błędy można bezpiecznie stłumić za pomocą adnotacji @OptIn.

Tło

Ani ExoPlayer w wersji 1, ani w wersji 2 nie zapewniały ścisłej gwarancji zgodności binarnej biblioteki w kolejnych wersjach. Interfejs ExoPlayer API jest bardzo rozbudowany, aby umożliwić aplikacjom dostosowywanie niemal wszystkich aspektów odtwarzania. Kolejne wersje ExoPlayera mogą wprowadzać zmiany nazw symboli lub inne zmiany wymagające aktualizacji (np. nowe wymagane metody w interfejsach). W większości przypadków udało się ograniczyć te problemy przez wprowadzenie nowego symbolu i wycofanie starego w kilku wersjach, aby dać deweloperom czas na przeniesienie zastosowań. Nie zawsze było to jednak możliwe.

Ta zmiana spowodowała 2 problemy u użytkowników bibliotek ExoPlayer w wersjach 1 i 2:

  1. Przejście na nowszą wersję ExoPlayera może spowodować, że kod przestanie się kompilować.
  2. Aplikacja, która korzystała z ExoPlayera bezpośrednio i za pomocą pośredniej biblioteki, musiała mieć te zależności w tej samej wersji. W przeciwnym razie niezgodności binarne mogłyby spowodować awarie w czasie działania.

Ulepszenia w Media3

Media3 gwarantuje zgodność binarną w przypadku podzbioru interfejsu API. Elementy, które nie gwarantują zgodności binarnej, są oznaczone jako @UnstableApi. Aby wyraźnie zaznaczyć to rozróżnienie, użycie niestabilnych symboli interfejsu API powoduje błąd lint, chyba że zostaną one opatrzone adnotacją @OptIn.

Po migracji z ExoPlayera 2 na Media3 możesz zobaczyć wiele błędów niestabilnego interfejsu API. Może to sprawiać wrażenie, że Media3 jest „mniej stabilna” niż ExoPlayer v2. Nie jest to prawda. Części interfejsu Media3 API o charakterze „niestabilnym” mają ten sam poziom stabilności co całość interfejsu API ExoPlayer w wersji 2, a gwarancje stabilności interfejsu Media3 API nie są w ogóle dostępne w ExoPlayerze w wersji 2. Różnica polega na tym, że błąd lintowania informuje teraz o różnych poziomach stabilności.

Obsługa niestabilnych błędów lint w interfejsie API

Więcej informacji o annotowaniu niestabilnych interfejsów API w języku Java i Kotlin za pomocą @OptIn znajdziesz w sekcji rozwiązywania problemów z tymi błędami lint.

Wycofane interfejsy API

W Android Studio możesz zauważyć, że wywołania wycofanych interfejsów API są przekreślone. Zalecamy zastąpienie takich wywołań odpowiednimi alternatywami. Najedź kursorem na symbol, aby wyświetlić informacje w JavaDoc, które podają, którego interfejsu API należy użyć zamiast tego.

Zrzut ekranu: jak wyświetlić dokument JavaDoc z alternatywną, wycofaną metodą
Rysunek 3. W JavaDoc w Android Studio znajdziesz alternatywę dla każdego symbolu, którego używanie zostało wycofane.

Przykłady kodu i aplikacje demonstracyjne