本指南包含面向开发者的说明,介绍了如何使用 Engage SDK 与 Google TV 共享应用订阅和使用权数据。用户可以在电视、移动设备和平板电脑上的 Google TV 体验中直接查找他们有权观看的内容,并让 Google TV 向他们提供高度相关的内容推荐。
前提条件
您必须先引入媒体操作 Feed,然后才能使用设备使用权 API。如果您尚未完成媒体操作 Feed 新手入门流程,请先完成此流程。
准备工作
在开始之前,请完成以下步骤 验证您的应用是否以 API 级别 19 或更高级别为目标平台
将
com.google.android.engage
库添加到您的应用中:集成时需要使用单独的 SDK:一个用于移动应用,另一个用于 TV 应用。
适用于移动设备
dependencies { implementation 'com.google.android.engage:engage-core:1.5.5 }
适用于电视
dependencies { implementation 'com.google.android.engage:engage-tv:1.0.2 }
在
AndroidManifest.xml
文件中将 Engage 服务环境设为生产环境。对于移动 APK
<meta-data android:name="com.google.android.engage.service.ENV" android:value="PRODUCTION"> </meta-data>
For TV apk
<meta-data android:name="com.google.android.engage.service.ENV" android:value="PRODUCTION"> </meta-data>
在将 APK 发送给 Google 之前,请在 AndroidManifest.xml 文件中将 Engage 服务环境设置为生产环境。为了获得最佳性能并确保未来兼容性,请仅在应用位于前台且用户正在与其积极互动时(例如应用启动、登录后或活跃使用期间)发布数据。不建议从后台进程中发布。
在以下事件中发布订阅信息:
- 用户登录您的应用。
- 用户在个人资料之间切换(如果支持个人资料)。
- 用户购买了新订阅。
- 用户升级现有订阅。
- 用户订阅到期。
集成
本部分提供了实现 AccountProfile
和 SubscriptionEntity
以管理各种订阅类型所需的代码示例和说明。
用户账号和个人资料
如需在 Google TV 上使用个性化功能,请提供账号信息。使用 AccountProfile
提供:
- 账号 ID:代表用户账号的唯一标识符。这可以是实际的账号 ID,也可以是经过适当混淆处理的版本。
// Set the account ID to which the subscription applies.
// Don't set the profile ID because subscription applies to account level.
val accountProfile = AccountProfile.Builder()
.setAccountId("user_account_id")
.setProfileId("user_profile id")
.build();
常规层级订阅
对于订阅了媒体提供商服务的基本服务的用户(例如,订阅层级只有一个,可访问所有付费内容的服务),请提供以下基本详细信息:
订阅类型:明确指明用户所拥有的具体订阅方案。
SUBSCRIPTION_TYPE_ACTIVE
:用户拥有有效的付费订阅。SUBSCRIPTION_TYPE_ACTIVE_TRIAL
:用户订阅了试用版。SUBSCRIPTION_TYPE_INACTIVE
:用户拥有账号,但没有有效的订阅或试用。
到期时间:可选时间(以毫秒为单位)。指定订阅的到期时间。
提供方软件包名称:指定处理订阅的应用的软件包名称。
示例:媒体提供商 Feed 示例。
"actionAccessibilityRequirement": [
{
"@type": "ActionAccessSpecification",
"category": "subscription",
"availabilityStarts": "2022-06-01T07:00:00Z",
"availabilityEnds": "2026-05-31T07:00:00Z",
"requiresSubscription": {
"@type": "MediaSubscription",
// Don't match this string,
// ID is only used to for reconciliation purpose
"@id": "https://www.example.com/971bfc78-d13a-4419",
// Don't match this, as name is only used for displaying purpose
"name": "Basic common name",
"commonTier": true
}
以下示例会为用户创建 SubscriptionEntity
:
val subscription = SubscriptionEntity
.Builder()
setSubscriptionType(
SubscriptionType.SUBSCRIPTION_TYPE_ACTIVE
)
.setProviderPackageName("com.google.android.example")
// Optional
// December 30, 2025 12:00:00AM in milliseconds since epoch
.setExpirationTimeMillis(1767052800000)
.build();
Premium 订阅
如果应用提供多层级付费订阅套餐,其中包含超出常规层级的扩展内容或功能,请通过向“订阅”添加一项或多项使用权来表示这一点。
此使用权包含以下字段:
- 标识符:此使用权所需的标识符字符串。此值必须与发布到 Google TV 的媒体提供商 Feed 中提供的使用权标识符(请注意,这不是 ID 字段)之一相匹配。
- 名称:这是辅助信息,用于匹配使用权。 虽然这不是强制性要求,但提供简单易懂的使用权名称有助于开发者和支持团队更好地了解用户使用权。例如:Sling Orange。
- Expiration TimeMillis:可选(如果此使用权的到期时间与订阅的到期时间不同),指定此使用权的到期时间(以毫秒为单位)。默认情况下,使用权将随订阅到期而失效。
对于以下媒体提供商 Feed 代码段示例:
"actionAccessibilityRequirement": [
{
"@type": "ActionAccessSpecification",
"category": "subscription",
"availabilityStarts": "2022-06-01T07:00:00Z",
"availabilityEnds": "2026-05-31T07:00:00Z",
"requiresSubscription": {
"@type": "MediaSubscription",
// Don't match this string,
// ID is only used to for reconciliation purpose
"@id": "https://www.example.com/971bfc78-d13a-4419",
// Don't match this, as name is only used for displaying purpose
"name": "Example entitlement name",
"commonTier": false,
// match this identifier in your API. This is the crucial
// entitlement identifier used for recommendation purpose.
"identifier": "example.com:entitlementString1"
}
以下示例会为订阅的用户创建 SubscriptionEntity
:
// Subscription with entitlements.
// The entitlement expires at the same time as its subscription.
val subscription = SubscriptionEntity
.Builder()
.setSubscriptionType(
SubscriptionType.SUBSCRIPTION_TYPE_ACTIVE
)
.setProviderPackageName("com.google.android.example")
// Optional
// December 30, 2025 12:00:00AM in milliseconds
.setExpirationTimeMillis(1767052800000)
.addEntitlement(
SubscriptionEntitlement.Builder()
// matches with the identifier in media provider feed
.setEntitlementId("example.com:entitlementString1")
.setDisplayName("entitlement name1")
.build()
)
.build();
// Subscription with entitlements
// The entitement has different expiration time from its subscription
val subscription = SubscriptionEntity
.Builder()
.setSubscriptionType(
SubscriptionType.SUBSCRIPTION_TYPE_ACTIVE
)
.setProviderPackageName("com.google.android.example")
// Optional
// December 30, 2025 12:00:00AM in milliseconds
.setExpirationTimeMillis(1767052800000)
.addEntitlement(
SubscriptionEntitlement.Builder()
.setEntitlementId("example.com:entitlementString1")
.setDisplayName("entitlement name1")
// You may set the expiration time for entitlement
// December 15, 2025 10:00:00 AM in milliseconds
.setExpirationTimeMillis(1765792800000)
.build())
.build();
关联的服务包的订阅
虽然订阅通常属于来源应用的媒体提供商,但您可以通过在订阅中指定关联的服务包名称,将订阅归因于关联的服务包。
以下代码示例演示了如何创建用户订阅。
// Subscription for linked service package
val subscription = SubscriptionEntity
.Builder()
.setSubscriptionType(
SubscriptionType.SUBSCRIPTION_TYPE_ACTIVE
)
.setProviderPackageName("com.google.android.example")
// Optional
// December 30, 2025 12:00:00AM in milliseconds since epoch
.setExpirationTimeMillis(1767052800000)
.build();
此外,如果用户订阅了其他附属服务,请添加其他订阅,并相应地设置关联的服务软件包名称。
// Subscription for linked service package
val linkedSubscription = Subscription
.Builder()
.setSubscriptionType(
SubscriptionType.SUBSCRIPTION_TYPE_ACTIVE
)
.setProviderPackageName("linked service package name")
// Optional
// December 30, 2025 12:00:00AM in milliseconds since epoch
.setExpirationTimeMillis(1767052800000)
.addBundledSubscription(
BundledSubscription.Builder()
.setBundledSubscriptionProviderPackageName(
"bundled-subscription-package-name"
)
.setSubscriptionType(SubscriptionType.SUBSCRIPTION_TYPE_ACTIVE)
.setExpirationTimeMillis(111)
.addEntitlement(
SubscriptionEntitlement.Builder()
.setExpirationTimeMillis(111)
.setDisplayName("Silver subscription")
.setEntitlementId("subscription.tier.platinum")
.build()
)
.build()
)
.build();
(可选)为关联的服务订阅添加使用权。
提供订阅集
在应用位于前台时运行内容发布作业。
使用 AppEngagePublishClient
类中的 publishSubscriptionCluster()
方法发布 SubscriptionCluster
对象。
使用 isServiceAvailable
检查服务是否可供集成。
client.publishSubscription(
PublishSubscriptionRequest.Builder()
.setAccountProfile(accountProfile)
.setSubscription(subscription)
.build();
)
使用 setSubscription()
验证用户应仅对该服务拥有一个订阅。
使用 addLinkedSubscription()
或 addLinkedSubscriptions()
(接受关联订阅的列表),让用户可以拥有 0 个或更多关联订阅。
当服务收到请求时,系统会创建新条目,并在 60 天后自动删除旧条目。系统始终使用最新条目。如果发生错误,系统将拒绝整个请求,并保留现有状态。
及时更新订阅
- 如需在发生变化时立即提供更新,请在用户的订阅状态发生变化(例如激活、停用、升级、降级)时调用
publishSubscriptionCluster()
。 为了定期验证以确保持续准确,请至少每月调用一次
publishSubscriptionCluster()
。如需删除视频发现数据,请在标准 60 天保留期限之前使用
client.deleteClusters()
方法从 Google TV 服务器手动删除用户的数据。这会删除账号个人资料或整个账号的所有现有视频发现数据,具体取决于给定的DeleteReason
。用于移除用户订阅的代码段
// If the user logs out from your media app, you must make the following call // to remove subscription and other video discovery data from the current // google TV device. client.deleteClusters( new DeleteClustersRequest.Builder() .setAccountProfile( AccountProfile .Builder() .setAccountId() .setProfileId() .build() ) .setReason(DeleteReason.DELETE_REASON_USER_LOG_OUT) .build() ) ``` Following code snippet demonstrates removal of user subscription when user revokes the consent. ```Kotlin // If the user revokes the consent to share across device, make the call // to remove subscription and other video discovery data from all google // TV devices. client.deleteClusters( new DeleteClustersRequest.Builder() .setAccountProfile( AccountProfile .Builder() .setAccountId() .setProfileId() .build() ) .setReason(DeleteReason.DELETE_REASON_LOSS_OF_CONSENT) .build() ) ``` Following code demonstrates how to remove subscription data on user profile deletion. ```Kotlin // If the user delete a specific profile, you must make the following call // to remove subscription data and other video discovery data. client.deleteClusters( new DeleteClustersRequest.Builder() .setAccountProfile( AccountProfile .Builder() .setAccountId() .setProfileId() .build() ) .setReason(DeleteReason.DELETE_REASON_ACCOUNT_PROFILE_DELETION) .build() )
测试
本部分提供了有关测试订阅实现的分步指南。在发布前验证数据准确性和功能正常性。
发布集成核对清单
发布应在应用处于前台且用户正在积极与其互动时进行。
发布时间:
- 用户首次登录。
- 用户更改个人资料(如果支持个人资料)。
- 用户购买了新订阅。
- 用户升级订阅。
- 用户订阅到期。
检查应用是否在 logcat 中针对发布事件正确调用了
isServiceAvailable()
和publishClusters()
API。验证数据是否显示在验证应用中。验证应用应将订阅显示为单独的行。调用发布 API 后,数据应显示在验证应用中。
- 验证互动服务标志是否未在应用的 Android 清单文件中设置为正式版。
- 安装并打开 Engage Verification 应用。
- 如果验证应用中的
isServiceAvailable
值为false
,请点击验证应用中的Toggle
按钮将其设置为true
。 - 输入应用的软件包名称。系统会自动显示已发布的数据。
前往相应应用,然后执行以下各项操作:
- 登录。
- 在个人资料之间切换(如果支持)。
- 购买新的订阅。
- 升级现有订阅。
- 让订阅过期。
验证集成
如需测试集成,请使用验证应用。
验证应用是一款 Android 应用,开发者可以使用它来验证集成能否正常运行。此应用包含用于帮助开发者验证数据和广播 intent 的 capability。这有助于在发布前验证数据准确性和功能正常运行情况。
- 对于每种事件,检查应用是否已调用
publishSubscription
API。在验证应用中验证已发布的数据。验证验证应用中的所有内容均为绿色 如果实体的所有信息均正确无误,则所有实体中都会显示“一切顺利”绿色对勾标记。
图 1. 订阅成功 验证应用中也会突出显示问题
图 2.订阅失败 如需查看捆绑订阅中存在的问题,请使用电视遥控器将焦点移至该特定捆绑订阅,然后点击以查看问题。您可能需要先将焦点移至该行,然后向右移动才能找到“捆绑订阅”卡片。问题会以红色突出显示,如图 3 所示。此外,您还可以使用遥控器向下移动,查看捆绑订阅中的使用权问题
图 3.订阅错误 如需查看使用权中存在的问题,请使用电视遥控器将焦点移至该特定使用权,然后点击以查看问题。问题会以红色突出显示。
图 4.订阅错误详情