Android TV 主屏幕 提供了一个以 频道 和 节目 表格的形式显示推荐内容的界面。每行对应一个频道。一个频道包含该频道上每个可用节目的卡片:
本文档介绍了如何向主屏幕添加频道和节目、更新内容、处理用户操作,以及为用户提供最佳体验。(如果您想深入了解该 API,请试用主屏幕 Codelab或观看2017 年 I/O Android TV 大会视频)
主屏幕界面
应用可以创建新频道,在频道中添加、移除和更新节目,还可以控制频道中节目的顺序。例如,应用可以创建一个名为“新内容”的频道,并显示新推出节目的卡片。
应用无法控制频道在主屏幕上显示的顺序。在您的应用创建新频道后,主屏幕会将其添加到频道列表末尾。用户可以重新排列频道顺序,也可以隐藏或显示频道。
“接下来观看”频道
“接下来观看”频道显示在主屏幕上的第二行,位于应用行下方。此频道由系统创建和维护。您的应用可以向“接下来观看”频道添加节目。如需了解详情,请参阅向“接下来观看”频道 添加节目。
应用频道
您的应用创建的频道均遵循以下流程:
- 用户在您的应用中发现频道并请求将其添加到主屏幕中。
- 应用创建频道并将其添加到
TvProvider(此时频道不可见)。 - 应用要求系统显示该频道。
- 系统要求用户批准这个新频道。
- 新频道显示在主屏幕上的最后一行中。
默认频道
您的应用可以提供任意数量的频道供用户添加到主屏幕。通常,在用户选择并批准频道后,频道才会显示在主屏幕中。每个应用都可以选择创建一个 默认 频道。默认频道比较特殊,因为它会自动显示在主屏幕中;用户无需明确要求添加该频道。
前提条件
Android TV 主屏幕使用 Android 的 TvProvider API 来管理您的应用创建的频道和节目。如需访问提供程序的数据,请将以下权限添加到应用的清单中:
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
借助 TvProvider 支持库,您可以更轻松地使用此提供程序。请将其添加到 build.gradle 文件的依赖项中:
Groovy
implementation 'androidx.tvprovider:tvprovider:1.0.0'
Kotlin
implementation("androidx.tvprovider:tvprovider:1.0.0")
如需处理频道和节目,请确保将这些支持库导入到您的程序中:
Kotlin
import android.support.media.tv.Channel
import android.support.media.tv.TvContractCompat
import android.support.media.tv.ChannelLogoUtils
import android.support.media.tv.PreviewProgram
import android.support.media.tv.WatchNextProgram
Java
import android.support.media.tv.Channel;
import android.support.media.tv.TvContractCompat;
import android.support.media.tv.ChannelLogoUtils;
import android.support.media.tv.PreviewProgram;
import android.support.media.tv.WatchNextProgram;
频道
您的应用创建的第一个频道会成为它的默认频道。默认频道会自动显示在主屏幕上, 而您创建的所有其他频道,则只有在用户选择并接受之后才会显示在主屏幕上。
创建频道
应用只有在前台运行时才可要求系统显示新添加的频道。这样可以防止应用在用户运行其他应用时显示对话框,请求批准添加您的频道。如果您尝试在后台运行时添加频道,Activity 的 onActivityResult() 方法会返回状态代码 RESULT_CANCELED。
如需创建频道,请按以下步骤操作:
创建频道 builder 并设置其属性。请注意,频道类型必须是
TYPE_PREVIEW。根据需要添加更多属性。Kotlin
val builder = Channel.Builder() // Every channel you create must have the type `TYPE_PREVIEW` builder.setType(TvContractCompat.Channels.TYPE_PREVIEW) .setDisplayName("Channel Name") .setAppLinkIntentUri(uri)Java
Channel.Builder builder = new Channel.Builder(); // Every channel you create must have the type `TYPE_PREVIEW` builder.setType(TvContractCompat.Channels.TYPE_PREVIEW) .setDisplayName("Channel Name") .setAppLinkIntentUri(uri);将频道插入提供程序:
Kotlin
var channelUri = context.contentResolver.insert( TvContractCompat.Channels.CONTENT_URI, builder.build().toContentValues())Java
Uri channelUri = context.getContentResolver().insert( TvContractCompat.Channels.CONTENT_URI, builder.build().toContentValues());您需要保存频道 ID,以便日后向频道添加节目。从返回的 URI 中提取频道 ID:
Kotlin
var channelId = ContentUris.parseId(channelUri)Java
long channelId = ContentUris.parseId(channelUri);您必须为频道添加徽标。请使用
Uri或Bitmap。徽标图标的大小应为 80dp x 80dp,且必须是不透明的。徽标会显示在圆形遮罩下。
Kotlin
// Choose one or the other storeChannelLogo(context: Context, channelId: Long, logoUri: Uri) // also works if logoUri is a URL storeChannelLogo(context: Context, channelId: Long, logo: Bitmap)Java
// Choose one or the other storeChannelLogo(Context context, long channelId, Uri logoUri); // also works if logoUri is a URL storeChannelLogo(Context context, long channelId, Bitmap logo);创建默认频道(可选):在应用创建第一个 频道后,您可以将它设为默认频道,这样该频道就会立即显示在主 屏幕中,而无需用户执行任何操作。您创建的所有其他频道 则要在用户明确选择后才会显示。
Kotlin
TvContractCompat.requestChannelBrowsable(context, channelId)Java
TvContractCompat.requestChannelBrowsable(context, channelId);让默认频道在应用打开之前就显示出来。要实现此行为,您可以添加
BroadcastReceiver,它会监听主屏幕在应用安装后发送的android.media.tv.action.INITIALIZE_PROGRAMS操作。<receiver android:name=".RunOnInstallReceiver" android:exported="true"> <intent-filter> <action android:name="android.media.tv.action.INITIALIZE_PROGRAMS" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </receiver>当您在开发过程中旁加载您的应用时,您可以通过 adb 触发 Intent 来测试这个步骤,其中 your.package.name/.YourReceiverName 即为您应用的
BroadcastReceiver:adb shell am broadcast -a android.media.tv.action.INITIALIZE_PROGRAMS -n \ your.package.name/.YourReceiverName在极少数情况下,您的应用可能会在用户启动它的同时收到广播。请确保您的代码不会尝试多次添加默认频道。
更新频道
更新频道的过程与创建频道非常相似。
请使用另一个 Channel.Builder 来设置需要更改的属性。
请使用 ContentResolver 来更新频道。请使用最初添加频道时保存的频道 ID:
Kotlin
context.contentResolver.update(
TvContractCompat.buildChannelUri(channelId),
builder.build().toContentValues(),
null,
null
)
Java
context.getContentResolver().update(TvContractCompat.buildChannelUri(channelId),
builder.build().toContentValues(), null, null);
如需更新频道徽标,请使用 storeChannelLogo()。
删除频道
Kotlin
context.contentResolver.delete(TvContractCompat.buildChannelUri(channelId), null, null)
Java
context.getContentResolver().delete(TvContractCompat.buildChannelUri(channelId), null, null);
节目
节目是指在频道内显示的各个内容卡片。您可以将节目发布到应用的自定义频道和系统管理的“接下来观看”频道。
向应用频道中添加节目
创建 PreviewProgram.Builder 并设置其属性:
Kotlin
val builder = PreviewProgram.Builder()
builder.setChannelId(channelId)
.setType(TvContractCompat.PreviewPrograms.TYPE_CLIP)
.setTitle("Title")
.setDescription("Program description")
.setPosterArtUri(uri)
.setIntentUri(uri)
.setInternalProviderId(appProgramId)
Java
PreviewProgram.Builder builder = new PreviewProgram.Builder();
builder.setChannelId(channelId)
.setType(TvContractCompat.PreviewPrograms.TYPE_CLIP)
.setTitle("Title")
.setDescription("Program description")
.setPosterArtUri(uri)
.setIntentUri(uri)
.setInternalProviderId(appProgramId);
根据节目类型添加更多属性。(要查看每种类型的节目可以使用的属性 ,请参阅以下表格。)
将节目插入提供程序:
Kotlin
var programUri = context.contentResolver.insert(TvContractCompat.PreviewPrograms.CONTENT_URI,
builder.build().toContentValues())
Java
Uri programUri = context.getContentResolver().insert(TvContractCompat.PreviewPrograms.CONTENT_URI,
builder.build().toContentValues());
提取节目 ID 以供日后引用:
Kotlin
val programId = ContentUris.parseId(programUri)
Java
long programId = ContentUris.parseId(programUri);
向“接下来观看”频道添加节目
如需向“接下来观看”频道插入节目,请参阅向“接下来观看”频道添加节目。
更新节目
您可以更新节目信息。例如,您可能需要更新影片的租借价格,或更新显示用户观看节目进度的进度条。
请使用 PreviewProgram.Builder 来设置您需要更改的属性,然后调用 getContentResolver().update
以更新节目。请指定最初添加节目时保存的节目 ID:
Kotlin
context.contentResolver.update(
TvContractCompat.buildPreviewProgramUri(programId),
builder.build().toContentValues(), null, null
)
Java
context.getContentResolver().update(TvContractCompat.buildPreviewProgramUri(programId),
builder.build().toContentValues(), null, null);
删除节目
Kotlin
context.contentResolver
.delete(TvContractCompat.buildPreviewProgramUri(programId), null, null)
Java
context.getContentResolver().delete(TvContractCompat.buildPreviewProgramUri(programId), null, null);
处理用户操作
您的应用可以提供用于显示和添加频道的界面来帮助用户发现内容。在频道显示在主屏幕上之后,您的应用还需处理用户与这些频道的交互。
发现并添加频道
您的应用可以提供一个界面元素,供用户选择并添加应用的频道(例如,要求添加频道的按钮)。
在用户请求特定频道后,请执行此代码,请求用户同意将该频道添加到主屏幕界面中:
Kotlin
val intent = Intent(TvContractCompat.ACTION_REQUEST_CHANNEL_BROWSABLE)
intent.putExtra(TvContractCompat.EXTRA_CHANNEL_ID, channelId)
try {
activity.startActivityForResult(intent, 0)
} catch (e: ActivityNotFoundException) {
// handle error
}
Java
Intent intent = new Intent(TvContractCompat.ACTION_REQUEST_CHANNEL_BROWSABLE);
intent.putExtra(TvContractCompat.EXTRA_CHANNEL_ID, channelId);
try {
activity.startActivityForResult(intent, 0);
} catch (ActivityNotFoundException e) {
// handle error
}
系统会显示一个对话框,要求用户批准该频道。请在 Activity 的 onActivityResult 方法中处理请求结果(Activity.RESULT_CANCELED 或 Activity.RESULT_OK)。
Android TV 主屏幕事件
当用户与应用发布的节目和频道互动时,主屏幕会向应用发送 intent:
- 当用户选择某个频道的徽标后,主屏幕会将存储在该频道的 APP_LINK_INTENT_URI 属性中的
Uri发送给应用。此时,应用应启动其主界面或与所选频道相关的视图。 - 当用户选择某个节目后,主屏幕会将存储在该节目的 INTENT_URI 属性中的
Uri发送给应用。应用应播放所选内容。 - 用户可以表示其对某个节目不再感兴趣,想要从主屏幕界面中移除该节目。系统会从界面中移除该节目,并将该节目的 ID 以及一个 Intent(android.media.tv.ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED 或 android.media.tv.ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED)一起发送给拥有该节目的应用。应用应从提供程序中移除该节目,并且不应重新插入该节目。
请务必针对主屏幕为用户互动发送的所有 Uris 创建 intent 过滤器;例如:
<receiver
android:name=".WatchNextProgramRemoved"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.media.tv.ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED" />
</intent-filter>
</receiver>
其他注意事项
- 许多 TV 应用都会要求用户登录。在这种情况下,监听
android.media.tv.action.INITIALIZE_PROGRAMS的BroadcastReceiver应该向未经身份验证的用户推荐频道内容。 例如,您的应用最初可以显示最佳内容或当前热门内容。在用户登录后,它可以显示个性化内容。这是应用在用户登录前进行追加销售的好机会。 - 当您的应用不在前台运行,并且您需要更新频道或节目时,请使用
JobScheduler来安排工作(请参阅JobScheduler和JobService)。 - 如果您的应用行为不当(例如:不断向提供程序发送垃圾数据),系统可以撤消应用的提供程序权限。请务必使用 try-catch 语句封装访问提供程序的代码,以处理安全异常。
在更新节目和频道之前,请向提供方查询需要更新的数据并对数据进行对账。例如,不需要更新用户想从界面中移除的节目。请使用后台作业,该作业会查询现有数据并请求批准您的频道,然后再将您的数据插入或更新到提供程序。您可以在应用启动时以及应用需要更新其数据时运行此作业。
Kotlin
context.contentResolver
.query(
TvContractCompat.buildChannelUri(channelId),
null, null, null, null).use({
cursor-> if (cursor != null and cursor.moveToNext()) {
val channel = Channel.fromCursor(cursor)
if (channel.isBrowsable()) {
//update channel's programs
}
}
})
Java
try (Cursor cursor = context.getContentResolver()
.query(
TvContractCompat.buildChannelUri(channelId),
null,
null,
null,
null)) {
if (cursor != null && cursor.moveToNext()) {
Channel channel = Channel.fromCursor(cursor);
if (channel.isBrowsable()) {
//update channel's programs
}
}
}
针对所有图片(徽标、图标、内容图片)使用不同的 URI。更新图片时请务必使用不同的 URI。系统会缓存所有图片。如果您在更改图片时未更改 URI,系统会继续显示旧图片。
请注意,不能使用 WHERE 语句;如果使用 WHERE 语句调用提供程序,系统将会抛出安全异常。
属性
本部分将分别介绍频道属性和节目属性。
频道属性
您必须为每个频道指定以下属性:
| 属性 | 备注 |
|---|---|
| TYPE | 设为 TYPE_PREVIEW。 |
| DISPLAY_NAME | 设置为频道名称。 |
| APP_LINK_INTENT_URI | 在用户选择频道徽标后,系统会发送 Intent 以启动提供与频道相关的内容的 Activity。请将此属性设为该 Activity 的 intent 过滤器中使用的 URI。 |
此外,频道还有六个字段预留供应用内部使用。 这些字段可用于存储有助于应用将频道映射到其内部数据结构的键或其他值:
- INTERNAL_PROVIDER_ID
- INTERNAL_PROVIDER_DATA
- INTERNAL_PROVIDER_FLAG1
- INTERNAL_PROVIDER_FLAG2
- INTERNAL_PROVIDER_FLAG3
- INTERNAL_PROVIDER_FLAG4
节目属性
请参阅每种节目类型所对应的属性的说明页面:
示例代码
如需详细了解如何构建能够与 Android TV 主屏幕交互并向主屏幕添加 频道和节目的应用,请参阅我们的主屏幕 Codelab。