本指南包含相关说明,介绍了开发者如何使用 Engage SDK 与 Google TV 分享应用订阅和授权数据。用户可以找到自己有权访问的内容,并让 Google TV 直接在电视、移动设备和平板电脑上的 Google TV 体验中,向用户提供高度相关的内容推荐。
前提条件
您必须先完成媒体操作 Feed 的初始配置,然后才能使用设备授权 API。如果您尚未完成媒体操作 Feed 新手入门流程,请完成该流程。
准备工作
在开始之前,请完成以下步骤 验证您的应用是否以 API 级别 19 或更高级别为目标平台,以便进行此集成
将
com.google.android.engage
库添加到您的应用中:在集成过程中,您需要使用不同的 SDK:一个用于移动应用,另一个用于电视应用。
移动版
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>
适用于电视的 APK
<meta-data android:name="com.google.android.engage.service.ENV" android:value="PRODUCTION"> </meta-data>
在将 APK 发送给 Google 之前,请在 AndroidManifest.xml 文件中将互动服务环境设置为生产环境。 为获得最佳性能和未来兼容性,请仅在应用位于前台且用户正积极与其互动时(例如应用启动、登录后或使用期间)发布数据。不建议从后台进程发布。
在以下事件发生时发布订阅信息:
- 用户登录您的应用。
- 用户在个人资料之间切换(如果支持个人资料)。
- 用户购买新订阅。
- 用户升级现有订阅。
- 用户订阅到期。
集成
本部分提供了必要的代码示例和说明,以帮助您实现 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 订阅
如果应用提供多层级的付费订阅套餐(包含超出普通层级的扩展内容或功能),请通过向 Subscription 添加一项或多项授权来表示这一点。
相应授权包含以下字段:
- 标识符:相应授权所需的标识符字符串。此值必须与发布到 Google TV 的媒体提供商 Feed 中提供的某个使用权标识符(请注意,这不是 ID 字段)相匹配。
- 名称:此为辅助信息,用于进行授权匹配。 虽然是可选的,但提供能让人看懂的授权名称有助于开发者和支持团队更好地了解用户授权。 例如:Sling Orange。
- ExpirationTimeMillis:可以选择性地指定相应使用权的过期时间(以毫秒为单位),如果该时间与订阅过期时间不同,则必须指定。 默认情况下,订阅到期后,相应授权也会失效。
对于以下示例媒体提供商 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()
(接受关联订阅的列表),以允许用户拥有零个或多个关联订阅。
当服务收到请求时,系统会创建一个新条目,并在 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 Service Flag 是否未设置为生产环境。
- 安装并打开 Engage Verification 应用。
- 如果验证应用中的
isServiceAvailable
值为false
,请点击验证应用中的Toggle
按钮,将其设置为true
。 - 输入应用的软件包名称。系统会自动显示已发布的数据。
前往应用并执行以下各项操作:
- 登录。
- 在个人资料之间切换(如果支持)。
- 购买新订阅。
- 升级现有订阅。
- 使订阅过期。
验证集成
如需测试集成,请使用验证应用。
验证应用是一款 Android 应用,开发者可以使用它来验证集成能否正常运行。此应用包含用于帮助开发者验证数据和广播 intent 的 capability。这有助于在发布前验证数据准确性和功能是否正常。
- 针对每个事件,检查应用是否已调用
publishSubscription
API。在验证应用中验证已发布的数据。 验证应用中的所有内容是否均为绿色 如果所有实体的信息都正确,则所有实体都会显示“一切正常”绿色对勾标记。
图 1. 成功订阅 验证应用中也会突出显示问题
图 2.订阅失败 如需查看捆绑式订阅中的问题,请使用电视遥控器将焦点对准该特定捆绑式订阅,然后点击以查看问题。您可能需要先将焦点移至相应行,然后向右移动才能找到“捆绑式订阅”卡片。问题会突出显示为红色,如图 3 所示。此外,使用遥控器向下移动,查看套装订阅中授权存在的问题
图 3. 订阅错误 如需查看相应授权中的问题,请使用电视遥控器将焦点对准该特定授权,然后点击以查看问题。问题会以红色突出显示。
图 4. 订阅错误详情
下载
您必须先接受以下条款及条件才能下载。