向您的应用添加客户端许可验证

警告:当您的应用在客户端执行许可验证过程时,潜在攻击者更容易修改或删除与此验证过程相关的逻辑。

因此,我们强烈建议您改用服务器端许可验证

设置发布者帐户和开发环境后(请参阅设置许可),您可以使用许可验证库 (LVL) 向您的应用添加许可验证。

使用 LVL 添加许可验证涉及以下任务

  1. 在应用的清单中添加许可权限
  2. 实施策略 - 您可以选择 LVL 中提供的完整实现之一,或创建自己的实现。
  3. 实施混淆器,如果您的Policy将缓存任何许可响应数据。
  4. 在应用的主要 Activity 中添加代码以检查许可
  5. 实施 DeviceLimiter(可选,不建议大多数应用使用)。

以下各部分介绍了这些任务。完成集成后,您应该能够成功编译您的应用,并可以开始测试,如设置测试环境中所述。

有关 LVL 中包含的完整源文件集的概述,请参阅LVL 类和接口的摘要

添加许可权限

要使用 Google Play 应用将许可检查发送到服务器,您的应用必须请求适当的权限com.android.vending.CHECK_LICENSE。如果您的应用没有声明许可权限,但试图启动许可检查,LVL 会抛出安全异常。

要在您的应用中请求许可权限,请将<uses-permission>元素声明为<manifest>的子元素,如下所示

<uses-permission android:name="com.android.vending.CHECK_LICENSE" />

例如,以下是 LVL 示例应用如何声明该权限

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...">
    <!-- Devices >= 3 have version of Google Play that supports licensing. -->
    <uses-sdk android:minSdkVersion="3" />
    <!-- Required permission to check licensing. -->
    <uses-permission android:name="com.android.vending.CHECK_LICENSE" />
    ...
</manifest>

注意:目前,您无法在 LVL 库项目的清单中声明CHECK_LICENSE权限,因为 SDK 工具不会将其合并到依赖应用的清单中。相反,您必须在每个依赖应用的清单中声明此权限。

实施策略

Google Play 许可服务本身不决定是否应该授予具有特定许可证的特定用户访问您的应用的权限。相反,此责任留给您在应用中提供的Policy实现。

Policy 是 LVL 声明的一个接口,旨在保存您的应用的逻辑,用于根据许可检查的结果允许或拒绝用户访问。要使用 LVL,您的应用必须提供Policy的实现。

The Policy 接口声明了两个方法,allowAccess()processServerResponse(),它们由 LicenseChecker 实例在处理来自许可证服务器的响应时调用。它还声明了一个名为 LicenseResponse 的枚举,它指定了在调用 processServerResponse() 时传递的许可证响应值。

  • processServerResponse() 允许您在确定是否授予访问权限之前预处理从许可证服务器接收的原始响应数据。

    一个典型的实现将从许可证响应中提取一些或所有字段并将数据存储到本地持久存储中,例如通过 SharedPreferences 存储,以确保数据在应用程序调用和设备电源循环之间可访问。例如,一个 Policy 将在持久存储中维护最后一次成功许可证检查的时间戳、重试次数、许可证有效期和类似信息,而不是在每次启动应用程序时重置这些值。

    在本地存储响应数据时,Policy 必须确保数据被混淆(请参见下面的 实现混淆器)。

  • allowAccess() 根据任何可用的许可证响应数据(来自许可证服务器或来自缓存)或其他特定于应用程序的信息,确定是否授予用户访问您的应用程序的权限。例如,您对 allowAccess() 的实现可以考虑其他标准,例如使用情况或从后端服务器检索到的其他数据。在所有情况下,allowAccess() 的实现仅在用户被许可使用该应用程序(由许可证服务器确定),或者存在阻止许可证检查完成的瞬时网络或系统问题时才应返回 true。在这种情况下,您的实现可以维护重试响应的计数并暂时允许访问,直到下次许可证检查完成。

为了简化在您的应用程序中添加许可的过程并说明如何设计 Policy,LVL 包含两个完整的 Policy 实现,您可以使用它们而无需修改或根据您的需要进行调整。

  • ServerManagedPolicy,一个灵活的 Policy,它使用服务器提供的设置和缓存的响应来管理各种网络条件下的访问权限,以及
  • StrictPolicy,它不缓存任何响应数据,并且仅在服务器返回已许可的响应时才允许访问。

对于大多数应用程序,强烈建议使用 ServerManagedPolicy。ServerManagedPolicy 是 LVL 的默认值,并与 LVL 示例应用程序集成。

自定义策略指南

在您的许可实现中,您可以使用 LVL 中提供的完整策略之一(ServerManagedPolicy 或 StrictPolicy),也可以创建自定义策略。对于任何类型的自定义策略,在您的实现中都有一些重要的设计要点需要理解和考虑。

许可证服务器应用一般请求限制以防止过度使用资源,这可能导致拒绝服务。当应用程序超过请求限制时,许可证服务器将返回 503 响应,该响应将作为一般服务器错误传递到您的应用程序。这意味着在限制重置之前,用户将无法获得任何许可证响应,这会对用户造成无限期影响。

如果您正在设计自定义策略,我们建议 Policy

  1. 缓存(并正确混淆)本地持久存储中的最新成功许可证响应。
  2. 只要缓存的响应有效,就返回所有许可证检查的缓存响应,而不是向许可证服务器发出请求。强烈建议根据服务器提供的 VT 附加信息设置响应有效期。有关更多信息,请参见 服务器响应附加信息
  3. 如果重试任何导致错误的请求,则使用指数级退避时间段。请注意,Google Play 客户端会自动重试失败的请求,因此在大多数情况下,您的 Policy 无需重试它们。
  4. 提供一个“宽限期”,允许用户在重试许可证检查时有限制地访问您的应用程序或使用一定次数。宽限期使用户能够在下次许可证检查成功完成之前进行访问,并通过在没有有效许可证响应可用时对访问您的应用程序设置硬性限制,使您受益。

根据上面列出的指南设计您的 Policy 非常重要,因为它可以确保为用户提供最佳体验,同时即使在错误情况下也能让您有效地控制您的应用程序。

请注意,任何 Policy 都可以使用许可证服务器提供的设置来帮助管理有效期和缓存、重试宽限期等。提取服务器提供的设置很简单,建议使用它们。有关如何提取和使用附加信息的示例,请参见 ServerManagedPolicy 实现。有关服务器设置的列表和有关如何使用它们的详细信息,请参见 服务器响应附加信息

ServerManagedPolicy

LVL 包含一个完整的、推荐的 Policy 接口实现,称为 ServerManagedPolicy。该实现与 LVL 类集成,并充当库中的默认 Policy

ServerManagedPolicy 提供了许可证和重试响应的所有处理。它将所有响应数据本地缓存到 SharedPreferences 文件中,并使用应用程序的 Obfuscator 实现对其进行混淆。这确保了许可证响应数据是安全的,并且在设备电源循环之间持续存在。ServerManagedPolicy 提供了接口方法 processServerResponse()allowAccess() 的具体实现,并且还包括一组用于管理许可证响应的支持方法和类型。

重要的是,ServerManagedPolicy 的一个关键特性是使用服务器提供的设置作为在应用程序退款期和各种网络和错误条件下管理许可的基础。当应用程序联系 Google Play 服务器进行许可证检查时,服务器会在某些许可证响应类型的附加信息字段中将几个设置附加为键值对。例如,服务器会为应用程序的许可证有效期、重试宽限期和最大允许重试次数等提供推荐值。ServerManagedPolicy 在其 processServerResponse() 方法中从许可证响应中提取这些值,并在其 allowAccess() 方法中检查它们。有关 ServerManagedPolicy 使用的服务器提供的设置的列表,请参见 服务器响应附加信息

为了方便、最佳性能以及使用 Google Play 服务器的许可证设置的优势,强烈建议使用 ServerManagedPolicy 作为您的许可证 Policy

如果您担心本地存储在 SharedPreferences 中的许可证响应数据的安全性,您可以使用更强大的混淆算法或设计更严格的 Policy,该策略不存储许可证数据。LVL 包含此类 Policy 的示例 - 有关更多信息,请参见 StrictPolicy

要使用 ServerManagedPolicy,只需将其导入您的 Activity、创建实例,并在构造您的 LicenseChecker 时传递对该实例的引用即可。有关更多信息,请参见 实例化 LicenseChecker 和 LicenseCheckerCallback

StrictPolicy

LVL 包含一个完整的替代 Policy 接口实现,称为 StrictPolicy。StrictPolicy 实现提供了比 ServerManagedPolicy 更严格的策略,因为它不允许用户访问应用程序,除非在访问时从服务器收到一个指示用户已获得许可的许可证响应。

StrictPolicy 的主要特性是不将任何许可证响应数据存储在本地持久存储中。由于没有存储数据,因此不会跟踪重试请求,并且无法使用缓存的响应来满足许可证检查。该 Policy 仅在以下情况下才允许访问:

  • 从许可证服务器接收许可证响应,并且
  • 许可证响应表明用户被许可访问应用程序。

如果您的主要关注点是确保在所有可能的情况下,不允许任何用户访问应用程序,除非在使用时确认用户已获得许可,则使用 StrictPolicy 是合适的。此外,该策略比 ServerManagedPolicy 提供了稍微更高的安全性 - 由于没有数据缓存在本地,因此恶意用户无法篡改缓存的数据并获得对应用程序的访问权限。

同时,此 Policy 对普通用户提出了挑战,因为它意味着在没有网络(蜂窝或 Wi-Fi)连接的情况下,他们将无法访问应用程序。另一个副作用是,您的应用程序将向服务器发送更多许可证检查请求,因为无法使用缓存的响应。

总的来说,此策略代表了某种程度上用户便利性和绝对安全性和访问控制之间的权衡。在使用此 Policy 之前,请仔细考虑这种权衡。

要使用 StrictPolicy,只需将其导入您的 Activity、创建实例,并在构造您的 LicenseChecker 时传递对它的引用即可。有关更多信息,请参见 实例化 LicenseChecker 和 LicenseCheckerCallback

一个典型的 Policy 实现需要将应用程序的许可证响应数据保存到持久存储中,以便在应用程序调用和设备电源循环之间可访问。例如,一个 Policy 将在持久存储中维护最后一次成功许可证检查的时间戳、重试次数、许可证有效期和类似信息,而不是在每次启动应用程序时重置这些值。LVL 中包含的默认 Policy,ServerManagedPolicy,将许可证响应数据存储在一个 SharedPreferences 实例中,以确保数据持久化。

由于 Policy 将使用存储的许可证响应数据来确定是否允许或拒绝访问应用程序,因此它必须确保任何存储的数据都是安全的,并且不能被设备上的根用户重复使用或操作。具体来说,Policy 必须始终在存储之前对数据进行混淆,使用一个对应用程序和设备唯一的密钥。使用对应用程序和设备都唯一的密钥进行混淆至关重要,因为它可以防止混淆后的数据在应用程序和设备之间共享。

LVL 帮助应用程序以安全且持久的方式存储其许可证响应数据。首先,它提供一个 Obfuscator 接口,允许您的应用程序为存储的数据提供其选择的混淆算法。在此基础上,LVL 提供了辅助类 PreferenceObfuscator,它处理了调用应用程序的 Obfuscator 类以及在 SharedPreferences 实例中读写混淆数据的绝大部分工作。

LVL 提供了一个名为 AESObfuscator 的完整的 Obfuscator 实现,它使用 AES 加密来混淆数据。您可以在应用程序中使用 AESObfuscator,无需修改,也可以根据您的需要进行调整。如果您使用的是缓存许可证响应数据的 Policy(如 ServerManagedPolicy),强烈建议使用 AESObfuscator 作为您 Obfuscator 实现的基础。有关更多信息,请参见下一节。

AESObfuscator

LVL 包含 Obfuscator 接口的完整推荐实现,名为 AESObfuscator。该实现与 LVL 示例应用程序集成,并作为库中的默认 Obfuscator

AESObfuscator 通过使用 AES 加密和解密在写入或读取存储时的数据来提供安全的数据混淆。 Obfuscator 使用应用程序提供的三个数据字段对加密进行种子。

  1. 盐 - 用于每次(取消)混淆的随机字节数组。
  2. 应用程序标识符字符串,通常是应用程序的包名。
  3. 设备标识符字符串,从尽可能多的设备特定来源派生而来,使其尽可能唯一。

要使用 AESObfuscator,首先将其导入您的 Activity。声明一个私有的静态最终数组来保存盐字节,并将其初始化为 20 个随机生成的字节。

Kotlin

// Generate 20 random bytes, and put them here.
private val SALT = byteArrayOf(
        -46, 65, 30, -128, -103, -57, 74, -64, 51, 88,
        -95, -45, 77, -117, -36, -113, -11, 32, -64, 89
)

Java

...
    // Generate 20 random bytes, and put them here.
    private static final byte[] SALT = new byte[] {
     -46, 65, 30, -128, -103, -57, 74, -64, 51, 88, -95,
     -45, 77, -117, -36, -113, -11, 32, -64, 89
     };
    ...

接下来,声明一个变量来保存设备标识符,并以任何所需的方式为其生成值。例如,LVL 中包含的示例应用程序查询系统设置以获取 android.Settings.Secure.ANDROID_ID,该标识符对于每个设备都是唯一的。

请注意,根据您使用的 API,您的应用程序可能需要请求其他权限才能获取设备特定信息。例如,要查询 TelephonyManager 以获取设备 IMEI 或相关数据,应用程序还需要在其清单中请求 android.permission.READ_PHONE_STATE 权限。

在为唯一目的获取设备特定信息以用于您的 Obfuscator 请求新权限之前,请考虑这样做可能会如何影响您的应用程序或其在 Google Play 上的过滤(因为某些权限会导致 SDK 构建工具添加关联的 <uses-feature>)。

最后,构造一个 AESObfuscator 实例,传递盐、应用程序标识符和设备标识符。您可以在构造 PolicyLicenseChecker 时直接构造该实例。例如

Kotlin

    ...
    // Construct the LicenseChecker with a Policy.
    private val checker = LicenseChecker(
            this,
            ServerManagedPolicy(this, AESObfuscator(SALT, packageName, deviceId)),
            BASE64_PUBLIC_KEY
    )
    ...

Java

    ...
    // Construct the LicenseChecker with a Policy.
    checker = new LicenseChecker(
        this, new ServerManagedPolicy(this,
            new AESObfuscator(SALT, getPackageName(), deviceId)),
        BASE64_PUBLIC_KEY // Your public licensing key.
        );
    ...

有关完整示例,请参见 LVL 示例应用程序中的 MainActivity。

从 Activity 检查许可证

在为管理对应用程序访问权限实现 Policy 后,下一步是在应用程序中添加许可证检查,该检查将在需要时启动对许可证服务器的查询,并根据许可证响应管理对应用程序的访问权限。所有添加许可证检查和处理响应的工作都在您的主 Activity 源文件内进行。

要添加许可证检查并处理响应,您必须

  1. 添加导入
  2. 实现 LicenseCheckerCallback 作为私有的内部类
  3. 创建一个 Handler 用于将 LicenseCheckerCallback 发布到 UI 线程
  4. 实例化 LicenseChecker 和 LicenseCheckerCallback
  5. 调用 checkAccess() 以启动许可证检查
  6. 嵌入您的公共密钥 用于许可
  7. 调用您的 LicenseChecker 的 onDestroy() 方法 以关闭 IPC 连接。

以下各节将描述这些任务。

许可证检查和响应概述

在大多数情况下,您应该将许可证检查添加到应用程序的主 Activity 中,在 onCreate() 方法内。这样可以确保当用户直接启动您的应用程序时,许可证检查会立即被调用。在某些情况下,您也可以在其他位置添加许可证检查。例如,如果您的应用程序包含多个其他应用程序可以通过 Intent 启动的 Activity 组件,您可以在这些 Activity 中添加许可证检查。

许可证检查包含两个主要操作

  • 调用方法以启动许可证检查 - 在 LVL 中,这是对您构造的 LicenseChecker 对象的 checkAccess() 方法的调用。
  • 一个回调,它返回许可证检查的结果。在 LVL 中,这是一个您实现的 LicenseCheckerCallback 接口。该接口声明了两个方法,allow()dontAllow(),它们根据许可证检查的结果由库调用。您使用所需逻辑实现这两个方法,以允许或拒绝用户访问您的应用程序。请注意,这些方法不决定是否允许访问 - 该决定由您的 Policy 实现负责。相反,这些方法只是为如何允许和拒绝访问(以及处理应用程序错误)提供应用程序行为。

    allow()dontAllow() 方法确实提供了对其响应的“原因”,这可能是以下 Policy 值之一: LICENSEDNOT_LICENSEDRETRY。特别是,您应该处理 dontAllow() 方法收到 RETRY 响应的情况,并为用户提供一个“重试”按钮,这可能是因为在请求期间服务不可用。

图 1. 典型许可证检查交互概述。

上图说明了典型的许可证检查是如何进行的

  1. 应用程序主 Activity 中的代码实例化 LicenseCheckerCallbackLicenseChecker 对象。在构造 LicenseChecker 时,代码将 Context、要使用的 Policy 实现以及发布者帐户的公共密钥作为许可参数传递。
  2. 然后,代码调用 LicenseChecker 对象上的 checkAccess() 方法。方法实现调用 Policy 来确定本地是否有有效的许可证响应缓存,在 SharedPreferences 中。
    • 如果是,则 checkAccess() 实现将调用 allow()
    • 否则,LicenseChecker 将启动一个发送到许可证服务器的许可证检查请求。

    注意:当您对草稿应用程序执行许可证检查时,许可证服务器始终返回 LICENSED

  3. 当收到响应时,LicenseChecker 将创建一个 LicenseValidator,它将验证签名的许可证数据并提取响应的字段,然后将它们传递给您的 Policy 以供进一步评估。
    • 如果许可证有效,则 Policy 将响应缓存到 SharedPreferences 中,并通知验证程序,然后验证程序将 LicenseCheckerCallback 对象上的 allow() 方法调用。
    • 如果许可证无效,则 Policy 将通知验证程序,验证程序将 LicenseCheckerCallback 上的 dontAllow() 方法调用。
  4. 如果发生可恢复的本地或服务器错误(例如当网络不可用以发送请求时),LicenseCheckerRETRY 响应传递给您的 Policy 对象的 processServerResponse() 方法。

    此外,allow()dontAllow() 回调方法都接收 reason 参数。 allow() 方法的原因通常是 Policy.LICENSEDPolicy.RETRY,而 dontAllow() 的原因通常是 Policy.NOT_LICENSEDPolicy.RETRY。这些响应值很有用,因此您可以为用户显示适当的响应,例如,当 dontAllow() 返回 Policy.RETRY 时提供“重试”按钮,这可能是因为服务不可用。

  5. 如果发生应用程序错误(例如,当应用程序尝试检查无效包名的许可证时),LicenseChecker 将错误响应传递给 LicenseCheckerCallback 的 applicationError() 方法。

请注意,除了启动许可证检查和处理结果(在以下各节中进行了描述)之外,您的应用程序还需要提供 Policy 实现,如果 Policy 存储响应数据(例如 ServerManagedPolicy),则还需要提供 Obfuscator 实现。

添加导入

首先,打开应用程序主 Activity 的类文件,并从 LVL 包中导入 LicenseCheckerLicenseCheckerCallback

Kotlin

import com.google.android.vending.licensing.LicenseChecker
import com.google.android.vending.licensing.LicenseCheckerCallback

Java

import com.google.android.vending.licensing.LicenseChecker;
import com.google.android.vending.licensing.LicenseCheckerCallback;

如果您使用的是 LVL 提供的默认 Policy 实现,即 ServerManagedPolicy,请也将其与 AESObfuscator 一起导入。如果您使用的是自定义 PolicyObfuscator,请改为导入它们。

Kotlin

import com.google.android.vending.licensing.ServerManagedPolicy
import com.google.android.vending.licensing.AESObfuscator

Java

import com.google.android.vending.licensing.ServerManagedPolicy;
import com.google.android.vending.licensing.AESObfuscator;

将 LicenseCheckerCallback 实现为私有的内部类

LicenseCheckerCallback 是 LVL 提供的用于处理许可证检查结果的接口。为了支持使用 LVL 进行许可,您必须实现 LicenseCheckerCallback 及其方法以允许或拒绝访问应用程序。

许可证检查的结果始终是调用其中一个 LicenseCheckerCallback 方法,该方法基于对响应有效负载的验证、服务器响应代码本身以及您的 Policy 提供的任何其他处理来进行。您的应用程序可以以任何需要的方式实现这些方法。通常,最好使方法保持简单,将其限制为管理 UI 状态和应用程序访问。如果您想进一步处理许可证响应,例如通过联系后端服务器或应用自定义约束,您应该考虑将该代码合并到您的 Policy 中,而不是将其放在 LicenseCheckerCallback 方法中。

在大多数情况下,您应该在应用程序的主 Activity 类中将 LicenseCheckerCallback 的实现声明为私有类。

根据需要实现 allow()dontAllow() 方法。首先,您可以在方法中使用简单的结果处理行为,例如在对话框中显示许可证结果。这有助于您更快地运行应用程序,并有助于调试。稍后,在确定所需的确切行为后,您可以添加更复杂的处理。

以下是一些在 dontAllow() 中处理未经许可响应的建议

  • 向用户显示“重试”对话框,包括一个按钮,如果提供的 reasonPolicy.RETRY,则启动新的许可证检查。
  • 显示一个“购买此应用程序”对话框,包括一个按钮,该按钮将用户深度链接到 Google Play 上的应用程序详细信息页面,用户可以在该页面上购买应用程序。有关如何设置此类链接的更多信息,请参阅 链接到您的产品
  • 显示一个 Toast 通知,指示应用程序的功能有限,因为它没有获得许可。

以下示例显示了 LVL 示例应用程序如何实现 LicenseCheckerCallback,其中包含在对话框中显示许可证检查结果的方法。

Kotlin

private inner class MyLicenseCheckerCallback : LicenseCheckerCallback {

    override fun allow(reason: Int) {
        if (isFinishing) {
            // Don't update UI if Activity is finishing.
            return
        }
        // Should allow user access.
        displayResult(getString(R.string.allow))
    }

    override fun dontAllow(reason: Int) {
        if (isFinishing) {
            // Don't update UI if Activity is finishing.
            return
        }
        displayResult(getString(R.string.dont_allow))

        if (reason == Policy.RETRY) {
            // If the reason received from the policy is RETRY, it was probably
            // due to a loss of connection with the service, so we should give the
            // user a chance to retry. So show a dialog to retry.
            showDialog(DIALOG_RETRY)
        } else {
            // Otherwise, the user isn't licensed to use this app.
            // Your response should always inform the user that the application
            // isn't licensed, but your behavior at that point can vary. You might
            // provide the user a limited access version of your app or you can
            // take them to Google Play to purchase the app.
            showDialog(DIALOG_GOTOMARKET)
        }
    }
}

Java

private class MyLicenseCheckerCallback implements LicenseCheckerCallback {
    public void allow(int reason) {
        if (isFinishing()) {
            // Don't update UI if Activity is finishing.
            return;
        }
        // Should allow user access.
        displayResult(getString(R.string.allow));
    }

    public void dontAllow(int reason) {
        if (isFinishing()) {
            // Don't update UI if Activity is finishing.
            return;
        }
        displayResult(getString(R.string.dont_allow));

        if (reason == Policy.RETRY) {
            // If the reason received from the policy is RETRY, it was probably
            // due to a loss of connection with the service, so we should give the
            // user a chance to retry. So show a dialog to retry.
            showDialog(DIALOG_RETRY);
        } else {
            // Otherwise, the user isn't licensed to use this app.
            // Your response should always inform the user that the application
            // isn't licensed, but your behavior at that point can vary. You might
            // provide the user a limited access version of your app or you can
            // take them to Google Play to purchase the app.
            showDialog(DIALOG_GOTOMARKET);
        }
    }
}

此外,您应该实现 applicationError() 方法,LVL 使用该方法让您的应用程序处理不可重试的错误。有关此类错误的列表,请参阅 许可参考 中的 服务器响应代码。您可以以任何需要的方式实现该方法。在大多数情况下,该方法应该记录错误代码并调用 dontAllow()

创建一个处理程序,用于将来自 LicenseCheckerCallback 的内容发布到 UI 线程

在许可证检查期间,LVL 将请求传递给 Google Play 应用程序,该应用程序负责处理与许可证服务器的通信。LVL 通过异步 IPC(使用 Binder)传递请求,因此实际的处理和网络通信不会发生在您的应用程序管理的线程上。类似地,当 Google Play 应用程序收到结果时,它会通过 IPC 调用回调方法,该方法反过来会在应用程序进程的 IPC 线程池中执行。

LicenseChecker 类管理应用程序与 Google Play 应用程序的 IPC 通信,包括发送请求的调用和接收响应的回调。 LicenseChecker 还跟踪打开的许可证请求并管理其超时。

为了能够正确处理超时,并且还能够处理传入的响应而不影响应用程序的 UI 线程, LicenseChecker 在实例化时会生成一个后台线程。在该线程中,它执行所有许可证检查结果的处理,无论结果是来自服务器的响应还是超时错误。在处理结束时,LVL 会从后台线程调用您的 LicenseCheckerCallback 方法。

对于您的应用程序,这意味着

  1. 在许多情况下,您的 LicenseCheckerCallback 方法将从后台线程调用。
  2. 这些方法将无法更新状态或在 UI 线程中调用任何处理,除非您在 UI 线程中创建一个处理程序,并让您的回调方法发布到处理程序。

如果您希望 LicenseCheckerCallback 方法更新 UI 线程,请在主 Activity 的 onCreate() 方法中实例化一个 Handler,如下所示。在此示例中,LVL 示例应用程序的 LicenseCheckerCallback 方法(见上文)调用 displayResult() 以通过处理程序的 post() 方法更新 UI 线程。

Kotlin

    private lateinit var handler: Handler

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        handler = Handler()
    }

Java

    private Handler handler;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        handler = new Handler();
    }

然后,在您的 LicenseCheckerCallback 方法中,您可以使用处理程序方法将 Runnable 或 Message 对象发布到处理程序。以下是 LVL 中包含的示例应用程序如何将 Runnable 发布到 UI 线程中的处理程序以显示许可证状态。

Kotlin

private fun displayResult(result: String) {
    handler.post {
        statusText.text = result
        setProgressBarIndeterminateVisibility(false)
        checkLicenseButton.isEnabled = true
    }
}

Java

private void displayResult(final String result) {
        handler.post(new Runnable() {
            public void run() {
                statusText.setText(result);
                setProgressBarIndeterminateVisibility(false);
                checkLicenseButton.setEnabled(true);
            }
        });
    }

实例化 LicenseChecker 和 LicenseCheckerCallback

在主 Activity 的 onCreate() 方法中,创建 LicenseCheckerCallback 和 LicenseChecker 的私有实例。您必须先实例化 LicenseCheckerCallback,因为您需要在调用 LicenseChecker 的构造函数时传递对该实例的引用。

实例化 LicenseChecker 时,您需要传入以下参数

  • 应用程序 Context
  • 对用于许可证检查的 Policy 实现的引用。在大多数情况下,您将使用 LVL 提供的默认 Policy 实现,即 ServerManagedPolicy。
  • 保存发布者帐户的许可证公共密钥的 String 变量。

如果您使用的是 ServerManagedPolicy,则无需直接访问该类,因此您可以在 LicenseChecker 构造函数中实例化它,如下面的示例所示。请注意,在构造 ServerManagedPolicy 时,您需要传递对新的 Obfuscator 实例的引用。

以下示例显示了从 Activity 类的 onCreate() 方法实例化 LicenseCheckerLicenseCheckerCallback

Kotlin

class MainActivity : AppCompatActivity() {
    ...
    private lateinit var licenseCheckerCallback: LicenseCheckerCallback
    private lateinit var checker: LicenseChecker

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Construct the LicenseCheckerCallback. The library calls this when done.
        licenseCheckerCallback = MyLicenseCheckerCallback()

        // Construct the LicenseChecker with a Policy.
        checker = LicenseChecker(
                this,
                ServerManagedPolicy(this, AESObfuscator(SALT, packageName, deviceId)),
                BASE64_PUBLIC_KEY // Your public licensing key.
        )
        ...
    }
}

Java

public class MainActivity extends Activity {
    ...
    private LicenseCheckerCallback licenseCheckerCallback;
    private LicenseChecker checker;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Construct the LicenseCheckerCallback. The library calls this when done.
        licenseCheckerCallback = new MyLicenseCheckerCallback();

        // Construct the LicenseChecker with a Policy.
        checker = new LicenseChecker(
            this, new ServerManagedPolicy(this,
                new AESObfuscator(SALT, getPackageName(), deviceId)),
            BASE64_PUBLIC_KEY // Your public licensing key.
            );
        ...
    }
}

请注意,LicenseChecker 在本地缓存了有效的许可证响应时,才会从 UI 线程调用 LicenseCheckerCallback 方法。如果许可证检查被发送到服务器,则回调始终来自后台线程,即使对于网络错误也是如此。

调用 checkAccess() 以启动许可证检查

在您的主 Activity 中,添加对 LicenseChecker 实例的 checkAccess() 方法的调用。在调用中,将对您的 LicenseCheckerCallback 实例的引用作为参数传递。如果您需要在调用之前处理任何特殊的 UI 效果或状态管理,您可能会发现从包装方法中调用 checkAccess() 很有用。例如,LVL 示例应用程序从 doCheck() 包装方法中调用 checkAccess()

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Call a wrapper method that initiates the license check
        doCheck()
        ...
    }
    ...
    private fun doCheck() {
        checkLicenseButton.isEnabled = false
        setProgressBarIndeterminateVisibility(true)
        statusText.setText(R.string.checking_license)
        checker.checkAccess(licenseCheckerCallback)
    }

Java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Call a wrapper method that initiates the license check
        doCheck();
        ...
    }
    ...
    private void doCheck() {
        checkLicenseButton.setEnabled(false);
        setProgressBarIndeterminateVisibility(true);
        statusText.setText(R.string.checking_license);
        checker.checkAccess(licenseCheckerCallback);
    }

嵌入您的许可证公共密钥

对于每个应用程序,Google Play 服务会自动生成一个 2048 位 RSA 公钥/私钥对,用于许可证和应用内结算。密钥对与应用程序唯一关联。虽然与应用程序相关联,但密钥对不是与您用于签名应用程序的密钥相同(或从中派生)。

Google Play Console 将用于许可证的公共密钥公开给登录 Play Console 的任何开发人员,但它将私钥安全地隐藏在所有用户无法访问的位置。当应用程序请求针对您帐户中发布的应用程序的许可证检查时,许可证服务器会使用应用程序密钥对的私钥签署许可证响应。当 LVL 收到响应时,它会使用应用程序提供的公共密钥来验证许可证响应的签名。

要将许可证添加到应用程序,您必须获取应用程序的许可证公共密钥并将其复制到应用程序中。以下是查找应用程序的许可证公共密钥的方法

  1. 转到 Google Play Console 并登录。请确保您登录到发布(或将要发布)您正在许可的应用程序的帐户。
  2. 在应用程序详细信息页面中,找到“服务和 API”链接并单击它。
  3. 在“服务和 API”页面中,找到“许可证和应用内结算”部分。您的许可证公共密钥在“此应用程序的许可证密钥”字段中给出。

要将公共密钥添加到应用程序,只需将字段中的密钥字符串复制/粘贴到应用程序中,作为 String 变量 BASE64_PUBLIC_KEY 的值。复制时,请确保已选择整个密钥字符串,而不要省略任何字符。

以下是来自 LVL 示例应用程序的示例

Kotlin

private const val BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... " //truncated for this example
class LicensingActivity : AppCompatActivity() {
    ...
}

Java

public class MainActivity extends Activity {
    private static final String BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... "; //truncated for this example
    ...
}

调用 LicenseChecker 的 onDestroy() 方法以关闭 IPC 连接

最后,为了让 LVL 在应用程序 Context 更改之前进行清理,请从 Activity 的 onDestroy() 实现中添加对 LicenseCheckeronDestroy() 方法的调用。该调用会导致 LicenseChecker 正确关闭与 Google Play 应用程序的 ILicensingService 的任何打开的 IPC 连接,并删除对该服务和处理程序的任何本地引用。

未能调用 LicenseCheckeronDestroy() 方法会导致应用程序生命周期出现问题。例如,如果用户在许可证检查处于活动状态时更改屏幕方向,应用程序 Context 将被销毁。如果您的应用程序没有正确关闭 LicenseChecker 的 IPC 连接,则应用程序会在收到响应时崩溃。类似地,如果用户在许可证检查正在进行时退出应用程序,则应用程序会在收到响应时崩溃,除非它已正确调用 LicenseCheckeronDestroy() 方法来断开与服务的连接。

以下是来自 LVL 中包含的示例应用程序的示例,其中 mCheckerLicenseChecker 实例

Kotlin

    override fun onDestroy() {
        super.onDestroy()
        checker.onDestroy()
        ...
    }

Java

    @Override
    protected void onDestroy() {
        super.onDestroy();
        checker.onDestroy();
        ...
    }

如果您正在扩展或修改 LicenseChecker,您可能还需要调用 LicenseCheckerfinishCheck() 方法,以清理任何打开的 IPC 连接。

实现 DeviceLimiter

在某些情况下,您可能希望您的 Policy 限制可以使用单个许可证的实际设备数量。这将阻止用户将已授权的应用程序移动到多个设备上,并在同一帐户 ID 下在这些设备上使用该应用程序。它还会阻止用户通过向其他个人提供与许可证关联的帐户信息来“共享”应用程序,然后这些个人可以在其设备上登录该帐户并访问该应用程序的许可证。

LVL 通过提供 DeviceLimiter 接口来支持每个设备的许可,该接口声明了一种方法,allowDeviceAccess()。当 LicenseValidator 处理来自许可服务器的响应时,它会调用 allowDeviceAccess(),并传递从响应中提取的用户 ID 字符串。

如果您不想支持设备限制,无需任何操作LicenseChecker 类会自动使用称为 NullDeviceLimiter 的默认实现。顾名思义,NullDeviceLimiter 是一个“无操作”类,其 allowDeviceAccess() 方法只对所有用户和设备返回 LICENSED 响应。

注意:每个设备的许可不建议大多数应用程序使用,因为

  • 它要求您提供一个后端服务器来管理用户和设备映射,并且
  • 它可能会意外导致用户被拒绝访问他们在其他设备上合法购买的应用程序。

混淆您的代码

为了确保您的应用程序的安全性,特别是对于使用许可证和/或自定义约束和保护的付费应用程序,混淆您的应用程序代码非常重要。正确混淆您的代码使恶意用户更难反编译应用程序的字节码,对其进行修改(例如删除许可证检查),然后重新编译它。

Android 应用程序提供了一些混淆器程序,包括 ProGuard,它还提供代码优化功能。强烈建议所有使用 Google Play 许可证的应用程序使用 ProGuard 或类似程序来混淆您的代码。

发布已授权的应用程序

完成许可证实现的测试后,您就可以在 Google Play 上发布应用程序了。按照正常步骤进行 准备签名,然后 发布应用程序

在哪里获取支持

如果您在应用程序中实施或部署发布时遇到问题或有疑问,请使用下表中列出的支持资源。通过将您的查询发送到正确的论坛,您可以更快速地获得所需的帮助。

表 2. Google Play 许可证服务的开发者支持资源。

支持类型 资源 主题范围
开发和测试问题 Google 论坛:android-developers LVL 下载和集成、库项目、Policy 问题、用户体验想法、处理响应、Obfuscator、IPC、测试环境设置
Stack Overflow:http://stackoverflow.com/questions/tagged/android
帐户、发布和部署问题 Google Play 帮助论坛 发布者帐户、许可证密钥对、测试帐户、服务器响应、测试响应、应用程序部署和结果
市场许可支持常见问题解答
Marketlicensing 项目问题跟踪器 专门针对 LVL 源代码类和接口实现的错误和问题报告 有关如何发布到上述论坛的常规信息,请参阅开发者支持资源页面上的 社区资源 部分。

其他资源

LVL 中包含的示例应用程序提供了如何在 MainActivity 类中启动许可证检查和处理结果的完整示例。

本页面上的内容和代码示例受 内容许可 中描述的许可证约束。