המלצות ב-Android N ובגרסאות קודמות

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

הערה: מומלץ להשתמש בממשקי ה-API שמתוארים כאן ליצירת המלצות באפליקציות שפועלות בגרסאות Android עד גרסה 7.1 (רמת API 25) בלבד, כולל Android 7.1. לספק המלצות לאפליקציות שפועלות בגרסת Android 8.0 (רמת API 26) ואילך, האפליקציה חייבת להשתמש בהן ערוצי המלצות.

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

איור 1. דוגמה לשורת ההמלצות.

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

שיטות מומלצות לעבודה עם המלצות

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

סוגים של המלצות

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

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

אפשר לקרוא מידע נוסף על עיצוב כרטיסי המלצות כך שיתנו את חוויית המשתמש הטובה ביותר. שורת המלצה במפרט העיצוב של Android TV.

רענון ההמלצות

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

התאמה אישית של המלצות

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

המלצות לקבוצות

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

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

כדאי להשתמש NotificationCompat.Builder.setGroup() כדי להגדיר את מחרוזת מפתח הקבוצה של המלצה. עבור לדוגמה, כדי לסמן שהמלצה מסוימת שייכת לקבוצה שמכילה תוכן פופולרי חדש, אפשר להתקשר אל setGroup("trending").

יצירת שירות המלצות

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

הקוד הבא מדגים איך להרחיב את IntentService אל ליצור שירות המלצות לאפליקציה:

Kotlin

class UpdateRecommendationsService : IntentService("RecommendationService") {
    override protected fun onHandleIntent(intent: Intent) {
        Log.d(TAG, "Updating recommendation cards")
        val recommendations = VideoProvider.getMovieList()
        if (recommendations == null) return

        var count = 0

        try {
            val builder = RecommendationBuilder()
                    .setContext(applicationContext)
                    .setSmallIcon(R.drawable.videos_by_google_icon)

            for (entry in recommendations.entrySet()) {
                for (movie in entry.getValue()) {
                    Log.d(TAG, "Recommendation - " + movie.getTitle())

                    builder.setBackground(movie.getCardImageUrl())
                            .setId(count + 1)
                            .setPriority(MAX_RECOMMENDATIONS - count)
                            .setTitle(movie.getTitle())
                            .setDescription(getString(R.string.popular_header))
                            .setImage(movie.getCardImageUrl())
                            .setIntent(buildPendingIntent(movie))
                            .build()
                    if (++count >= MAX_RECOMMENDATIONS) {
                        break
                    }
                }
                if (++count >= MAX_RECOMMENDATIONS) {
                    break
                }
            }
        } catch (e: IOException) {
            Log.e(TAG, "Unable to update recommendation", e)
        }
    }

    private fun buildPendingIntent(movie: Movie): PendingIntent {
        val detailsIntent = Intent(this, DetailsActivity::class.java)
        detailsIntent.putExtra("Movie", movie)

        val stackBuilder = TaskStackBuilder.create(this)
        stackBuilder.addParentStack(DetailsActivity::class.java)
        stackBuilder.addNextIntent(detailsIntent)

        // Ensure a unique PendingIntents, otherwise all
        // recommendations end up with the same PendingIntent
        detailsIntent.setAction(movie.getId().toString())

        val intent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
        return intent
    }

    companion object {
        private val TAG = "UpdateRecommendationsService"
        private val MAX_RECOMMENDATIONS = 3
    }
}

Java

public class UpdateRecommendationsService extends IntentService {
    private static final String TAG = "UpdateRecommendationsService";
    private static final int MAX_RECOMMENDATIONS = 3;

    public UpdateRecommendationsService() {
        super("RecommendationService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.d(TAG, "Updating recommendation cards");
        HashMap<String, List<Movie>> recommendations = VideoProvider.getMovieList();
        if (recommendations == null) return;

        int count = 0;

        try {
            RecommendationBuilder builder = new RecommendationBuilder()
                    .setContext(getApplicationContext())
                    .setSmallIcon(R.drawable.videos_by_google_icon);

            for (Map.Entry<String, List<Movie>> entry : recommendations.entrySet()) {
                for (Movie movie : entry.getValue()) {
                    Log.d(TAG, "Recommendation - " + movie.getTitle());

                    builder.setBackground(movie.getCardImageUrl())
                            .setId(count + 1)
                            .setPriority(MAX_RECOMMENDATIONS - count)
                            .setTitle(movie.getTitle())
                            .setDescription(getString(R.string.popular_header))
                            .setImage(movie.getCardImageUrl())
                            .setIntent(buildPendingIntent(movie))
                            .build();

                    if (++count >= MAX_RECOMMENDATIONS) {
                        break;
                    }
                }
                if (++count >= MAX_RECOMMENDATIONS) {
                    break;
                }
            }
        } catch (IOException e) {
            Log.e(TAG, "Unable to update recommendation", e);
        }
    }

    private PendingIntent buildPendingIntent(Movie movie) {
        Intent detailsIntent = new Intent(this, DetailsActivity.class);
        detailsIntent.putExtra("Movie", movie);

        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        stackBuilder.addParentStack(DetailsActivity.class);
        stackBuilder.addNextIntent(detailsIntent);
        // Ensure a unique PendingIntents, otherwise all
        // recommendations end up with the same PendingIntent
        detailsIntent.setAction(Long.toString(movie.getId()));

        PendingIntent intent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
        return intent;
    }
}

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

<manifest ... >
  <application ... >
    ...

    <service
            android:name="com.example.android.tvleanback.UpdateRecommendationsService"
            android:enabled="true" />
  </application>
</manifest>

יצירת המלצות

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

הגדרת הערכים

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

Kotlin

class RecommendationBuilder {
    ...

    fun setTitle(title: String): RecommendationBuilder {
        this.title = title
        return this
    }

    fun setDescription(description: String): RecommendationBuilder {
        this.description = description
        return this
    }

    fun setImage(uri: String): RecommendationBuilder {
        imageUri = uri
        return this
    }

    fun setBackground(uri: String): RecommendationBuilder {
        backgroundUri = uri
        return this
    }

...

Java

public class RecommendationBuilder {
    ...

    public RecommendationBuilder setTitle(String title) {
            this.title = title;
            return this;
        }

        public RecommendationBuilder setDescription(String description) {
            this.description = description;
            return this;
        }

        public RecommendationBuilder setImage(String uri) {
            imageUri = uri;
            return this;
        }

        public RecommendationBuilder setBackground(String uri) {
            backgroundUri = uri;
            return this;
        }
...

יצירת ההתראה

אחרי שמגדירים את הערכים, יוצרים את ההתראה ומקצה את הערכים מה-builder. את הכיתה, ולהתקשר אל NotificationCompat.Builder.build().

כמו כן, חשוב להתקשר setLocalOnly() כך שההודעה NotificationCompat.BigPictureStyle לא תופיע במכשירים אחרים.

הקוד לדוגמה הבא מדגים איך לבנות המלצה.

Kotlin

class RecommendationBuilder {
    ...

    @Throws(IOException::class)
    fun build(): Notification {
        ...

        val notification = NotificationCompat.BigPictureStyle(
        NotificationCompat.Builder(context)
                .setContentTitle(title)
                .setContentText(description)
                .setPriority(priority)
                .setLocalOnly(true)
                .setOngoing(true)
                .setColor(context.resources.getColor(R.color.fastlane_background))
                .setCategory(Notification.CATEGORY_RECOMMENDATION)
                .setLargeIcon(image)
                .setSmallIcon(smallIcon)
                .setContentIntent(intent)
                .setExtras(extras))
                .build()

        return notification
    }
}

Java

public class RecommendationBuilder {
    ...

    public Notification build() throws IOException {
        ...

        Notification notification = new NotificationCompat.BigPictureStyle(
                new NotificationCompat.Builder(context)
                        .setContentTitle(title)
                        .setContentText(description)
                        .setPriority(priority)
                        .setLocalOnly(true)
                        .setOngoing(true)
                        .setColor(context.getResources().getColor(R.color.fastlane_background))
                        .setCategory(Notification.CATEGORY_RECOMMENDATION)
                        .setLargeIcon(image)
                        .setSmallIcon(smallIcon)
                        .setContentIntent(intent)
                        .setExtras(extras))
                .build();

        return notification;
    }
}

הפעלת שירות המלצות

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

Kotlin

class BootupActivity : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Log.d(TAG, "BootupActivity initiated")
        if (intent.action.endsWith(Intent.ACTION_BOOT_COMPLETED)) {
            scheduleRecommendationUpdate(context)
        }
    }

    private fun scheduleRecommendationUpdate(context: Context) {
        Log.d(TAG, "Scheduling recommendations update")
        val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
        val recommendationIntent = Intent(context, UpdateRecommendationsService::class.java)
        val alarmIntent = PendingIntent.getService(context, 0, recommendationIntent, 0)
        alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                INITIAL_DELAY,
                AlarmManager.INTERVAL_HALF_HOUR,
                alarmIntent
        )
    }

    companion object {
        private val TAG = "BootupActivity"
        private val INITIAL_DELAY:Long = 5000
    }
}

Java

public class BootupActivity extends BroadcastReceiver {
    private static final String TAG = "BootupActivity";

    private static final long INITIAL_DELAY = 5000;

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "BootupActivity initiated");
        if (intent.getAction().endsWith(Intent.ACTION_BOOT_COMPLETED)) {
            scheduleRecommendationUpdate(context);
        }
    }

    private void scheduleRecommendationUpdate(Context context) {
        Log.d(TAG, "Scheduling recommendations update");

        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        Intent recommendationIntent = new Intent(context, UpdateRecommendationsService.class);
        PendingIntent alarmIntent = PendingIntent.getService(context, 0, recommendationIntent, 0);

        alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                INITIAL_DELAY,
                AlarmManager.INTERVAL_HALF_HOUR,
                alarmIntent);
    }
}

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

<manifest ... >
  <application ... >
    <receiver android:name="com.example.android.tvleanback.BootupActivity"
              android:enabled="true"
              android:exported="false">
      <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
      </intent-filter>
    </receiver>
  </application>
</manifest>

חשוב: כדי לקבל התראה על השלמת אתחול, צריך להתקין את האפליקציה מבקשת את ההרשאה RECEIVE_BOOT_COMPLETED. מידע נוסף זמין כאן: ACTION_BOOT_COMPLETED.

בסיווג השירות של ההמלצות onHandleIntent() צריך לפרסם את ההמלצה למנהל באופן הבא:

Kotlin

val notification = notificationBuilder.build()
notificationManager.notify(id, notification)

Java

Notification notification = notificationBuilder.build();
notificationManager.notify(id, notification);