פיתוח אפליקציית מדיה לפי תבנית

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

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

המדריך הזה מיועד למשתמשים שכבר יש להם אפליקציית מדיה שמפעילה אודיו בטלפון, ושמבנה אפליקציית המדיה שלהם תואם לארכיטקטורת אפליקציות המדיה של Android. ספריית האפליקציות לרכב מאפשרת להחליף את חוויית השימוש באפליקציה בתבניות, במקום להשתמש בתבניות שנוצרו באמצעות מבנה הנתונים של יצירת אפליקציות מדיה לרכב MediaBrowser. עדיין צריך לספק MediaSession לרכיבי ה-UI להפעלת סרטונים, וגם MediaBrowserService או MediaLibraryService, שמשמשים להמלצות ולחוויות חכמות אחרות.

הגדרת המניפסט של האפליקציה

בנוסף לשלבים שמתוארים במאמר בנושא שימוש בספריית האפליקציות של Android למכוניות, אפליקציות מדיה שמבוססות על תבניות צריכות לעמוד בדרישות הבאות:

הצהרה על תמיכה בקטגוריה במניפסט

באפליקציה צריך להצהיר על androidx.car.app.category.MEDIA קטגוריית אפליקציות לרכב במסנן ה-Intent של CarAppService.

<application>
    ...
   <service
       ...
        android:name=".MyCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService" />
        <category android:name="androidx.car.app.category.MEDIA"/>
      </intent-filter>
    </service>
    ...
<application>

כדי לקבל גישה אל MediaPlaybackTemplate, האפליקציה צריכה גם להצהיר על ההרשאה androidx.car.app.MEDIA_TEMPLATES בקובץ המניפסט שלה:

<manifest ...>
  ...
  <uses-permission android:name="androidx.car.app.MEDIA_TEMPLATES"/>
  ...
</manifest>

הגדרת רמת ה-API המינימלית של אפליקציית הרכב

אפליקציות מדיה שמשתמשות ב-MediaPlaybackTemplate נתמכות רק ב-CAL API 8 ומעלה, לכן חשוב לוודא שערך המינימום של Car App API level מוגדר ל-8.

<application ...>
  ...
  <meta-data
    android:name="androidx.car.app.minCarApiLevel"
    android:value="8"/>
  ...
</application>

הוספת סמל ייחוס

חשוב להוסיף סמל שיוך לאפליקציות מדיה שנוצרו באמצעות ספריית Car App.

הצהרה על תמיכה ב-Android Auto

חשוב לוודא שהרכיבים הבאים כלולים במניפסט של האפליקציה:

<application>
  ...
  <meta-data android:name="com.google.android.gms.car.application"
      android:resource="@xml/automotive_app_desc"/>
  ...
</application>

לאחר מכן, מוסיפים את הצהרת התבנית אל automotive_app_desc.xml במשאבי ה-XML. הוא אמור להיראות כך:

<automotiveApp xmlns:android="http://schemas.android.com/apk/res/android">
 <uses name="media"/>
 <uses name="template"/>
</automotiveApp>

הצהרה על תמיכה ב-Android Automotive OS

יש שתי דרכים שונות להפצת אפליקציית מדיה שמופעלת באמצעות ספריית האפליקציות לרכב ב-Android Automotive OS: כקובץ APK יחיד או כשני קובצי APK נפרדים. אם אתם מפיצים קובץ APK יחיד, הוא יתמוך בכלי רכב שמופעלת בהם Android Automotive OS עם מארח ספריית האפליקציות לרכב, ויחזור לאפליקציית MediaBrowserService או MediaLibraryService אם לא, גם בגרסאות Android ישנות יותר (Android 10 עד Android 13). אם תבחרו להפיץ שתי חבילות APK נפרדות, תוכלו לעדכן בקלות רבה יותר את התוספות החדשות לגרסה של Car App Library בלי לחשוש מהשפעה על גרסת MediaBrowserService או MediaLibraryService של האפליקציה.

הפצה של קובץ APK יחיד

כשמפיצים קובץ APK יחיד לגרסאות Car App Library ו-MediaBrowserService או MediaLibraryService של האפליקציה, חשוב להגדיר את לערך android:required="false".

<uses-feature android:name="android.software.car.templates_host.media" android:required="false"/>

לאחר מכן, פועלים לפי ההנחיות של ספריית האפליקציות לרכב ל-AAOS ומוסיפים CarAppActivity שאפשר להפעיל (או פעילות מסוג trampoline). צריך להגדיר את הפעילות ל-android:enabled="false" במניפסט. לאחר מכן, מוסיפים תג מטא-נתונים להצהרה MediaBrowserService שמציין את רכיב CarAppActivity כרכיב החלפה. דוגמה למניפסט:

<service android:name=".media.MyMediaService"
    android:exported="true"
    android:label="@string/app_name">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
    </intent-filter>

    <!-- Link to Car App Library Activity -->
    <meta-data
        android:name="androidx.car.app.media.CalMediaActivityComponent" 
        android:value="com.example.mediaapp.LaunchableTrampoline"/>
</service>

<activity
    android:name=".LaunchableTrampoline"
    android:exported="true"
    android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
    android:launchMode="singleTask"
    android:label="@string/app_name_cal"
    android:enabled="false"> <!-- Set to false -->

    <meta-data android:name="distractionOptimized" android:value="true" />

    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <action android:name="androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>

הפצה ב-Play

חבילת ה-APK עם ספריית האפליקציות לרכב ו-MediaBrowserService או MediaLibraryService צריכה להיות מופעלת עם קוד גרסה גבוה יותר ו-minSdk שמטרגט את Android 14 ‏ (34).

הפצה באמצעות שני קובצי APK

כדי להפיץ שני קובצי APK נפרדים, אחד באמצעות Car App Library ואחד באמצעות MediaBrowserService או MediaLibraryService, צריך לבצע את השלבים הבאים כדי לוודא שהיכולות הנכונות של הרכב מוגדרות בצורה נכונה.

כשיוצרים קובץ APK נפרד לגרסה של האפליקציה בספריית האפליקציות לרכב, צריך להגדיר את הערך של android.software.car.templates_host.media ל-android:required=true. כך מוודאים שהאפליקציה מופצת רק בגרסאות של Android Automotive OS שאושרו עם תמיכה במארח של ספריית האפליקציות למכוניות.

<uses-feature android:name="android.software.car.templates_host.media" android:required="true"/>

בנוסף לשימוש ב-android.software.car.templates_host.media ולהגדרתו ל-android:required=true כמו שמופיע למעלה, צריך לבצע את השלבים הבאים כדי להפעיל את Android Automotive OS לפעילות של ספריית האפליקציות לרכב שאפשר להפעיל.

Play Distribution

את ה-APK שמשתמש בספריית האפליקציות לרכב צריך להפיץ במסלול הייעודי ל-Automotive OS.

תמיכה בפעולות קוליות

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

יצירת תבנית ההפעלה

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

נגן מוזיקה מציג את השיר Sounds of Spring (צלילי האביב) של Summer Fielding עם תמונת דיוקן מרובעת של אישה שמנגנת בגיטרה.

איור 1: MediaPlaybackTemplate עם פעולת כותרת לפתיחת התור בחלק העליון.

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

val playbackTemplate = MediaPlaybackTemplate.Builder()
      .setHeader(
        Header.Builder()
          .setStartHeaderAction(Action.BACK)
          .addEndHeaderAction(
                Action.Builder()
                  .setTitle(model.context.getString(R.string.queue_button_title))
                  .setIcon(
                    CarIcon.Builder(
                        IconCompat.createWithResource(
                          model.context,
                          R.drawable.gs_queue_music_vd_theme_24,
                        ))
                      .build())
                  .setOnClickListener(showQueueScreen())
                  .build())
          .setTitle(model.context.getString(R.string.media_playback_view_title))
          .build())
      .build()

כשמשתמשים ב-MediaPlaybackTemplate, צריך לרשום אסימון MediaSession באמצעות MediaPlaybackManager ב-CarAppService. אם לא עושים את זה, מוצגת שגיאה כששולחים MediaPlaybackTemplate למארח.

import androidx.car.app.media.MediaPlaybackManager


override fun onCreateSession(sessionInfo: SessionInfo): Session {
    return object : Session() {
        

        init {
          lifecycle.addObserver(
            LifecycleEventObserver { _, event ->
              if (event == ON_CREATE) {
                val token = ... // MediaSessionCompat.Token
                (carContext.getCarService(CarContext.MEDIA_PLAYBACK_SERVICE) as MediaPlaybackManager)
                  .registerMediaPlaybackToken(token)
              }
              ...
            }
          )
        }
    }
}

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

באפליקציות שמשתמשות בספריית Media3, שבה נעשה שימוש ב-PlatformToken במקום ב-MediaSessionCompat.Token רגיל, צריך להטמיע SessionCommand מותאם אישית ב-MediaLibrarySession.Callback שמחזיר את טוקן הפלטפורמה הבסיסי של הסשן: session.platformToken. ב-CarAppService שולחים את הפקודה המותאמת אישית הזו לסשן. אחרי שמקבלים את אסימון הפלטפורמה, ממירים אותו באמצעות MediaSessionCompat.Token.fromToken(platformToken) ומעבירים את אסימון התאימות הזה לספריית האפליקציות לרכב ב-.registerMediaPlaybackToken().

ארגון מדיה באמצעות תבניות

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

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

איור 2: רכיב SectionedItemTemplate שמכיל רכיב RowSection ואחריו רכיב GridSection

שימוש ב-SectionedItemTemplate בתוך TabTemplate

דרך נוחה לסווג מדיה באפליקציה היא באמצעות התג SectionedItemTemplate בתוך התג TabTemplate.

val template =
      SectionedItemTemplate.Builder()...build();
val tabTemplate = 
      TabTemplate.Builder(tabCallback)
          .setTabContents(TabContents.Builder(template).build)
          .setHeaderAction(Action.APP_ICON)
          
          .build();

רכיבים ותכונות של ספריית אפליקציות לרכב 1.9

Car App Library API Version 1.9 כולל רכיבים מותאמים אישית ליכולות גלישה ייחודיות, כמו צ'יפים, סרגלי התקדמות, פריטים מצומצמים, כותרת אינטראקטיבית מורחבת, קטעי תוכן מומלצים ובאנרים.

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

איור 3: תיבת טקסט SectionedItemTemplate עם הטקסט Chips,‏ Condensed Items,‏ Interactive Header,‏ Grid Items ו-Minimized Control Panel

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

איור 4: שני מסכים של דפדוף במדיה עם הסמלים Expanded Header, Spotlight Sections ו-Progress Bars

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

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

אם אתם משתמשים ב-SectionedItemTemplate, תוכלו להוסיף כפתור פעולה שיעביר אתכם למסך הפעלת המדיה. משתמשים בפעולה Action.MEDIA_PLAYBACK הרגילה של ספריית האפליקציות לרכב. אפליקציית מדיה תציג את הפעולה הזו כלוח בקרה ממוזער, שנדרש כדי לעמוד בדרישת האיכות MFT-1 אם משתמשים ב-Car App Library API בגרסה 1.9 ואילך. בתבניות אחרות, אפשר להשתמש בפעולה בכותרת כדי להשיג את אותה תוצאה.

טיפול ב-intents של הפעלת מדיה במערכת

כשמפעילים אפליקציה מממשק מערכת להפעלת מדיה, כמו כרטיס מדיה, צריך להפנות את המשתמש אל MediaPlaybackTemplate. אנחנו דורשים מאפליקציות מדיה לטפל בIntent Action בסדר הזה כדי לספק למשתמשים חוויה חלקה.

מוסיפים את פעולת androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK ל-intent-filter של רכיב Car App Library (או CarAppActivity או Activity).

צריך לוודא שהפעילות משתמשת בlaunchMode של singleTask או singleTop כדי להפעיל את onNewIntent().

<activity
    android:name=".LaunchableTrampoline"
    android:exported="true"
    android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
    android:launchMode="singleTask"
    android:label="@string/app_name_cal"
    android:enabled="false">

    <meta-data android:name="distractionOptimized" android:value="true" />

    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <action android:name="androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>

בכיתה Session, מחליפים את onNewIntent() כדי לנתח את הכוונה הנכנסת. אם פעולת Intent הנכנסת תואמת ל-SHOW_MEDIA_PLAYBACK, צריך להעביר את המשתמש למסך 'מה שומעים עכשיו?'.

@Override
public void onNewIntent(@NonNull Intent intent) {
    super.onNewIntent(intent);
    if (SHOW_MEDIA_PLAYBACK.equals(intent.getAction())) {
        ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class);
        // Avoid redundant navigation if already on the playing screen
        if (screenManager.getTop() instanceof MyMediaPlayScreen) {
            return;
        }
        screenManager.push(MyMediaPlayScreen.createScreenFromPlaying(
                getCarContext(), mMediaSessionController));
    }
}

אם אתם משתמשים בפעילות טרמפולינה, בדקו את פעולת Intent בתוך onCreate(). מעבירים את הפעולה הזו אל כוונת היצירה CarAppActivity לפני שקוראים ל-finish().

public class LaunchableTrampoline extends AppCompatActivity {
    private static final String SHOW_MEDIA_PLAYBACK = "androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent receivedIntent = getIntent();
        String action;

        if (SHOW_MEDIA_PLAYBACK.equals(receivedIntent.getAction())) {
            action = SHOW_MEDIA_PLAYBACK;
        } else {
            action = Intent.ACTION_MAIN;
        }

        Intent intent = new Intent(action);
        intent.setClassName(getPackageName(), "androidx.car.app.activity.CarAppActivity");
        startActivity(intent);
        finish();
    }
}