在与电视互动时,用户通常倾向于在观看内容之前提供最少量的输入信息。对于许多电视用户而言,最理想的场景就是:坐下来,打开电视,然后观看。通常,用户更喜欢执行最少的步骤,让用户看到他们喜欢的内容。
注意:本文介绍的 API 仅适用于向在 Android 7.1(API 级别 25)及更低版本运行的应用中提供推荐内容。如需为在 Android 8.0(API 级别 26)及更高版本中运行的应用提供推荐,您的应用必须使用推荐频道。
Android 框架通过在主屏幕上提供推荐行来帮助尽量减少输入互动。首次使用设备后,电视主屏幕的第一行会显示内容推荐。从应用的内容目录中贡献推荐内容有助于吸引用户再次使用您的应用。
![](https://developer.android.google.cn/static/images/tv/home-recommendations.png?authuser=5&hl=nl)
图 1. “推荐”行示例。
本指南介绍了如何创建推荐并将其提供给 Android 框架,以便用户轻松发现和欣赏您的应用内容。另请参阅 Leanback 示例应用中的实现示例。
创建推荐的最佳做法
推荐可以帮助用户快速找到他们喜爱的内容和应用。为了通过 TV 应用打造出色的用户体验,创建贴合用户需求的优质推荐至关重要。因此,您应该仔细考虑向用户提供哪些推荐,并密切管理推荐。
推荐类型
创建推荐时,您应该将用户链接回未完成的观看活动,或者推荐将相关内容扩展到相关内容的活动。以下是您应该考虑的一些特定类型的建议:
- 接续内容推荐,供用户继续观看连续剧的下一集。或者,针对已暂停的电影、电视节目或播客使用接续建议,以便用户只需点击几下即可重新观看暂停的内容。
- 新内容推荐,例如在用户观看完其他连续剧时推荐新的首次播放剧集。此外,如果您的应用允许用户订阅、关注或跟踪内容,请针对其跟踪内容列表中未观看的项目使用新内容推荐。
- 相关内容推荐,根据用户的历史观看行为推荐。
如需详细了解如何设计推荐卡片以提供最佳用户体验,请参阅 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 框架。框架会以 Notification
对象的形式接收推荐,这些对象使用特定模板并使用特定类别进行标记。
设置值
如需为推荐卡片设置界面元素值,您需要创建遵循下述构建器模式的构建器类。首先,设置推荐卡片元素的值。
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; } ...
创建通知
设置值后,您便可以构建通知,将构建器类中的值分配给通知,然后调用 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);