מדריך להעברת נתונים מ-AndroidX Media3

אפליקציות שמשתמשות כרגע בספרייה העצמאית com.google.android.exoplayer2 וב-androidx.media צריכות לעבור ל-androidx.media3. משתמשים בסקריפט ההעברה כדי להעביר קובצי build של Gradle, קובצי מקור של Java ו-Kotlin וקובצי פריסה של XML מ-ExoPlayer בגרסה 2.19.1 ל-AndroidX Media3 בגרסה 1.1.1.

סקירה כללית

לפני שאתם מבצעים את ההעברה, כדאי לקרוא את הסעיפים הבאים כדי לקבל מידע נוסף על היתרונות של ממשקי ה-API החדשים, על ממשקי ה-API שצריך להעביר ועל הדרישות המוקדמות שהפרויקט של האפליקציה צריך לעמוד בהן.

למה כדאי לעבור ל-Jetpack Media3

  • זהו הבית החדש של ExoPlayer, והשימוש ב-com.google.android.exoplayer2 הופסק.
  • גישה ל-Player API ברכיבים או בתהליכים באמצעות MediaBrowser/MediaController.
  • להשתמש ביכולות המורחבות של ה-API של MediaSession ו-MediaController.
  • פרסום יכולות הפעלה באמצעות בקרת גישה מפורטת (FGAC).
  • כדאי לפשט את האפליקציה על ידי הסרת MediaSessionConnector ו-PlayerNotificationManager.
  • תאימות לדורות קודמים עם ממשקי API של לקוחות media-compat (MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat)

ממשקי Media API שצריך להעביר ל-AndroidX Media3

  • ExoPlayer והתוספים שלו
    כולל את כל המודולים של פרויקט ExoPlayer הקודם, חוץ מהמודול mediasession שהופסק. אפליקציות או מודולים שתלויים בחבילות ב-com.google.android.exoplayer2 אפשר להעביר באמצעות סקריפט ההעברה.
  • ‫MediaSessionConnector (בהתאם לחבילות androidx.media.* של androidx.media:media:1.4.3+)
    צריך להסיר את MediaSessionConnector ולהשתמש ב-androidx.media3.session.MediaSession במקום זאת.
  • ‫MediaBrowserServiceCompat (בהתאם לחבילות androidx.media.* של androidx.media:media:1.4.3+)
    צריך להעביר מחלקות משנה של androidx.media.MediaBrowserServiceCompat אל androidx.media3.session.MediaLibraryService וקוד שמשתמש ב-MediaBrowserCompat.MediaItem אל androidx.media3.common.MediaItem.
  • MediaBrowserCompat (בהתאם לחבילות android.support.v4.media.* של androidx.media:media:1.4.3+)
    צריך להעביר את קוד הלקוח באמצעות MediaBrowserCompat או MediaControllerCompat כדי להשתמש ב-androidx.media3.session.MediaBrowser עם androidx.media3.common.MediaItem.

דרישות מוקדמות

  1. מוודאים שהפרויקט נמצא בבקרת מקור

    חשוב לוודא שאפשר לבטל בקלות שינויים שבוצעו באמצעות כלי העברה מבוססי-סקריפט. אם הפרויקט שלכם עדיין לא נמצא בבקרת מקור, זה הזמן להתחיל להשתמש בה. אם מסיבה כלשהי אתם לא רוצים לעשות את זה, צרו עותק גיבוי של הפרויקט לפני שתתחילו בהעברה.

  2. עדכון האפליקציה

    • מומלץ לעדכן את הפרויקט כך שישתמש בגרסה העדכנית ביותר של ספריית ExoPlayer ולהסיר את כל הקריאות לשיטות שהוצאו משימוש. אם אתם מתכוונים להשתמש בסקריפט להעברה, אתם צריכים להתאים את הגרסה שאתם מעדכנים לגרסה שהסקריפט מטפל בה.

    • מגדילים את compileSdkVersion של האפליקציה ל-32 לפחות.

    • משדרגים את Gradle ואת הפלאגין של Android Studio Gradle לגרסה עדכנית שפועלת עם יחסי התלות המעודכנים שלמעלה. לדוגמה:

      • גרסת הפלאגין של Android Gradle: ‏ 7.1.0
      • גרסת Gradle‏: 7.4
    • החליפו את כל הצהרות הייבוא של תווי הכללה שמשתמשות בכוכבית (*) והשתמשו בהצהרות ייבוא מלאות: מחקו את הצהרות הייבוא של תווי הכללה והשתמשו ב-Android Studio כדי לייבא את ההצהרות המלאות (F2 - Alt/Enter, F2 - Alt/Enter, ...).

    • מעבר מ-com.google.android.exoplayer2.PlayerView אל com.google.android.exoplayer2.StyledPlayerView. זה הכרחי כי אין מקבילה ל-com.google.android.exoplayer2.PlayerView ב-AndroidX Media3.

העברת ExoPlayer עם תמיכה בסקריפטים

הסקריפט מאפשר לעבור מ-com.google.android.exoplayer2 למבנה החדש של החבילה והמודול ב-androidx.media3. הסקריפט מבצע כמה בדיקות אימות בפרויקט ומדפיס אזהרות אם האימות נכשל. אחרת, הוא מחיל את המיפויים של מחלקות וחבילות ששמן השתנה במשאבים של פרויקט Gradle ל-Android שכתוב ב-Java או ב-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

שימוש בסקריפט ההעברה

  1. מורידים את סקריפט ההעברה מהתג של פרויקט ExoPlayer ב-GitHub, שתואם לגרסה שאליה עדכנתם את האפליקציה:

    curl -o media3-migration.sh \
      "https://raw.githubusercontent.com/google/ExoPlayer/r2.19.1/media3-migration.sh"
    
  2. הופכים את הסקריפט לניתן להפעלה:

    chmod 744 media3-migration.sh
    
  3. מריצים את הסקריפט עם --help כדי לקבל מידע על האפשרויות.

  4. מריצים את הסקריפט עם -l כדי להציג את רשימת הקבצים שנבחרו להעברה (משתמשים ב--f כדי לאלץ את הצגת הרשימה בלי אזהרות):

    ./media3-migration.sh -l -f /path/to/gradle/project/root
    
  5. מריצים את הסקריפט עם -m כדי למפות חבילות, מחלקות ומודולים ל-Media3. הפעלת הסקריפט עם האפשרות -m תחיל את השינויים על הקבצים שנבחרו.

    • עצירה בשגיאת אימות בלי לבצע שינויים
    ./media3-migration.sh -m /path/to/gradle/project/root
    
    • הרצה מאולצת

    אם הסקריפט מוצא הפרה של הדרישות המוקדמות, אפשר לאלץ את ההעברה באמצעות הדגל -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

אחרי שמריצים את הסקריפט עם האפשרות -m, צריך לבצע את השלבים הבאים באופן ידני:

  1. בודקים איך הסקריפט שינה את הקוד: משתמשים בכלי diff ומתקנים בעיות פוטנציאליות (כדאי לדווח על באג אם חושבים שיש בעיה כללית בסקריפט שהוצגה בלי להעביר את האפשרות -f).
  2. מפתחים את הפרויקט: משתמשים ב-./gradlew clean build או בוחרים באפשרות File > Sync Project with Gradle Files (קובץ > סנכרון הפרויקט עם קובצי Gradle) ב-Android Studio, ואז בוחרים באפשרות Build > Clean project (פיתוח > ניקוי הפרויקט) ואז באפשרות Build > Rebuild project (פיתוח > פיתוח מחדש של הפרויקט). אפשר לעקוב אחרי הפיתוח בכרטיסייה Build - Build Output (פיתוח - פלט פיתוח) ב-Android Studio.

השלבים המומלצים להמשך:

  1. פותרים את הבעיה הצטרפות לשגיאות בנוגע לשימוש בממשקי API לא יציבים.
  2. החלפת קריאות ל-API שהוצאו משימוש: משתמשים ב-API החלופי שמוצע. העבירו את הסמן מעל האזהרה ב-Android Studio, ועיינו ב-JavaDoc של הסמל שהוצא משימוש כדי לגלות במה להשתמש במקום קריאה מסוימת.
  3. מיון הצהרות הייבוא: פותחים את הפרויקט ב-Android Studio, לוחצים לחיצה ימנית על צומת של תיקיית חבילה בתצוגת הפרויקט ובוחרים באפשרות Optimize imports (אופטימיזציה של הייבוא) בחבילות שמכילות את קובצי המקור שהשתנו.

מחליפים את MediaSessionConnector ב-androidx.media3.session.MediaSession

בגרסה הקודמת של MediaSessionCompat, MediaSessionConnector היה אחראי לסנכרון המצב של הנגן עם המצב של הסשן ולקבלת פקודות מבקרים שהיו צריכים להקצות למתודות המתאימות של הנגן. ב-AndroidX Media3, הפעולה הזו מתבצעת ישירות על ידי MediaSession, בלי צורך במחבר.

  1. הסרת כל ההפניות והשימוש ב-MediaSessionConnector: אם השתמשתם בסקריפט האוטומטי כדי להעביר מחלקות וחבילות של ExoPlayer, סביר להניח שהסקריפט השאיר את הקוד במצב שלא ניתן להדר לגבי MediaSessionConnector שלא ניתן לפתור. אם תנסו ליצור או להפעיל את האפליקציה, ב-Android Studio יוצג הקוד הפגום.

  2. בקובץ build.gradle שבו אתם מנהלים את יחסי התלות, מוסיפים תלות בהטמעה למודול הסשן של AndroidX Media3 ומסירים את התלות הקודמת:

    implementation "androidx.media3:media3-session:1.11.1"
    
  3. מחליפים את MediaSessionCompat ב-androidx.media3.session.MediaSession.

  4. באתר הקוד שבו יצרתם את MediaSessionCompat הקודם, משתמשים ב-androidx.media3.session.MediaSession.Builder כדי ליצור MediaSession. מעבירים את הנגן כדי ליצור את בונה הסשנים.

    Kotlin

    val player = ExoPlayer.Builder(context).build()
    mediaSession = MediaSession.Builder(context, player).setCallback(MySessionCallback()).build()

    Java

    ExoPlayer player = new ExoPlayer.Builder(context).build();
    mediaSession =
        new MediaSession.Builder(context, player).setCallback(new MySessionCallback()).build();

  5. מטמיעים את MySessionCallback לפי הצורך באפליקציה. השלב הזה אופציונלי. אם רוצים לאפשר לבקרים להוסיף קובצי מדיה לנגן, צריך להטמיע את MediaSession.Callback.onAddMediaItems(). הוא מציע שיטות API שונות, עדכניות וקודמות, שמוסיפות קובצי מדיה לנגן להפעלה באופן שתואם לדורות קודמים. זה כולל את השיטות MediaController.set/addMediaItems() של הבקר Media3, וגם את השיטות TransportControls.prepareFrom*/playFrom* של ה-API הקודם. אפשר למצוא הטמעה לדוגמה של onAddMediaItems ב-PlaybackService של אפליקציית ההדגמה של הסשן.

  6. משחררים את סשן המדיה באתר הקוד שבו הסשן הושמד לפני ההעברה:

    Kotlin

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

    Java

    if (mediaSession != null) {
      mediaSession.getPlayer().release();
      mediaSession.release();
      mediaSession = null;
    }

פונקציונליות של MediaSessionConnector ב-Media3

בטבלה הבאה מוצגים ממשקי ה-API של Media3 שמטפלים בפונקציונליות שהוטמעה בעבר ב-MediaSessionConnector.

MediaSessionConnectorAndroidX Media3
CustomActionProvider MediaSession.Callback.onCustomCommand()/ MediaSession.setMediaButtonPreferences()
PlaybackPreparer MediaSession.Callback.onAddMediaItems() (הפונקציה prepare() נקראת באופן פנימי)
QueueNavigator ForwardingSimpleBasePlayer
QueueEditor MediaSession.Callback.onAddMediaItems()
RatingCallback MediaSession.Callback.onSetRating()
PlayerNotificationManager DefaultMediaNotificationProvider/ MediaNotification.Provider

העברת MediaBrowserService אל MediaLibraryService

ב-AndroidX Media3 יש את MediaLibraryService שמחליף את MediaBrowserServiceCompat. ה-JavaDoc של MediaLibraryService ושל מחלקת העל שלו, MediaSessionService, מספק מבוא טוב ל-API ולמודל התכנות האסינכרוני של השירות.

ה-MediaLibraryService תואם לדורות קודמים של MediaBrowserService. אפליקציית לקוח שמשתמשת ב-MediaBrowserCompat או ב-MediaControllerCompat ממשיכה לפעול בלי שינויים בקוד כשמתחברים ל-MediaLibraryService. מבחינת הלקוח, לא משנה אם האפליקציה משתמשת ב-MediaLibraryService או ב-MediaBrowserServiceCompat מדור קודם.

תרשים של רכיבי אפליקציה עם שירות, פעילות ואפליקציות חיצוניות.
איור 1: סקירה כללית של רכיבי אפליקציית המדיה
  1. כדי שהתאימות לדורות קודמים תעבוד, צריך לרשום את שני ממשקי השירות בשירות שלכם ב-AndroidManifest.xml. כך הלקוח יכול למצוא את השירות שלכם לפי ממשק השירות הנדרש:

    <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. בקובץ build.gradle שבו אתם מנהלים את יחסי התלות, מוסיפים תלות בהטמעה של מודול הסשן של AndroidX Media3 ומסירים את התלות הקודמת:

    implementation "androidx.media3:media3-session:1.11.1"
    
  3. שינוי השירות כך שיירש מ-MediaLibraryService במקום מ-MediaBrowserService: כמו שצוין קודם, MediaLibraryService תואם ל-MediaBrowserService הקודם. בהתאם, ה-API הרחב יותר שהשירות מציע ללקוחות נשאר זהה. לכן, סביר להניח שאפליקציה יכולה לשמור על רוב הלוגיקה שנדרשת כדי להטמיע את MediaBrowserService ולהתאים אותה ל-MediaLibraryService החדש.

    אלה ההבדלים העיקריים בהשוואה ל-MediaBrowserServiceCompat הקודם:

    • הטמעת השיטות של מחזור החיים של השירות: השיטות שצריך לשנות בשירות עצמו הן onCreate/onDestroy, שבהן האפליקציה מקצה או משחררת את הסשן של הספרייה, הנגן ומשאבים אחרים. בנוסף לשיטות הרגילות של מחזור החיים של השירות, האפליקציה צריכה לשנות את onGetSession(MediaSession.ControllerInfo) כדי להחזיר את ה-MediaLibrarySession שנוצר ב-onCreate.

    • הטמעת MediaLibraryService.MediaLibrarySessionCallback: כדי ליצור סשן, צריך MediaLibraryService.MediaLibrarySessionCallback שמטמיע את השיטות בפועל של ה-API של הדומיין. לכן, במקום לשנות את השיטות של ה-API של השירות הקודם, צריך לשנות את השיטות של MediaLibrarySession.Callback.

      לאחר מכן, פונקציית הקריאה החוזרת משמשת ליצירת MediaLibrarySession:

      Kotlin

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

      Java

      mediaLibrarySession =
          new MediaLibrarySession.Builder(context, player, new MySessionCallback()).build();

      ה-API המלא של MediaLibrarySessionCallback מופיע במאמרי העזרה של ה-API.

    • הטמעה של MediaSession.Callback.onAddMediaItems(): פונקציית ה-callback‏ onAddMediaItems(MediaSession, ControllerInfo, List<MediaItem>) משמשת שיטות API שונות, נוכחיות וקודמות, שמוסיפות פריטי מדיה לנגן להפעלה באופן שתואם לדורות קודמים. זה כולל את השיטות MediaController.set/addMediaItems() של הבקר Media3, וגם את השיטות TransportControls.prepareFrom*/playFrom* של ה-API הקודם. ב-PlaybackService של אפליקציית ההדגמה של הסשן אפשר למצוא דוגמה להטמעה של פונקציית ה-callback.

    • ‫AndroidX Media3 משתמש ב-androidx.media3.common.MediaItem במקום ב-MediaBrowserCompat.MediaItem וב-MediaMetadataCompat. צריך לשנות בהתאם את חלקי הקוד שקשורים למחלקות הקודמות, או למפות אותם ל-MediaItem של Media3.

    • מודל התכנות האסינכרוני הכללי השתנה ל-Futures, בניגוד לגישת Result הניתנת לניתוק של MediaBrowserServiceCompat. הטמעת השירות יכולה להחזיר ListenableFuture אסינכרוני במקום לנתק תוצאה, או להחזיר Future מיידי כדי להחזיר ערך באופן ישיר.

הסרת PlayerNotificationManager

MediaLibraryService תומך בהתראות מדיה באופן אוטומטי, ואפשר להסיר את PlayerNotificationManager כשמשתמשים ב-MediaLibraryService או ב-MediaSessionService.

אפליקציה יכולה להתאים אישית את ההתראה על ידי הגדרת MediaNotification.Provider בהתאמה אישית ב-onCreate() שמחליף את DefaultMediaNotificationProvider. לאחר מכן, MediaLibraryService מפעיל את השירות בחזית לפי הצורך.

באמצעות שינוי של MediaLibraryService.updateNotification(), אפליקציה יכולה לקבל בעלות מלאה על פרסום התראה ועל הפעלה או עצירה של השירות בחזית, לפי הצורך.

העברת קוד לקוח באמצעות MediaBrowser

ב-AndroidX Media3, ‏MediaBrowser מטמיע את הממשקים של MediaController/Player ואפשר להשתמש בו כדי לשלוט בהפעלת המדיה, בנוסף לדפדוף בספריית המדיה. אם הייתם צריכים ליצור MediaBrowserCompat ו-MediaControllerCompat בעולם הקודם, אתם יכולים לעשות את אותו הדבר רק באמצעות MediaBrowser ב-Media3.

אפשר ליצור MediaBrowser ולהמתין עד שהחיבור לשירות ייווצר:

Kotlin

scope.launch {
  val sessionToken = SessionToken(context, ComponentName(context, "MusicService"))
  browser =
    MediaBrowser.Builder(context, sessionToken)
      .setListener(BrowserListener())
      .buildAsync()
      .await()
}

Java

SessionToken sessionToken =
    new SessionToken(context, new ComponentName(context, "MusicService"));
ListenableFuture<MediaBrowser> browserFuture =
    new MediaBrowser.Builder(context, sessionToken)
        .setListener(new BrowserListener())
        .buildAsync();

במאמר שליטה בהפעלה בסשן המדיה מוסבר איך ליצור MediaController כדי לשלוט בהפעלה ברקע.

שלבים נוספים וניקוי

שגיאות API לא יציבות

אחרי המעבר ל-Media3, יכול להיות שיופיעו שגיאות Lint לגבי שימוש בממשקי API לא יציבים. ממשקי ה-API האלה בטוחים לשימוש, והשגיאות של ה-lint הן תוצר לוואי של ההתחייבויות החדשות שלנו לתאימות בינארית. אם אתם לא צריכים תאימות בינארית קפדנית, אתם יכולים להסתיר את השגיאות האלה באמצעות הערת @OptIn.

רקע

גרסאות 1 ו-2 של ExoPlayer לא סיפקו הבטחות מחמירות לגבי תאימות בינארית של הספרייה בין גרסאות עוקבות. ממשק ה-API של ExoPlayer הוא גדול מאוד, כדי לאפשר לאפליקציות להתאים אישית כמעט כל היבט של ההפעלה. בגרסאות הבאות של ExoPlayer, לפעמים שינו את השמות של הסמלים או ביצעו שינויי תוכנה שעלולים לגרום לכשל (למשל, שיטות חדשות שנדרשות בממשקים). ברוב המקרים, כדי למנוע את התקלות האלה, הוספנו את הסמל החדש לצד הסמל הישן שהוצא משימוש בכמה גרסאות, כדי לתת למפתחים זמן להעביר את השימושים שלהם, אבל זה לא תמיד היה אפשרי.

השינויים האלה גרמו לשתי בעיות למשתמשים בספריות ExoPlayer בגרסאות 1 ו-2:

  1. שדרוג לגרסה של ExoPlayer עלול לגרום לקוד להפסיק לעבור קומפילציה.
  2. באפליקציה שהייתה תלויה ב-ExoPlayer גם באופן ישיר וגם דרך ספריית ביניים, היה צריך לוודא ששני יחסי התלות היו באותה גרסה. אחרת, חוסר תאימות בינארית עלול היה לגרום לקריסות בזמן הריצה.

שיפורים ב-Media3

‫Media3 מבטיח תאימות בינארית לקבוצת משנה של ממשק ה-API. החלקים שלא מבטיחים תאימות בינארית מסומנים ב-@UnstableApi. כדי להבהיר את ההבדל הזה, שימוש בסמלי API לא יציבים יוצר שגיאת איתור שגיאות בקוד (lint), אלא אם הם מסומנים ב-@OptIn.

אחרי המעבר מ-ExoPlayer v2 ל-Media3, יכול להיות שיופיעו הרבה שגיאות lint של ממשק API לא יציב. לכן יכול להיות ש-Media3 תיראה פחות יציבה מ-ExoPlayer v2. זה לא נכון. החלקים ה"לא יציבים" של Media3 API הם באותה רמת יציבות כמו כל ממשק ExoPlayer v2 API, וההבטחות של ממשק Media3 API היציב לא זמינות בכלל ב-ExoPlayer v2. ההבדל היחיד הוא שעכשיו שגיאת lint מתריעה על רמות היציבות השונות.

טיפול בשגיאות לא יציבות של API lint

בקטע פתרון הבעיות של שגיאות ה-lint האלה מוסבר איך להוסיף הערות לשימושים ב-Java וב-Kotlin בממשקי API לא יציבים באמצעות @OptIn.

ממשקי API שיצאו משימוש

יכול להיות שתבחינו שקריאות לממשקי API שהוצאו משימוש מסומנות בקו חוצה ב-Android Studio. מומלץ להחליף את הקריאות האלה בחלופה המתאימה. מעבירים את העכבר מעל הסמל כדי לראות את ה-JavaDoc שמציין באיזה API להשתמש במקום זאת.

צילום מסך: איך להציג JavaDoc עם חלופה לשיטה שהוצאה משימוש
איור 3: הסבר קצר של JavaDoc ב-Android Studio מציע חלופה לכל סמל שהוצא משימוש.

דוגמאות קוד ואפליקציות להדגמה

  • אפליקציית הדגמה של סשן AndroidX Media3 (לנייד ול-WearOS)
    • פעולות בהתאמה אישית
    • התראה בממשק המשתמש של המערכת, MediaButton/BT
    • שליטה ברכיבי UI להפעלה באמצעות Google Assistant
  • ‫UAMP: Android Media Player (branch media3) (נייד, AutomotiveOS)
    • התראה בממשק המשתמש של המערכת, MediaButton/BT, המשך הפעלה
    • שליטה ברכיבי UI להפעלה באמצעות Google Assistant או WearOS
    • ‫AutomotiveOS: פקודה מותאמת אישית וכניסה לחשבון