Рекомендации в Android N и более ранних версиях

При взаимодействии с телевизорами пользователи обычно предпочитают минимально вводить данные перед просмотром контента. Идеальный сценарий для многих пользователей телевизора: сядьте, включите и смотрите. Наименьшее количество шагов для получения пользователями контента, который им нравится, обычно является тем путем, который они предпочитают.

Примечание. Используйте описанные здесь API-интерфейсы только для предоставления рекомендаций в приложениях, работающих в версиях Android до Android 7.1 включительно (уровень API 25). Чтобы предоставлять рекомендации для приложений, работающих на Android 8.0 (уровень API 26) и более поздних версиях, ваше приложение должно использовать каналы рекомендаций .

Платформа Android обеспечивает минимальное взаимодействие с пользователем, предоставляя строку рекомендаций на главном экране. Рекомендации по контенту отображаются в первой строке главного экрана телевизора после первого использования устройства. Рекомендации из каталога контента вашего приложения могут помочь вернуть пользователей в ваше приложение.

Рисунок 1. Пример строки рекомендаций.

В этом руководстве вы узнаете, как создавать рекомендации и предоставлять их платформе Android, чтобы пользователи могли легко находить и наслаждаться контентом вашего приложения. См. также пример реализации в примере приложения Leanback .

Лучшие практики для рекомендаций

Рекомендации помогают пользователям быстро найти контент и приложения, которые им нравятся. Создание качественных и актуальных для пользователей рекомендаций является важным фактором в создании удобного пользовательского опыта при использовании вашего ТВ-приложения. По этой причине вам следует тщательно продумать, какие рекомендации вы предоставляете пользователю, и тщательно ими управлять.

Виды рекомендаций

Когда вы создаете рекомендации, вам следует связывать пользователей с незавершенными действиями по просмотру или предлагать действия, которые распространяются на связанный контент. Вот некоторые конкретные рекомендации, которые вам следует принять во внимание:

  • Рекомендации по содержанию продолжения следующей серии, чтобы пользователи могли возобновить просмотр сериала. Или используйте рекомендации по продолжению для приостановленных фильмов, телешоу или подкастов, чтобы пользователи могли вернуться к просмотру приостановленного контента всего за несколько кликов.
  • Рекомендации по новому контенту , например для нового первого эпизода, если пользователь закончил просмотр другого сериала. Кроме того, если ваше приложение позволяет пользователям подписываться на контент, следить за ним или отслеживать его, используйте рекомендации по новому контенту для непросматриваемых элементов в их списке отслеживаемого контента.
  • Рекомендации по связанному контенту, основанные на историческом поведении пользователей при просмотре.

Дополнительную информацию о том, как создавать карточки рекомендаций для удобства пользователей, см. в разделе «Строка рекомендаций» в спецификации дизайна Android TV.

Обновить рекомендации

При обновлении рекомендаций не удаляйте и не публикуйте их повторно, поскольку в этом случае рекомендации появятся в конце строки рекомендаций. После воспроизведения элемента контента, например фильма, удалите его из рекомендаций.

Настройте рекомендации

Вы можете настроить карточки рекомендаций для передачи информации о бренде, задав элементы пользовательского интерфейса, такие как передний план и фоновое изображение карточки, цвет, значок приложения, заголовок и подзаголовок. Дополнительную информацию см. в разделе «Строка рекомендаций» в спецификации дизайна Android TV.

Групповые рекомендации

При желании вы можете группировать рекомендации на основе источника рекомендаций. Например, ваше приложение может предоставлять две группы рекомендаций: рекомендации по контенту, на который подписан пользователь, и рекомендации по новому популярному контенту, о котором пользователь может не знать.

Система ранжирует и упорядочивает рекомендации для каждой группы отдельно при создании или обновлении строки рекомендаций. Предоставляя информацию о группе для своих рекомендаций, вы можете гарантировать, что ваши рекомендации не будут располагаться ниже несвязанных рекомендаций.

Используйте NotificationCompat.Builder.setGroup() чтобы установить строку группового ключа рекомендации. Например, чтобы пометить рекомендацию как принадлежащую к группе, содержащей новый популярный контент, вы можете вызвать setGroup("trending") .

Создать сервис рекомендаций

Рекомендации по контенту создаются с помощью фоновой обработки. Чтобы ваше приложение вносило вклад в рекомендации, создайте службу, которая периодически добавляет списки из каталога вашего приложения в список рекомендаций системы.

В следующем примере кода показано, как расширить IntentService для создания службы рекомендаций для вашего приложения.

Котлин

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
    }
}

Ява

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. Платформа получает рекомендации в виде объектов Notification , которые используют определенный шаблон и помечены определенной категорией.

Установка значений

Чтобы задать значения элементов пользовательского интерфейса для карточки рекомендаций, вы создаете класс построителя, который следует описанному ниже шаблону построителя. Сначала вы задаете значения элементов карточки рекомендаций.

Котлин

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
    }

...

Ява

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;
        }
...

Создать уведомление

После установки значений вы создаете уведомление, присваивая уведомлению значения из класса компоновщика и вызывая NotificationCompat.Builder.build() .

Кроме того, обязательно вызовите setLocalOnly() , чтобы уведомление NotificationCompat.BigPictureStyle не отображалось на других устройствах.

В следующем примере кода показано, как создать рекомендацию.

Котлин

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
    }
}

Ява

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 расширяется для периодического запуска службы рекомендаций каждые полчаса:

Котлин

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
    }
}

Ява

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 должна запускаться после запуска телевизионного устройства, на котором он установлен. Для этого зарегистрируйте этот класс в манифесте приложения с помощью фильтра намерений, который прослушивает завершение процесса загрузки устройства. В следующем примере кода показано, как добавить эту конфигурацию в манифест:

<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() вашего класса службы рекомендаций отправьте рекомендацию менеджеру следующим образом:

Котлин

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

Ява

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