警告:当您的应用在客户端执行许可验证过程时,潜在攻击者更容易修改或移除与此验证过程相关的逻辑。
因此,我们强烈建议您改用服务器端许可验证。
在您设置了发布商帐号和开发环境(请参阅设置许可)后,您就可以使用许可验证库 (LVL) 向您的应用添加许可验证了。
使用 LVL 添加许可验证涉及以下任务:
- 将许可权限添加到您应用的清单文件。
- 实现 Policy — 您可以选择 LVL 中提供的完整实现之一,也可以创建自己的实现。
- 如果您的
Policy
将缓存任何许可响应数据,则实现 Obfuscator。 - 在您应用的主 Activity 中添加代码以检查许可。
- 实现 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 工具不会将其合并到依赖应用的清单文件中。相反,您必须在每个依赖应用的清单文件中声明该权限。
实现 Policy
Google Play 许可服务本身不决定是否应向拥有给定许可的给定用户授予对您应用的访问权限。相反,此责任留给了您在应用中提供的 Policy
实现。
Policy
是 LVL 声明的一个接口,旨在根据许可检查结果来保存应用允许或禁止用户访问的逻辑。要使用 LVL,您的应用必须提供 Policy
的实现。
Policy
接口声明了两个方法:allowAccess()
和 processServerResponse()
,它们由 LicenseChecker
实例在处理来自许可服务器的响应时调用。它还声明了一个名为 LicenseResponse
的枚举,该枚举指定了在调用 processServerResponse()
时传入的许可响应值。
processServerResponse()
允许您在确定是否授予访问权限之前,预处理从许可服务器接收到的原始响应数据。典型的实现会从许可响应中提取部分或所有字段,并将数据本地存储到持久性存储中,例如通过
SharedPreferences
存储,以确保数据在应用调用和设备电源循环之间均可访问。例如,Policy
会在持久性存储中维护上次成功许可检查的时间戳、重试计数、许可有效期和类似信息,而不是每次启动应用时都重置这些值。在本地存储响应数据时,
Policy
必须确保数据已混淆(请参阅下文的实现 Obfuscator)。allowAccess()
根据任何可用的许可响应数据(来自许可服务器或缓存)或其他应用特定信息,确定是否授予用户对您应用的访问权限。例如,您的allowAccess()
实现可以考虑其他条件,例如使用情况或从后端服务器检索的其他数据。在所有情况下,allowAccess()
的实现应仅在用户获得许可使用应用(由许可服务器确定)或存在阻止许可检查完成的暂时性网络或系统问题时才返回true
。在这种情况下,您的实现可以维护重试响应计数,并临时允许访问,直到下一次许可检查完成。
为了简化向您的应用添加许可的过程并提供 Policy 应如何设计的示例,LVL 包含两种完整的 Policy
实现,您可以不经修改直接使用,或根据您的需求进行调整:
- ServerManagedPolicy,一个灵活的
Policy
,它使用服务器提供的设置和缓存响应来管理各种网络条件下的访问,以及 - StrictPolicy,它不缓存任何响应数据,并且仅当服务器返回许可响应时才允许访问。
对于大多数应用,强烈建议使用 ServerManagedPolicy。ServerManagedPolicy 是 LVL 的默认设置,并已与 LVL 示例应用集成。
自定义 Policy 的指导原则
在您的许可实现中,您可以使用 LVL 中提供的完整策略之一(ServerManagedPolicy 或 StrictPolicy),也可以创建自定义策略。对于任何类型的自定义策略,您的实现中都有几个重要的设计点需要理解和考虑。
许可服务器会应用一般请求限制,以防止资源过度使用可能导致服务拒绝。当应用超出请求限制时,许可服务器会返回 503 响应,该响应会作为一般服务器错误传递到您的应用。这意味着在限制重置之前,用户将无法获得许可响应,这可能会无限期地影响用户。
如果您正在设计自定义 Policy,我们建议该 Policy
- 将最近一次成功的许可响应缓存(并正确混淆)在本地持久性存储中。
- 在缓存响应有效的情况下,对所有许可检查都返回缓存响应,而不是向许可服务器发出请求。强烈建议根据服务器提供的
VT
额外参数设置响应有效期。如需了解更多信息,请参阅服务器响应额外参数。 - 如果重试任何导致错误的请求,则使用指数退避期。请注意,Google Play 客户端会自动重试失败的请求,因此在大多数情况下,您的
Policy
无需重试它们。 - 提供“宽限期”,允许用户在许可检查重试期间,在有限的时间或使用次数内访问您的应用。宽限期通过允许访问直到下一次许可检查成功完成而使用户受益,并通过在没有有效许可响应可用时对您的应用访问施加硬性限制而使您受益。
根据上述指南设计您的 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 包含一个名为 StrictPolicy 的 Policy
接口的替代完整实现。StrictPolicy 实现提供了一个比 ServerManagedPolicy 更严格的 Policy,因为它不允许用户访问应用,除非在访问时从服务器收到指示用户已获得许可的许可响应。
StrictPolicy 的主要特点是它不在本地持久性存储中存储任何许可响应数据。由于不存储数据,重试请求不会被跟踪,并且缓存的响应不能用于完成许可检查。Policy
仅在以下情况下允许访问:
- 从许可服务器收到许可响应,并且
- 许可响应表明用户已获得访问应用的许可。
如果您的主要关注点是确保在所有可能的情况下,除非用户在使用时被确认获得许可,否则不允许任何用户访问应用,那么使用 StrictPolicy 是合适的。此外,该 Policy 比 ServerManagedPolicy 提供了略高的安全性 — 由于没有数据在本地缓存,恶意用户无法篡改缓存数据并获得应用访问权限。
同时,此 Policy
对普通用户提出了挑战,因为它意味着在没有网络(蜂窝或 Wi-Fi)连接可用时,他们将无法访问应用。另一个副作用是您的应用会向服务器发送更多许可检查请求,因为无法使用缓存响应。
总的来说,此策略代表着在一定程度上牺牲用户便利性以换取绝对安全和访问控制。在使用此 Policy
之前,请仔细权衡利弊。
要使用 StrictPolicy,只需将其导入您的 Activity,创建一个实例,并在构造 LicenseChecker
时将该实例的引用传递给它。如需了解更多信息,请参阅实例化 LicenseChecker 和 LicenseCheckerCallback。
典型的 Policy
实现需要将应用的许可响应数据保存到持久性存储中,以便在应用调用和设备电源循环之间均可访问。例如,Policy
会在持久性存储中维护上次成功许可检查的时间戳、重试计数、许可有效期和类似信息,而不是每次启动应用时都重置这些值。LVL 中包含的默认 Policy
(即 ServerManagedPolicy)将许可响应数据存储在 SharedPreferences
实例中,以确保数据持久性。
由于 Policy
将使用存储的许可响应数据来确定是否允许或禁止访问应用,因此它必须确保任何存储的数据都是安全的,并且不会被设备上的 root 用户重用或操纵。具体来说,Policy
必须始终在使用应用程序和设备独有的密钥存储数据之前对其进行混淆。使用应用程序和设备特有的密钥进行混淆至关重要,因为它能防止混淆数据在应用程序和设备之间共享。
LVL 协助应用以安全、持久的方式存储其许可响应数据。首先,它提供了一个 Obfuscator
接口,允许您的应用为其存储的数据提供所选择的混淆算法。在此基础上,LVL 提供了辅助类 PreferenceObfuscator,它处理调用应用的 Obfuscator
类以及在 SharedPreferences
实例中读取和写入混淆数据的大部分工作。
LVL 提供了一个完整的 Obfuscator
实现,名为 AESObfuscator,它使用 AES 加密来混淆数据。您可以在应用中不经修改地使用 AESObfuscator,也可以根据需要对其进行调整。如果您正在使用缓存许可响应数据(例如 ServerManagedPolicy)的 Policy
,强烈建议将 AESObfuscator 作为您的 Obfuscator
实现的基础。有关更多信息,请参阅下一部分。
AESObfuscator
LVL 包含一个完整且推荐的 Obfuscator
接口实现,名为 AESObfuscator。该实现与 LVL 示例应用集成,并作为库中的默认 Obfuscator
。
AESObfuscator 通过使用 AES 加密和解密数据,在数据写入或从存储中读取时提供安全的数据混淆。该 Obfuscator
使用应用提供的三个数据字段来初始化加密:
- 一个盐值 — 一个随机字节数组,用于每次(解)混淆。
- 一个应用标识符字符串,通常是应用的软件包名称。
- 一个设备标识符字符串,尽可能从多个设备特定来源派生,以使其尽可能独一无二。
要使用 AESObfuscator,首先将其导入您的 Activity。声明一个私有静态 final 数组来保存盐值字节,并将其初始化为 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
,该 ID 对于每个设备都是唯一的。
请注意,根据您使用的 API,您的应用可能需要请求额外的权限才能获取设备特定信息。例如,要查询 TelephonyManager
以获取设备 IMEI 或相关数据,应用还需要在其清单文件中请求 android.permission.READ_PHONE_STATE
权限。
在仅为了在 Obfuscator
中使用设备特定信息而请求新权限之前,请考虑这样做可能会如何影响您的应用或其在 Google Play 上的过滤(因为某些权限可能会导致 SDK 构建工具添加关联的 <uses-feature>
)。
最后,构造一个 AESObfuscator 实例,传入盐值、应用标识符和设备标识符。您可以在构造 Policy
和 LicenseChecker
时直接构造该实例。例如:
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
源文件中进行。
要添加许可检查并处理响应,您必须:
- 添加导入
- 将LicenseCheckerCallback 实现为私有内部类
- 创建处理程序,用于从 LicenseCheckerCallback 发布到 UI 线程
- 实例化 LicenseChecker 和 LicenseCheckerCallback
- 调用 checkAccess() 以启动许可检查
- 嵌入您的公钥以进行许可
- 调用 LicenseChecker 的 onDestroy() 方法以关闭 IPC 连接。
以下各部分将介绍这些任务。
许可检查和响应概览
在大多数情况下,您应该在应用的主 Activity
的 onCreate()
方法中添加许可检查。这可确保当用户直接启动您的应用时,许可检查会立即调用。在某些情况下,您也可以在其他位置添加许可检查。例如,如果您的应用包含其他应用可以通过 Intent
启动的多个 Activity 组件,则可以在这些 Activity 中添加许可检查。
许可检查包括两个主要操作:
- 调用一个方法以启动许可检查 — 在 LVL 中,这是调用您构造的
LicenseChecker
对象的checkAccess()
方法。 - 一个返回许可检查结果的回调。在 LVL 中,这是一个您实现的
LicenseCheckerCallback
接口。该接口声明了两个方法:allow()
和dontAllow()
,库会根据许可检查的结果调用它们。您可以使用任何所需的逻辑来实现这两个方法,以允许或禁止用户访问您的应用。请注意,这些方法不决定是否允许访问 — 该决定是您的Policy
实现的责任。相反,这些方法仅提供应用如何允许和禁止访问(以及处理应用错误)的行为。allow()
和dontAllow()
方法确实提供了其响应的“原因”,该原因可以是Policy
值之一:LICENSED
、NOT_LICENSED
或RETRY
。特别地,您应该处理dontAllow()
方法收到RETRY
响应的情况,并向用户提供一个“重试”按钮,这可能是因为请求期间服务不可用。

上图说明了典型的许可检查如何进行:
- 应用主 Activity 中的代码实例化
LicenseCheckerCallback
和LicenseChecker
对象。在构造LicenseChecker
时,代码会传入Context
、要使用的Policy
实现以及发布商帐号用于许可的公钥作为参数。 - 然后,代码调用
LicenseChecker
对象的checkAccess()
方法。该方法的实现会调用Policy
以确定本地SharedPreferences
中是否缓存了有效的许可响应。- 如果是,
checkAccess()
实现会调用allow()
。 - 否则,
LicenseChecker
会发起一个发送到许可服务器的许可检查请求。
注意:当您对草稿应用执行许可检查时,许可服务器始终返回
LICENSED
。 - 如果是,
- 收到响应后,
LicenseChecker
会创建一个 LicenseValidator,用于验证已签名的许可数据并提取响应字段,然后将其传递给您的Policy
进行进一步评估。- 如果许可有效,
Policy
会将响应缓存到SharedPreferences
中,并通知验证器,然后验证器会调用LicenseCheckerCallback
对象的allow()
方法。 - 如果许可无效,
Policy
会通知验证器,验证器会调用LicenseCheckerCallback
上的dontAllow()
方法。
- 如果许可有效,
- 如果出现可恢复的本地或服务器错误(例如网络不可用而无法发送请求),
LicenseChecker
会将RETRY
响应传递给您的Policy
对象的processServerResponse()
方法。此外,
allow()
和dontAllow()
回调方法都接收一个reason
参数。allow()
方法的原因通常是Policy.LICENSED
或Policy.RETRY
,而dontAllow()
的原因通常是Policy.NOT_LICENSED
或Policy.RETRY
。这些响应值非常有用,因此您可以向用户显示适当的响应,例如当dontAllow()
响应Policy.RETRY
时提供一个“重试”按钮,这可能是因为服务不可用。 - 如果出现应用错误(例如当应用尝试检查无效软件包名称的许可时),
LicenseChecker
会将错误响应传递给 LicenseCheckerCallback 的applicationError()
方法。
请注意,除了启动许可检查和处理结果(如下文所述)之外,您的应用还需要提供Policy 实现,并且如果 Policy
存储响应数据(例如 ServerManagedPolicy),则还需要提供Obfuscator 实现。
添加导入
首先,打开应用主 Activity 的类文件,并从 LVL 软件包中导入 LicenseChecker
和 LicenseCheckerCallback
。
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。如果您正在使用自定义 Policy
或 Obfuscator
,请改为导入它们。
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
方法中。
在大多数情况下,您应该将 LicenseCheckerCallback
的实现声明为应用主 Activity 类中的私有类。
根据需要实现 allow()
和 dontAllow()
方法。首先,您可以在方法中使用简单的结果处理行为,例如在对话框中显示许可结果。这有助于您的应用更快地运行并协助调试。之后,在您确定了所需的精确行为后,您可以添加更复杂的处理。
在 dontAllow()
中处理未许可响应的一些建议包括:
- 向用户显示一个“重试”对话框,如果提供的
reason
是Policy.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
方法。
对于您的应用而言,这意味着:
- 在许多情况下,您的
LicenseCheckerCallback
方法将从后台线程调用。 - 这些方法将无法更新状态或在 UI 线程中调用任何处理,除非您在 UI 线程中创建
Handler
并让您的回调方法发布到该Handler
。
如果您希望 LicenseCheckerCallback
方法更新 UI 线程,请在主 Activity 的 onCreate()
方法中实例化一个 Handler
,如下所示。在此示例中,LVL 示例应用的 LicenseCheckerCallback
方法(参见上文)调用 displayResult()
以通过 Handler
的 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
方法中,您可以使用 Handler 方法将 Runnable 或 Message 对象发布到 Handler。以下是 LVL 中包含的示例应用如何将 Runnable 发布到 UI 线程中的 Handler 以显示许可状态的方式。
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()
方法实例化 LicenseChecker
和 LicenseCheckerCallback
。
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 管理中心向所有登录 Play 管理中心的开发者公开用于许可的公钥,但会将私钥安全地隐藏在所有用户之外。当应用请求对您帐号中发布的某个应用进行许可检查时,许可服务器会使用您应用的密钥对的私钥签署许可响应。当 LVL 收到响应时,它会使用应用提供的公钥来验证许可响应的签名。
要向应用添加许可,您必须获取您应用的用于许可的公钥并将其复制到您的应用中。以下是查找您的应用用于许可的公钥的方法:
- 转到 Google Play 管理中心并登录。请确保您登录的是您正在许可的应用已发布(或将发布)的帐号。
- 在应用详情页面,找到并点击服务与 API 链接。
- 在服务与 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()
实现中添加对 LicenseChecker
的 onDestroy()
方法的调用。此调用会使 LicenseChecker
正确关闭与 Google Play 应用的 ILicensingService 的所有打开的 IPC 连接,并删除对服务和处理程序的所有本地引用。
未能调用 LicenseChecker
的 onDestroy()
方法可能会导致应用生命周期中的问题。例如,如果用户在许可检查处于活动状态时更改屏幕方向,则应用 Context
将被销毁。如果您的应用未正确关闭 LicenseChecker
的 IPC 连接,则在收到响应时,您的应用将崩溃。同样,如果用户在许可检查进行中时退出您的应用,则在收到响应时,您的应用将崩溃,除非它已正确调用 LicenseChecker
的 onDestroy()
方法以断开与服务的连接。
以下是 LVL 中包含的示例应用中的一个示例,其中 mChecker
是 LicenseChecker
实例:
Kotlin
override fun onDestroy() { super.onDestroy() checker.onDestroy() ... }
Java
@Override protected void onDestroy() { super.onDestroy(); checker.onDestroy(); ... }
如果您正在扩展或修改 LicenseChecker
,您可能还需要调用 LicenseChecker
的 finishCheck()
方法,以清理所有打开的 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 帮助论坛 | 发布商帐号、许可密钥对、测试帐号、服务器响应、测试响应、应用部署和结果 |
市场许可支持常见问题解答 | ||
LVL 问题跟踪器 | Marketlicensing 项目问题跟踪器 | 专门与 LVL 源代码类和接口实现相关的错误和问题报告 |
有关如何发布到上述群组的一般信息,请参阅开发者支持资源页面上的社区资源部分。
其他资源
LVL 附带的示例应用在 MainActivity
类中提供了一个完整的示例,展示了如何启动许可检查和处理结果。