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

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

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

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

Пример строки рекомендаций на главном экране Android TV.
Рисунок 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
    }
}

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-фреймворк. Фреймворк получает рекомендации в виде объектов 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
    }

...

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

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

После установки значений вы формируете уведомление, присваивая ему значения из класса-конструктора и вызывая метод 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
    }
}

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

Котлин

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

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

Java

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

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

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

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

Пример строки рекомендаций на главном экране Android TV.
Рисунок 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
    }
}

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-фреймворк. Фреймворк получает рекомендации в виде объектов 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
    }

...

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

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

После установки значений вы формируете уведомление, присваивая ему значения из класса-конструктора и вызывая метод 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
    }
}

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

Котлин

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

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

Java

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

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

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

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

Пример строки рекомендаций на главном экране Android TV.
Рисунок 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
    }
}

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-фреймворк. Фреймворк получает рекомендации в виде объектов 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
    }

...

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

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

После установки значений вы формируете уведомление, присваивая ему значения из класса-конструктора и вызывая метод 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
    }
}

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

Котлин

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

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

Java

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