在与电视互动时,用户通常会希望输入操作越简单越好。对于许多电视用户而言,最理想的场景就是:坐下来,打开电视,然后观看。通常,用户会希望执行最少的步骤,就能观看自己喜欢的内容。
注意:本文中介绍的 API 仅适用于向运行 Android 7.1(API 级别 25)及更低版本的应用提供推荐。如需向运行 Android 8.0(API 级别 26)及更高版本的应用提供推荐,您的应用必须使用推荐频道。
Android 框架通过在主屏幕上显示内容推荐行帮助尽量减少输入互动。首次使用设备后,电视主屏幕上的第一行中将会显示内容推荐。从应用的内容目录中贡献推荐内容有助于让用户回到您的应用。
图 1. 内容推荐行示例。
本课程将介绍如何创建推荐并将其提供给 Android 框架,以便用户可以轻松发现和欣赏您的应用内容。这里讨论的是 Android TV GitHub 代码库中 Android Leanback 示例应用的部分代码。
创建推荐的最佳做法
推荐可以帮助用户快速找到他们喜爱的内容和应用。为了通过电视应用提供出色的用户体验,创建贴合用户需求的高质量推荐至关重要。因此,您应该仔细考虑要向用户推荐的内容,并对其进行有效的管理。
推荐类型
创建推荐时,应将用户链接回未完成的观看活动,或者推荐扩展到相关内容的活动。以下是您应该考虑的一些具体类型的推荐:
接续内容推荐,供用户继续观看连续剧的下一集。或者,对暂停播放的电影、电视节目或播客使用接续推荐,以便用户只需点击几下就可以返回观看暂停的内容。
新内容推荐,例如在用户观看完一部连续剧后推荐新的首播剧集。此外,如果您的应用允许用户订阅、关注或跟踪内容,请对用户的跟踪内容列表中未观看的项目使用新内容推荐。
相关内容推荐,根据用户的历史观看行为推荐内容。
如需详细了解如何设计推荐卡片以提供最佳用户体验,请参阅 Android TV 设计规范中的推荐行。
刷新推荐
刷新推荐时,不要只是移除并重新发布它们,因为这样做会使推荐显示在推荐行的末尾。一旦内容项(比如影片)已播放,就将其从推荐中移除。
自定义推荐
您可以通过设置界面元素(例如卡片的前景和背景图片、颜色、应用图标、标题和副标题)自定义推荐卡片,以传递品牌信息。如需了解详情,请参阅 Android TV 设计规范中的推荐行。
对推荐进行分组
您可以选择根据推荐来源对推荐进行分组。例如,您的应用可以提供两组推荐:针对用户订阅内容的推荐,以及针对用户可能不了解的新趋势内容的推荐。
创建或更新推荐行时,系统会对每个分组中的推荐分别进行排名和排序。通过为推荐提供分组信息,您可以确保推荐不会列在无关的推荐下方。
使用 setGroup("trending")。
创建推荐服务
内容推荐是通过后台处理创建的。为了让您的应用为推荐贡献内容,可以创建一个服务,定期将应用目录中所列的项目添加到系统的推荐列表中。
以下代码示例说明了如何扩展
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> 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> 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;
}
}
为了让该服务能够被系统识别并运行,请使用应用清单来注册它。以下代码段说明了如何将此类声明为服务:
...
android:name="com.example.android.tvleanback.UpdateRecommendationsService"
android:enabled="true" />
构建推荐
推荐服务开始运行后,它就必须创建推荐并将其传递给 Android 框架。框架会以
设置值
要设置推荐卡片的界面元素值,您需要创建遵循下述 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;
}
...
创建通知
设置值之后,您便可以通过以下方式构建通知:将构建器类中的值分配给通知,然后调用
以下代码示例展示了如何构建推荐。
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;
}
}
运行推荐服务
为了创建最新的推荐,您的应用的推荐服务必须定期运行。为了运行您的服务,请创建一个运行定时器并定期调用它的类。以下代码示例扩展了
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);
}
}
android:enabled="true"
android:exported="false">
在推荐服务类的
Kotlin
val notification = notificationBuilder.build()
notificationManager.notify(id, notification)Java
Notification notification = notificationBuilder.build();
notificationManager.notify(id, notification);