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

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

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

המדריך הזה מיועד למשתמשים שכבר יש להם אפליקציית מדיה שמפעילה אודיו בטלפון, ושמבנה אפליקציית המדיה שלהם תואם לארכיטקטורת אפליקציות המדיה של Android. ספריית האפליקציות לרכב מאפשרת להחליף את חוויית השימוש באפליקציה בתבניות במקום בתבניות שנוצרו באמצעות מבנה הנתונים של יצירת אפליקציות מדיה לרכב MediaBrowser. עדיין צריך לספק MediaSession ללחצני ההפעלה, וגם 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

יש שתי דרכים שונות להפצת אפליקציית מדיה שמופעלת באמצעות Car App Library ב-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.software.car.templates_host.media ל-android:required="false".

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

לאחר מכן, פועלים לפי ההנחיות של ספריית האפליקציות לרכב ל-AAOS ומוסיפים CarAppActivity שאפשר להפעיל (או פעילות טרמפולינה). צריך להגדיר את הפעילות כ-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 לפעילות של ספריית Car App שאפשר להפעיל.

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) ומעבירים את אסימון התאימות הזה אל Car App Library ב-.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 כולל רכיבים מותאמים אישית ליכולות גלישה ייחודיות, כמו Chips,‏ Progress Bars, ‏ Condensed Items,‏ Interactive and Expanded Header,‏ Spotlight Sections ו-Banners.

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

איור 3: צורה A SectionedItemTemplate שמכילה Chips, Condensed Items,‏ Interactive Header, Grid Items וMinimized Control Panel

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

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

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

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

אם אתם משתמשים ב-SectionedItemTemplate, תוכלו להוסיף כפתור פעולה שיעביר אתכם למסך הפעלת המדיה. משתמשים בפעולה הסטנדרטית של Car App Library 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 (או 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() כדי לנתח את הכוונה הנכנסת. אם פעולת הכוונה הנכנסת תואמת ל-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();
    }
}