使用 Engage SDK 与 Google TV 共享应用权益

本指南包含开发者使用 Engage SDK 与 Google TV 共享应用订阅和权益数据的说明。用户可以找到他们有权访问的内容,并让 Google TV 直接在电视、移动设备和平板电脑上的 Google TV 体验中向用户提供高度相关的内容推荐。

前提条件

在使用设备权益 API 之前,需要进行媒体操作 Feed 的引导。如果你尚未完成,请完成 媒体操作 Feed 引导流程。

准备工作

在开始之前,请完成以下步骤:验证你的应用是否针对 API 级别 19 或更高版本进行此集成。

  1. 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
      }
    
  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>
    
  3. 在将 APK 发送给 Google 之前,请在你的 AndroidManifest.xml 文件中将 Engage 服务环境设置为生产环境。为了获得最佳性能和未来的兼容性,仅当应用在前台且用户正在积极与其交互时(例如应用启动、登录后或在使用过程中)发布数据。不建议从后台进程发布。

  4. 在以下事件中发布订阅信息:

    1. 用户登录你的应用。
    2. 用户在配置文件之间切换(如果支持配置文件)。
    3. 用户购买新订阅。
    4. 用户升级现有订阅。
    5. 用户订阅过期。

集成

本部分提供实现 AccountProfileSubscriptionEntity 以管理各种订阅类型所需的代码示例和说明。

用户帐号和个人资料

为了在 Google TV 上启用个性化功能,请提供帐号信息。使用 AccountProfile 提供

  1. 帐号 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();

普通套餐订阅

对于拥有媒体提供商服务基本订阅的用户,例如,具有一个订阅套餐的服务,该套餐可授予对所有付费内容的访问权限,请提供以下基本详细信息:

  1. 订阅类型:清晰指示用户拥有的具体订阅计划。

    1. SUBSCRIPTION_TYPE_ACTIVE:用户拥有有效的付费订阅。
    2. SUBSCRIPTION_TYPE_ACTIVE_TRIAL:用户拥有试用订阅。
    3. SUBSCRIPTION_TYPE_INACTIVE:用户拥有帐号但没有有效的订阅或试用。
  2. 过期时间:可选的时间,以毫秒为单位。指定订阅何时过期。

  3. 提供商软件包名称:指定处理订阅的应用的软件包名称。

示例:对于示例媒体提供商 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();

高级订阅

如果应用提供多层高级订阅包,其中包括超出普通层的内容或功能,则可以通过向 Subscription 添加一个或多个权益来表示这一点。

此权益具有以下字段:

  1. 标识符:此权益所需的标识符字符串。此标识符必须与发布到 Google TV 的媒体提供商 Feed 中提供的权益标识符之一(请注意,这不是 ID 字段)匹配。
  2. 名称:这是辅助信息,用于权益匹配。虽然可选,但提供可读的权益名称可以增强开发者和支持团队对用户权益的理解。例如:Sling Orange。
  3. 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()(它们接受关联订阅列表),使用户可以拥有零个或多个关联订阅。

当服务收到请求时,会创建一个新条目,并且旧条目将在 60 天后自动删除。系统始终使用最新条目。如果发生错误,整个请求将被拒绝,并且现有状态将保持不变。

保持订阅最新

  1. 为了在更改时立即提供更新,每当用户的订阅状态发生变化时(例如激活、停用、升级、降级),请调用 publishSubscriptionCluster()
  2. 为了定期验证持续的准确性,每月至少调用一次 publishSubscriptionCluster()

  3. 要删除视频发现数据,在标准的 60 天保留期之前从 Google TV 服务器手动删除用户数据,请使用 client.deleteClusters() 方法。这将删除帐户配置文件或整个帐户的所有现有视频发现数据,具体取决于给定的 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()
    )
    

测试

本节提供了测试订阅实施的分步指南。在发布之前验证数据准确性和正确功能。

发布集成清单

  1. 发布应在应用位于前台且用户正在积极与其交互时进行。

  2. 在以下情况发布:

    • 用户首次登录。
    • 用户更改个人资料(如果支持个人资料)。
    • 用户购买新订阅。
    • 用户升级订阅。
    • 用户订阅过期。
  3. 检查应用是否在发布事件上正确调用了 logcat 中的 isServiceAvailable()publishClusters() API。

  4. 验证数据在验证应用中是否可见。验证应用应将订阅显示为单独的一行。调用发布 API 后,数据应显示在验证应用中。

    • 验证应用的 Android Manifest 文件中是否Engage 服务标志设置为生产环境。
    • 安装并打开 Engage 验证应用。
    • 如果验证应用中 isServiceAvailable 的值为 false,请点击验证应用中的 Toggle 按钮将其设置为 true
    • 输入应用的软件包名称。它会自动显示已发布的数据。
  5. 转到应用并执行以下每项操作:

    • 登录。
    • 在配置文件之间切换(如果支持)。
    • 购买新订阅。
    • 升级现有订阅。
    • 订阅过期。

验证集成

要测试你的集成,请使用验证应用

验证应用是一个 Android 应用程序,开发者可以使用它来验证集成是否正常工作。该应用包含可帮助开发者验证数据和广播 Intent 的功能。它有助于在发布前验证数据准确性和正确功能。

  1. 对于每个事件,检查应用是否已调用 publishSubscription API。在验证应用中验证已发布的数据。验证验证应用中的所有内容都显示为绿色。
  2. 如果所有实体的信息都正确,则在所有实体中都会显示一个“一切正常”的绿色勾选标记。

    Verification App Success Screenshot
    图 1. 订阅成功
  3. 验证应用中也会突出显示问题

    Verification App Error Screenshot
    图 2.订阅不成功
  4. 要查看捆绑订阅中的问题,请使用电视遥控器将焦点移到该特定捆绑订阅上,然后点击以查看问题。你可能需要先将焦点移到行上,然后向右移动才能找到捆绑订阅卡。问题将以红色突出显示,如图 3 所示。此外,使用遥控器向下移动以查看捆绑订阅中权益的问题

    Verification App Error Details Screenshot
    图 3.订阅错误
  5. 要查看权益中的问题,请使用电视遥控器将焦点移到该特定权益上,然后点击以查看问题。问题将以红色突出显示。

    Verification App Error Screenshot
    图 4.订阅错误详情