原文链接:http://developer.android.com/guide/google/play/licensing/adding-licensing.html
当你建立一个开发者账号和开发环境之后(见设置签名),你就可以在License Verification Library(LVL)给你的app添加签名了。
在LVL添加签名认证需要遵循下面这些步骤:
1.在你的应用的manifest.xml里添加签名权限
2.实现Policy —— 你可以选择LVL 中提供的完整的实现方案,或者你自己创建一个
3.实现Obfuscator, 如果你的Policy需要缓存任何签名认证结果
4.在你应用的main Activity中添加代码进行签名检查
5.实现DeviceLimiter(可选, 但是不推荐)
下面描述了这些步骤. 当你完成了这些操作, 你就应用可以成功编译你的应用了, 并按照设定的测试环境开始测试你的应用.
LVL包含了对全套源码完整的概述, 见Summary of LVL Classes and Interfaces
为了使用Google Play发送签名验证请求到服务器, 你的应用本身必须有相应的权限: com.android.vending.CHECK_LICENSE. 如果你的应用没有进行签名认证权限的声明就尝试进行签名认证, LVL会抛出一个安全异常.
在你的应用里添加签名认证的权限, 需要在下创建元素 , 如:
下面是例子:
<?xml version"utf-8"?>
...
注意:你不可以在LVL库的项目的manifest中声明CHECK_LICENSE权限, 因为SDK Tools不会把它合并到依赖的项目中. 因此, 你必须在每个项目的manifest中进行权限声明.
Google Play的签名服务本身不确定是否应该让提交的签名通过认证. 相反, 这个职责交给了你应用中提供的Policy.
Policy是LVL声明的接口, 用来根据签名验证结果控制你的应用允许或禁用用户访问. 使用LVL, 你的应用必须提供Policy的实现.
Policy定了两个方法, allowAccess()、processServerResponse(), 当LicenseChecker实例处理来自签名服务器的响应的时候会被调用. 它还定义了一个叫做LicenseResponse的枚举类, 在调用processServerResponse()的时候把指定的签名验证结果传进去 .
processServerResponse() 在决定是否授权之前让你对从签名验证服务器接收到的响应数据进行预处理.
一个典型的实现是可以 从响应数据中提取一些或全部字段并存到本地进行持久化存储, 比如通过SharedPreferences 存储, 以确保可以跨应用访问, 而且不会因为设备关机导致数据丢失. 例如, Policy可以在持久性数据容器 中持有签名最后一次验证成功的时间戳、重试次数、有效期等类似的信息, 而不是在应用每次启动的时候重新加载这些数据.
当在本地存储响应数据的时候, Policy必须确保数据进行了混淆处理(见”实现Obfuscator”)???
ServerManagedPolicy, 使用服务器默认设置和缓存的响应结果来管理各种网络环境下的访问,
StrictPolicy, 不缓存任何响应结果并且只有服务器返回验证过的响应结果才能访问.
在你的签名实现中, 你 可以使用LVL提供的完整的Policy实现(ServerManagedPolicy或StrictPolicy)或者你可以自定义一个Policy. 对于任何自定义Policy, 这里有几个重要的设计要点需要理解并且在你的实现中使用.
签名验证服务器 一般会限制请求次数以防止过度使用服务器资源而导致拒绝服务. 当一个应用超过了请求限制, 服务器会返回503响应码, 作为一个通用的服务器错误告诉你的应用. 这就意味着在限制被重置之前将没有可用的签名验证响应结果提供给用户, 这会无期限的影响应用用户.
如果你准备自定义一个policy, 我们推荐Policy:
把大部分最近的成功的 签名验证结果缓存(并混淆处理) 在本地进行持久化保存
只要缓存的响应结果是有效的, 那么对所有的签名检查都返回缓存的响应结果, 而不是创建一个验证请求. 我们推荐根据服务器额外提供的VT设置响应结果的有效期. 更多信息见Server Response Extras
如果尝试任何请求都返回错误, 使用一个指数回避这个情况. 记录Google Play客户端自动尝试失败的请求, 因为在大多数情况下你的Policy不需要再去尝试
提供一个“宽限期限”允许用户在限定的时间或次数内使用你的应用, 在需要重新进行签名验证的时候. 当没有有效的签名响应结果时, 你可以设置一个范围很大的限制值控制在宽限期内的用户允许使用你的应用直到下一次签名检查完成.
依照上面列出的向导设计你的Policy是很关键, 因为它可以确保最好的用户体验, 甚至同时让你能在出错的情况下有效的控制你的应用.
记录任何服务器提供的可用的设置帮助你管理有效期和缓存, 重试宽限期等等. 使用服务器提供的设置非常简单并且高度推荐. 可以看看ServerManagedPolicy实现的例子. 有关于服务器的提供设置和信息可以见Server Response Extras
ServerManagedPolicy是LVL提供的完整实现的并推荐使用的Policy接口. 这个实现已经作为默认的Policy跟LVL的类和服务整合在类库里.
ServerManagedPolicy提供了处理签名和重试响应的所有方法. 它会把所有的响应结果缓存到本地SharedPreferences 文件, 并使用应用的Obfuscator 的实现类混淆. 这确保了签名的响应数据是安全的并且不会因为设备关机而丢失.
ServerManagedPolicy 提供了接口的具体实现方法processServerResponse()和allowAccess()同时也包括一组支持管理签名响应数据的方法和类型.
重要的是, ServerManagedPolicy的关键特性是使用服务器提供的设置为基础管理应用的签名授权, 而不受各种网络和异常条件的影响. 当一个应用连接到Google Play服务器进行签名认证的时候, 服务器会把设置项作为key-value的形式存到某些响应类型的额外的字段中. 例如, 服务器提供了推荐应用签名使用的一些值, 尤其是是有效期、重试宽限期、最大重试次数. ServerManagedPolicy 把签名响应数据中的值传入到processServerResponse() 并在allowAccess()中校验值. 有关于ServerManagedPolicy使用服务器提供的设置项, 见Server Response Extras.
为了方便、更好的性能和从Google Play服务器上取得更好的签名设置效果, 强烈推荐使用ServerManagedPolicy作为你的Policy.
如果你担心存在本地SharedPreferences中的签名响应数据的安全, 你可以使用更强大的混淆算法或设计一个更严格的不存储签名数据的Policy. LVL包含了一个这样的例子, 更多信息见StrictPolicy .
使用ServerManagedPolicy, 只要将它导入到你的activity中, 创建一个实例, 当创建LicenseChecker的时候传递它的引用进去. 更多信息见Instantiate LicenseChecker and LicenseCheckerCallback
StrictPolicy是LVL提供的另一个可选的Policy的实现接口. 它提供的Policy比ServerManagedPolicy更为严格, 它不允许用户访问应用除非在访问的时候从服务器接收到标识用户可以访问的签名响应数据.
StrictPolicy的主要特点是不会把任何签名响应数据以持久化方式存在本地. 因为不存储数据, 重试请求不会被记录并且缓存的响应数据不会用于完成签名检查. Policy允许访问只能在:
从服务器接收到签名响应数据
并且响应数据标识用户验证通过可以访问应用
如果你所关心的是在所有情况下, 没有用户可以访问你的应用除非用户在使用的时候被确认认证过了, 那么使用StrictPolicy是比较合适的. 此外, Policy比ServerManagedPolicy稍微安全点, 自从不在本地缓存数据后, 恶意用户就没有办法篡改本地数据获取应用访问权限.
同时, Policy对于普通用户来说是一个挑战, 这意味着他们在没有可用网络的时候无法使用应用. 另一个副作用是自从你的应用不使用缓存后将会发送更多的签名验证请求到服务器.
总的来说, 这个策略代表了在某个程度上的用户是绝对安全并且可控的. 所以用Policy之前要仔细考虑下.
使用StrictPolicy, 只要将它导入到你的activity中, 创建一个实例, 当创建LicenseChecker的时候传递它的引用进去.更多信息见Instantiate LicenseChecker and LicenseCheckerCallback.
一个典型的Policy实现需要为一个应用保存签名响应数据到持久化存储器, 所以它可以跨应用调用并且不会因为设备关机而丢失数据. 例如, Policy可以在持久性数据容器中持有签名最后一次验证成功的时间戳、重试次数、有效期等类似的信息, 而不是在应用每次启动的时候重新加载这些数据. 默认的Policy实现在LVL的ServerManagedPolicy中, 它会把响应数据保存在SharedPreferences实例中, 以确保数据是持久化的.
因为Policy会使用保存的数据去判断允许还是不允许访问应用, 它必须保证任何数据是安全的并且不能重用或被拥有root权限的用户控制. 所以Policy必须要在存储之前混淆数据, 对每一个设备使用一个唯一的密钥. 在指定的应用和指定的设备中使用密钥是很关键的, 因为它可以防止在应用和设备上共享混淆过的数据.
LVL帮助应用以一种安全、持久化的方式保存签名响应数据. 首先, 它提供Obfuscator 接口让你的应用在存储数据的时候可以选择混淆算法. 在这个基础上, LVL提供了帮助类PreferenceObfuscator, 负责调用应用的Obfuscator 类, 并且读写SharedPreferences 实例中混淆的数据.
AESObfuscator是LVL提供的并推荐的Obfuscator 的实现接口. 该实现已经集成在LVL的示例应用中并且在lib库中作为默认的Obfuscator .
AESObfuscator提供安全混淆实现是在从数据存储器中读写数据的时候通过AES加密或解密实现的. Obfuscator 种子加密要用到应用提供的三个数据字段:
salt – 一个随机的字节(byte)数组用于每一个(单位)的混淆
应用的字符标识, 代表着应用的包名
设备的字符标识, 尽可能来自指定的设备的源数据, 以便于是唯一值
使用AESObfuscator, 首先要导入到你的activity. 定义一个私有静态的常量数组(private static final array)用来保存salt字节并初始化生成20个随机字节.
...
// 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码或相关数据, 你的应用就需要在manifest里添加权限android.permission.READ_PHONE_STATE
请求新权限唯一的目的就是为了获取特定设备的信息用于你的Obfuscator, 考虑做可能会影响你的应用或在Google Play上被过滤(因为有些权限可能引起SDK构建工具添加关联的)
最后, 构建AESObfuscator实例, 传入参数salt、应用标识符、设备标识符. 你可以在构建Policy 和LicenseChecker 的时候直接创建. 例如:
...
// Construct the LicenseChecker with a Policy.
mChecker = new LicenseChecker(
this, new ServerManagedPolicy(this,
new AESObfuscator(SALT, getPackageName(), deviceId)),
BASE64_PUBLIC_KEY // Your public licensing key.
);
...
完整的例子见LVL示例代码的MainActivity.
在Activity中检查签名
当你实现了Policy管理你应用的访问, 下一步就是增加签名认证到你的应用, 创建一个验证请求到服务器如果需要基于响应结果管理你应用的访问. 所有增加签名检查和处理响应结果的工作都在你的main Activity里.
增加签名检查和处理响应结果, 你必须:
导入
以内部类的方式实现LicenseCheckerCallback
创建一个从LicenseCheckerCallback 到UI线程的Handler
实例化LicenseChecker和LicenseCheckerCallback
调用checkAccess()初始化签名检查
给签名认证植入你的公钥
关闭IPC连接的时候调用LicenseChecker的onDestory()
下面描述了这些步骤.
签名检查和响应的描述
一个启动签名检查的方法——在LVL里, 这是你实现的LicenseCheckerCallback接口. 这个接口定义了两个方法, allow() 和 dontAllow(), 它们都会被lib库根据签名检查的结果触发. 你可以根据你的需求实现任何逻辑, 允许或不允许用户访问你的应用. 注意, 这两个方法不确定是否允许访问——这个由你实现的Policy来决定. 当然, 这些方法简单的提供了应用的行为, 怎么允许和禁止(或处理应用的错误).
allow()和dontAllow()方法提供了它们响应结果的“理由”(reason), 这个是Policy中的一个值, LICENSED, NOT_LICENSED, or RETRY. 需要提出来的是, 你应该在dontAllow()中处理接收到RETRY的情况并且提供一个“重试”的按钮给用户, 这可能会因为服务在请求阶段不可用而发生.
http://developer.android.com/images/licensing_flow.png
图6 签名检查的流程图
上面的图表说明了签名检查发生的过程:
在应用main Activity里实例化LicenseCheckerCallback和LicenseChecker等对象. 当构造LicenseChecker的时候, 需要传入Context、Policy的实现类、开发者账号的公钥作为签名检查的参数.
然后在LicenseChecker对象里调用checkAccess(). 方法的实现调用Policy判断SharedPreferences里是否有有效的签名响应数据.
:* 如果有, checkAccess()调用allow()
:* 否则, LicenseChecker会初始化一个签名认证请求发送到签名认证服务器
注意:签名认证服务器总是会返回LICENSED 当你执行一个测试应用的签名检查时
接收到响应数据的时候, LicenseChecker会创建一个LicenseValidator检查签名的签名并提取响应数据中的字段, 然后把它们传进你的Policy做进一步的操作.
:* 如果签名有效, Policy会缓存响应结果到SharedPreferences并通知LicenseValidator等下调用LicenseCheckerCallback对象里的allow()
:* 如果签名无效, Policy会通知LicenseValidator调用LicenseCheckerCallback里的dontallow()
如果发生可恢复的本地或者服务器错误, 例如当前网络无法发送请求, LicenseChecker会传递RETRY为响应结果到Policy的processServerResponse(). 并且allow()和dontAllow()也会接收一个reason参数. allow()的reason值一般是Policy.LICENSED 或Policy.RETRY, dontAllow()的reason值一般是Policy. Policy.LICENSED 或Policy.RETRY或Policy.RETRY. 如果你想给用户显示一个响应值, 那么这些值是非常有用的, 例如提供一个”重试”的按钮当dontAllow()响应Policy.RETRY, 这可能是由于服务不可用
如果应用出现错误, 例如应用尝试以一个无效的包名去检查签名时候, LicenseChecker传递一个错误的响应结果到LicenseCheckerCallback的applicationError()中
注意, 除了在启动的时候进行签名检查并处理结果, 你的应用也需要提供Policy的实现, 如果Policy保存响应结果(例如ServerManagedPolicy), 一个Obfuscator 的实现.
导入
首先, 打开应用的main Activity文件, 从LVL的包中导入LicenseChecker和LicenseCheckerCallback.
import com.android.vending.licensing.LicenseChecker; import com.android.vending.licensing.LicenseCheckerCallback;
如果你需要用LVL提供的默认Policy实现ServerManagedPolicy, 也导入它, 同时导入AESObfuscator. 如果使用自定义的Policy和Obfuscator就不要了.
import com.android.vending.licensing.ServerManagedPolicy; import com.android.vending.licensing.AESObfuscator;
以内部类的方式实现LicenseCheckerCallback
LicenseCheckerCallback是LVL提供的处理签名检查结果的接口. 使用LVL进行签名检查, 必须实现LicenseCheckerCallback和它的方法允许或者不允许访问应用.
签名的检查结果总是会调用LicenseCheckerCallback中的一个方法, 基于响应结果的验证, 服务器验证码的本身, 和任何你的Policy提供的额外的处理. 你的应用可以在任何需要的地方实现方法. 一般最好保持方法简单, 限制它们管理UI状态和应用的访问. 如果你想对签名响应结果进行进一步处理, 例如连接后端服务器或使用自定义的约束, 你应该考虑把你的代码合并到Policy, 而不是把它们放在LicenseCheckerCallback的方法里.
在大多数情况下, 你应该把LicenseCheckerCallback的实现作为一个内部类定义在你的main Activity中.
根据需要实现allow()和dontAllow(). 首先, 你可以在方法里只进行简单的结果处理, 例如在对话框里显示签名的检查结果. 这可以帮助你更快的运行你的应用而且有利于调试. 然后, 当你决定你想要其他的行为之后就可以添加更复杂处理.
这里是一些在dontAllow()里处理没有验证结果的建议:
如果reason是Policy.RETRY, 显示一个”重试”的对话框给用户, 包含一个按钮可以创建一个新的签名认证请求
显示”购买这个应用”的对话框, 包含一个按钮可以引导用户到Google Play上可以购买这个应用的详情页. 有关于设置这种链接的的信息, 见 Linking to Your Products
显示一个Toast通知表明这个应用是有限的, 因为它没有认证通过
下面是LVL中实现LicenseCheckerCallback的例子, 在方法中使用对话框显示签名检查结果.
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));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 is not licensed to use this app.
// Your response should always inform the user that the application
// is not 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让你的应用处理无法重试的错误. 错误列表见Licensing Reference的Server Response Codes. 你可以在任何你需要的地方实现这个方法. 在大多数情况下, 这个方法应该记录错误并调用dontAllow().
创建一个从LicenseCheckerCallback 到UI的Handler线程
在签名检查的时候, LVL会传递一个请求到Google Play, 来处理和签名验证服务器的通信. LVL在异步IPC(使用Binder)上传递请求 , 所以实际处理和网络通信不在你应用的同一个线程中. 同样的, 当Google Play接收到结果时, 它在IPC里调用回调方法, 在你应用进程的IPC线程池里轮流执行 .
LicenseChecker通过Google Play管理你应用的IPC通信, 包括调用发送请求和接收响应的回调函数的方法. LicenseChecker会跟踪打开的签名请求并管理它们的超时设定.
所以它可以适当的处理超时也可以处理传进来的响应, 而不会影响你的UI线程, LicenseChecker实例化的时候会产生一个后台线程. 它会在线程里处理所有的签名检查结果, 无论是是从服务器接收到的响应结果还是超时错误. LVL最后都会从后台线程调用你的LicenseCheckerCallback.
对你的应用来说, 这意味着:
在大多数情况下, 你的LicenseCheckerCallback里的方法会被后台线程触发
这些方法不会修改状态或在UI线程里调用任何处理方法, 除非你在UI线程里创建一个Handler并且把你的回调方法传进去.
如果你想让LicenseCheckerCallback里的方法更新UI线程, 如下所示, 在main Activity里的onCreate()里创建Handler. 在这个例子里, LVL示例应用的LicenseCheckerCallback方法(见上文)调用displayResult()通过Handler的post()更新UI线程.
private Handler mHandler;
1
2
3
4
5
@Override
public void onCreate(Bundle savedInstanceState) {
...
mHandler = new Handler();
}
然后, 在LicenseCheckerCallback方法里, 你可以使用Handler方法传递Runnable或Message对象到Handler对象. 这里是LVL里的例子, 在UI线程里传递Runnable到Handler显示签名状态.
private void displayResult(final String result) {
mHandler.post(new Runnable() {
public void run() {
mStatusText.setText(result);
setProgressBarIndeterminateVisibility(false);
mCheckLicenseButton.setEnabled(true);
}
});
}
初始化LicenseChecker和LicenseCheckerCall -back
在main Activity的onCreate()里, 创建LicenseChecker和LicenseCheckerCallback的私有实例对象. 你必须先实例化LicenseCheckerCallback, 因为你调用LicenseChecker的构造函数的时候需要传递LicenseCheckerCallback的引用进去.
当你实例化LicenseChecker的时候, 你需要传入这些参数:
Context
用于签名检查的Policy实例引用. 在大多数情况下, 你可以使用LVL提供的默认Policy实例, ServerManagedPolicy.
用于认证的你公共账号密钥的字符
如果你使用ServerManagedPolicy, 你不需要直接访问类, 所以你可以像下面的例子里一样在LicenseChecker的构造函数里初始化它. 注意你在构造ServerManagedPolicy的时候需要传入一个Obfuscator实例的引用.
下面是在Activity的onCreate()里初始化LicenseChecker和LicenseCheckerCallback的例子.
public class MainActivity extends Activity {
...
private LicenseCheckerCallback mLicenseCheckerCallback;
private LicenseChecker mChecker;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
// Construct the LicenseCheckerCallback. The library calls this when done.
mLicenseCheckerCallback = new MyLicenseCheckerCallback();
// Construct the LicenseChecker with a Policy.
mChecker = new LicenseChecker(
this, new ServerManagedPolicy(this,
new AESObfuscator(SALT, getPackageName(), deviceId)),
BASE64_PUBLIC_KEY // Your public licensing key.
);
...
}
}
注意只有在本地的缓存里有有效的签名响应数据的时候, LicenseChecker才会在UI线程里调用LicenseCheckerCallback的回调方法. 如果发送了签名检查请求到服务器, 回调方法总会被调用, 甚至在网络错误的时候也会调用.
调用checkAccess()初始化签名检查
在main Activity里, 增加调用LicenseChecker实例的checkAccess()的方法. 在这个方法里, 传入LicenseCheckerCallback实例的引用作为作为参数. 如果你需要在调用处理任何特殊的UI效果或状态管理, 你可以在checkAccess()的封装方法里有效的处理. 下面是LVL里从doCheck()封装方法里调用checkAccess()的例子:
public class MainActivity extends Activity {
...
private LicenseCheckerCallback mLicenseCheckerCallback;
private LicenseChecker mChecker;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
// Construct the LicenseCheckerCallback. The library calls this when done.
mLicenseCheckerCallback = new MyLicenseCheckerCallback();
// Construct the LicenseChecker with a Policy.
mChecker = new LicenseChecker(
this, new ServerManagedPolicy(this,
new AESObfuscator(SALT, getPackageName(), deviceId)),
BASE64_PUBLIC_KEY // Your public licensing key.
);
...
}
}
为签名认证植入你的密钥
对每一个开发者账号, Google Play是自动生成只用于签名检查的2048位的RSA公有/私有密钥. 密钥和开发者的账号是唯一关联的, 并且通过这个账号发布的应用都共享这一个密钥. 虽然密钥和开发者账号关联, 但是它和你登录应用(或它的衍生产品)所用的不一样.
Google Play 开发者网站公开用于签名的公共密钥授权给任何开发者, 但是它保持所有用户的私有密钥在一个安全的地方. 当一个应用为你账号发布的应用请求签名认证, 签名认证服务器会标识响应结果是使用的私有密钥. 当LVL接收到响应的时候, 它会使用应用提供的公共密钥去验证响应结果的签名.
要在应用中添加签名, 你必须获得你开发账号的公钥并复制到你的应用. 下面是怎么获取你账号的公钥:
1.登录Google Play开发者网站. 确保你登录账号上的应用已发布(或将发布).
2.在账号首页, 查找”编辑属性”(Edit profile)链接并点击
3.在编辑属性(Edit profile)页面, 查找”签名”(Licensing)面板. 用于签名的公钥在文本框”公钥”(Public Key)里.
添加公钥到你的应用, 只要在文本框里复制到你的应用里, 赋值给你String 属性BASE64_PUBLIC_KEY. 当你复制的时候确保你选择了完整密钥, 不要遗漏了字符.
示例:
public class MainActivity extends Activity {
private static final String BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... "; //truncated for this example
...
}
关闭IPC连接的时候调用LicenseChecker的onDestory()方法
最后, 让LVL在你的应用Context改变的时候清空, 在Activity的onDesctory()实现里增加一个调用LicenseChecker的onDestory()的方法. 这会让LicenseChecker适时的关闭任何连接到Google Play 应用的IliecensingService的IPC连接, 并且删除任何service和handler的引用.
调用LicenseChecker的onDestory()失败会导致你的应用在生命周期内出现问题. 例如, 当用户在签名检查的时候改变屏幕方向, 应用的Context会被销毁. 如果你的应用不能适时的关闭LicenseChecker的IPC连接, 你用的应用会在接收到响应的时候崩溃. 类似的, 如果用户在签名检查的时候退出应用会导致应用在接收到响应的时候崩溃, 除非你在断开连接的时候正确的调用LicenseChecker的onDestory().
下面是LVL里的例子, mChecker是LicenseChecker的实例:
@Override
protected void onDestroy() {
super.onDestroy();
mChecker.onDestroy();
...
}
如果你继承或修改LicenseChecker, 你可能还需要调用LicenseChecker的finishCheck()清理所有IPC连接.
实现DeviceLimiter
有些时候, 你可能希望你的Policy限制一些实际的设备允许使用单一的签名. 这可以防止用户将签名的应用移动到其他设备上并且在这些设备上通过同一个账号使用应用 . 它还可以防止用户通过提供账号关联的签名给其他人共享应用, 他们随后就可以在他们的设备上登录账号并授权给应用.
LVL支持通过DeviceLimiter接口给每台定义了方法allowDeviceAccess()的设备签名. 当LicenseValidator处理来自签名服务器的响应数据时候, 它会调用allowDeviceAccess(), 把响应数据中的用户ID传进去.
如果你不行限定设备, 这不是必需的工作——LicenseChecker类会自动使用默认的实现NullDeviceLimiter. 顾名思义, NullDeviceLimiter是一个”无操作”的类, 它的allowDeviceAccess()方法对所有用户和设备只会简单的返回LICENSED.
有些时候, 你可能希望你的Policy限制一些实际的设备允许使用单一的签名. 这可以防止用户将签名的应用移动到其他设备上并且在这些设备上通过同一个账号使用应用 . 它还可以防止用户通过提供账号关联的签名给其他人共享应用, 他们随后就可以在他们的设备上登录账号并授权给应用.
LVL支持通过DeviceLimiter接口给每台定义了方法allowDeviceAccess()的设备签名. 当LicenseValidator处理来自签名服务器的响应数据时候, 它会调用allowDeviceAccess(), 把响应数据中的用户ID传进去.
如果你不行限定设备, 这不是必需的工作——LicenseChecker类会自动使用默认的实现NullDeviceLimiter. 顾名思义, NullDeviceLimiter是一个”无操作”的类, 它的allowDeviceAccess()方法对所有用户和设备只会简单的返回LICENSED.
警告: Per-device签名对大多数应用不建议使用, 因为:
它需要你提供后端服务器来管理用户和设备的映射
它可能在不经意间导致用户在另一个设备上无法访问他们合法购买的应用
混淆你的代码
确保你的应用安全, 特别对于使用签名和/或自定义的约束和保护的付费应用来说, 混淆你的应用程序的代码是非常重要的. 适当的混淆你应用的代码可以让恶意用户反编译你的应用程序字节码变得非常困难, 修改它——例如移除签名检查的部分, 然后重新编译.
大多数混淆处理程序对android应用都是有效的, 包括ProGuard也提供了代码优化的特性. 强烈推荐使用了Google Play Licensing的应用使用ProGuard或类似的程序混淆你的代码.
发布签名过的应用
你测试完你的签名之后, 就可以准备在Google Play上发布你的应用了. 按照正常的步骤准备、登录, 然后发布你的应用.
删除Copy Protection
当你上传签名过的应用之后, 记得在应用里删除copy protection如果它现在在使用. 登录开发者网站进入应用上传的详情页, 检查并删除copy protection. 在发布(Publishing)选项部分, 确保Copy Protection单选按钮选项是”关闭”(Off)状态.
获得支持
如果你在实现或部署发布你的应用的时候有问题或遇到问题, 请使用下面表格中的支持资源列表. 通过指导你查询正确的论坛, 你可以更快速的获得你需要的支持.
表2 Google Play 授权服务的开发者支持资源
{|style="border-spacing: 0px;margin: 4px 4px; width: 90%; border-left:1px solid #ccc;border-top:1px solid #ccc; "
|-style="background:#DEE8F1; "
! style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px" | 支持类型
! style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px" | 资源
! style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px" | 主体范围
|- style=" vertical-align:top;"
| style=" border-right:1px solid #ccc;border-bottom:0px solid #ccc; padding:5px 15px; " |
开发和测试问题
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
Google 群组: android-developers
| style="border-right:1px solid #ccc;border-bottom:0px solid #ccc; padding:5px 15px; " |
LVL 下载和集成, lib项目,Policy 问题, 用户体验建议, 处理响应, Obfuscator, IPC, 测试环境设置
|- style=" vertical-align:top;"
| style=" border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
Stack Overflow:http://stackoverflow.com/questions/tagged/android
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
|- style=" vertical-align:top;"
| style=" border-right:1px solid #ccc;border-bottom:0px solid #ccc; padding:5px 15px; " |
账号,发布和部署问题
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
Google Play 帮助论坛
| style="border-right:1px solid #ccc;border-bottom:0px solid #ccc; padding:5px 15px; " |
开发者账号, 签名密钥, 测试账号, 服务器响应, 测试响应, 应用部署和结果
|- style=" vertical-align:top;"
| style=" border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
授权常见问题
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
|- style=" vertical-align:top;"
| style=" border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
LVL 问题跟踪
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
Marketlicensing project 问题跟踪
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
LVL源码和接口实现的Bug和问题报告
|}
怎么把一般信息发送到上面的群组列表里去, 见Resource标签页的 Developer Forums文档
当你建立一个开发者账号和开发环境之后(见设置签名),你就可以在License Verification Library(LVL)给你的app添加签名了。
在LVL添加签名认证需要遵循下面这些步骤:
1.在你的应用的manifest.xml里添加签名权限
2.实现Policy —— 你可以选择LVL 中提供的完整的实现方案,或者你自己创建一个
3.实现Obfuscator, 如果你的Policy需要缓存任何签名认证结果
4.在你应用的main Activity中添加代码进行签名检查
5.实现DeviceLimiter(可选, 但是不推荐)
下面描述了这些步骤. 当你完成了这些操作, 你就应用可以成功编译你的应用了, 并按照设定的测试环境开始测试你的应用.
LVL包含了对全套源码完整的概述, 见Summary of LVL Classes and Interfaces
添加签名权限
为了使用Google Play发送签名验证请求到服务器, 你的应用本身必须有相应的权限: com.android.vending.CHECK_LICENSE. 如果你的应用没有进行签名认证权限的声明就尝试进行签名认证, LVL会抛出一个安全异常.
在你的应用里添加签名认证的权限, 需要在下创建元素 , 如:
下面是例子:
<?xml version"utf-8"?>
...
注意:你不可以在LVL库的项目的manifest中声明CHECK_LICENSE权限, 因为SDK Tools不会把它合并到依赖的项目中. 因此, 你必须在每个项目的manifest中进行权限声明.
实现Policy
Google Play的签名服务本身不确定是否应该让提交的签名通过认证. 相反, 这个职责交给了你应用中提供的Policy.
Policy是LVL声明的接口, 用来根据签名验证结果控制你的应用允许或禁用用户访问. 使用LVL, 你的应用必须提供Policy的实现.
Policy定了两个方法, allowAccess()、processServerResponse(), 当LicenseChecker实例处理来自签名服务器的响应的时候会被调用. 它还定义了一个叫做LicenseResponse的枚举类, 在调用processServerResponse()的时候把指定的签名验证结果传进去 .
processServerResponse() 在决定是否授权之前让你对从签名验证服务器接收到的响应数据进行预处理.
一个典型的实现是可以 从响应数据中提取一些或全部字段并存到本地进行持久化存储, 比如通过SharedPreferences 存储, 以确保可以跨应用访问, 而且不会因为设备关机导致数据丢失. 例如, Policy可以在持久性数据容器 中持有签名最后一次验证成功的时间戳、重试次数、有效期等类似的信息, 而不是在应用每次启动的时候重新加载这些数据.
当在本地存储响应数据的时候, Policy必须确保数据进行了混淆处理(见”实现Obfuscator”)???
allowAccess()根据任何有效的签名验证响应数据(来自服务器响应或缓存)或其他特定应用程序信息决定是否授权给用户访问你的应用.例如, 你实现的 allowAccess()可以带入一些额外的条件, 如usage或者从后台服务器检索到的数据. 在所有的情况下, 如果用户被授权使用应用, 方法应该只返回true. 如果因为网络或系统原因导致签名验证未完成, 你可以指定一个重试次数并允许用户暂时访问直到下一次完成验证.
为了简化你给应用添加签名的过程并提供一个设置Policy的说明, LVL提供了两个完整的Policy实现, 你可以直接使用而不需要任何修改:ServerManagedPolicy, 使用服务器默认设置和缓存的响应结果来管理各种网络环境下的访问,
StrictPolicy, 不缓存任何响应结果并且只有服务器返回验证过的响应结果才能访问.
对于大多数应用来说, 比较推荐ServerManagedPolicy. ServerManagedPolicy是LVL默认的方案, 并且已经包含在LVL的示例应用中.
在你的签名实现中, 你 可以使用LVL提供的完整的Policy实现(ServerManagedPolicy或StrictPolicy)或者你可以自定义一个Policy. 对于任何自定义Policy, 这里有几个重要的设计要点需要理解并且在你的实现中使用.
签名验证服务器 一般会限制请求次数以防止过度使用服务器资源而导致拒绝服务. 当一个应用超过了请求限制, 服务器会返回503响应码, 作为一个通用的服务器错误告诉你的应用. 这就意味着在限制被重置之前将没有可用的签名验证响应结果提供给用户, 这会无期限的影响应用用户.
如果你准备自定义一个policy, 我们推荐Policy:
把大部分最近的成功的 签名验证结果缓存(并混淆处理) 在本地进行持久化保存
只要缓存的响应结果是有效的, 那么对所有的签名检查都返回缓存的响应结果, 而不是创建一个验证请求. 我们推荐根据服务器额外提供的VT设置响应结果的有效期. 更多信息见Server Response Extras
如果尝试任何请求都返回错误, 使用一个指数回避这个情况. 记录Google Play客户端自动尝试失败的请求, 因为在大多数情况下你的Policy不需要再去尝试
提供一个“宽限期限”允许用户在限定的时间或次数内使用你的应用, 在需要重新进行签名验证的时候. 当没有有效的签名响应结果时, 你可以设置一个范围很大的限制值控制在宽限期内的用户允许使用你的应用直到下一次签名检查完成.
依照上面列出的向导设计你的Policy是很关键, 因为它可以确保最好的用户体验, 甚至同时让你能在出错的情况下有效的控制你的应用.
记录任何服务器提供的可用的设置帮助你管理有效期和缓存, 重试宽限期等等. 使用服务器提供的设置非常简单并且高度推荐. 可以看看ServerManagedPolicy实现的例子. 有关于服务器的提供设置和信息可以见Server Response Extras
ServerManagedPolicy
ServerManagedPolicy是LVL提供的完整实现的并推荐使用的Policy接口. 这个实现已经作为默认的Policy跟LVL的类和服务整合在类库里.
ServerManagedPolicy提供了处理签名和重试响应的所有方法. 它会把所有的响应结果缓存到本地SharedPreferences 文件, 并使用应用的Obfuscator 的实现类混淆. 这确保了签名的响应数据是安全的并且不会因为设备关机而丢失.
ServerManagedPolicy 提供了接口的具体实现方法processServerResponse()和allowAccess()同时也包括一组支持管理签名响应数据的方法和类型.
重要的是, ServerManagedPolicy的关键特性是使用服务器提供的设置为基础管理应用的签名授权, 而不受各种网络和异常条件的影响. 当一个应用连接到Google Play服务器进行签名认证的时候, 服务器会把设置项作为key-value的形式存到某些响应类型的额外的字段中. 例如, 服务器提供了推荐应用签名使用的一些值, 尤其是是有效期、重试宽限期、最大重试次数. ServerManagedPolicy 把签名响应数据中的值传入到processServerResponse() 并在allowAccess()中校验值. 有关于ServerManagedPolicy使用服务器提供的设置项, 见Server Response Extras.
为了方便、更好的性能和从Google Play服务器上取得更好的签名设置效果, 强烈推荐使用ServerManagedPolicy作为你的Policy.
如果你担心存在本地SharedPreferences中的签名响应数据的安全, 你可以使用更强大的混淆算法或设计一个更严格的不存储签名数据的Policy. LVL包含了一个这样的例子, 更多信息见StrictPolicy .
使用ServerManagedPolicy, 只要将它导入到你的activity中, 创建一个实例, 当创建LicenseChecker的时候传递它的引用进去. 更多信息见Instantiate LicenseChecker and LicenseCheckerCallback
StrictPolicy
StrictPolicy是LVL提供的另一个可选的Policy的实现接口. 它提供的Policy比ServerManagedPolicy更为严格, 它不允许用户访问应用除非在访问的时候从服务器接收到标识用户可以访问的签名响应数据.
StrictPolicy的主要特点是不会把任何签名响应数据以持久化方式存在本地. 因为不存储数据, 重试请求不会被记录并且缓存的响应数据不会用于完成签名检查. Policy允许访问只能在:
从服务器接收到签名响应数据
并且响应数据标识用户验证通过可以访问应用
如果你所关心的是在所有情况下, 没有用户可以访问你的应用除非用户在使用的时候被确认认证过了, 那么使用StrictPolicy是比较合适的. 此外, Policy比ServerManagedPolicy稍微安全点, 自从不在本地缓存数据后, 恶意用户就没有办法篡改本地数据获取应用访问权限.
同时, Policy对于普通用户来说是一个挑战, 这意味着他们在没有可用网络的时候无法使用应用. 另一个副作用是自从你的应用不使用缓存后将会发送更多的签名验证请求到服务器.
总的来说, 这个策略代表了在某个程度上的用户是绝对安全并且可控的. 所以用Policy之前要仔细考虑下.
使用StrictPolicy, 只要将它导入到你的activity中, 创建一个实例, 当创建LicenseChecker的时候传递它的引用进去.更多信息见Instantiate LicenseChecker and LicenseCheckerCallback.
实现Obfuscator
一个典型的Policy实现需要为一个应用保存签名响应数据到持久化存储器, 所以它可以跨应用调用并且不会因为设备关机而丢失数据. 例如, Policy可以在持久性数据容器中持有签名最后一次验证成功的时间戳、重试次数、有效期等类似的信息, 而不是在应用每次启动的时候重新加载这些数据. 默认的Policy实现在LVL的ServerManagedPolicy中, 它会把响应数据保存在SharedPreferences实例中, 以确保数据是持久化的.
因为Policy会使用保存的数据去判断允许还是不允许访问应用, 它必须保证任何数据是安全的并且不能重用或被拥有root权限的用户控制. 所以Policy必须要在存储之前混淆数据, 对每一个设备使用一个唯一的密钥. 在指定的应用和指定的设备中使用密钥是很关键的, 因为它可以防止在应用和设备上共享混淆过的数据.
LVL帮助应用以一种安全、持久化的方式保存签名响应数据. 首先, 它提供Obfuscator 接口让你的应用在存储数据的时候可以选择混淆算法. 在这个基础上, LVL提供了帮助类PreferenceObfuscator, 负责调用应用的Obfuscator 类, 并且读写SharedPreferences 实例中混淆的数据.
LVL提供了Obfuscator 的完整实现类——AESObfuscator, 使用AES加密技术混淆数据. 你可以直接在你的应用中使用AESObfuscator或者修改它以适应你的需求. 更多信息见一下章节.
AESObfuscator是LVL提供的并推荐的Obfuscator 的实现接口. 该实现已经集成在LVL的示例应用中并且在lib库中作为默认的Obfuscator .
AESObfuscator提供安全混淆实现是在从数据存储器中读写数据的时候通过AES加密或解密实现的. Obfuscator 种子加密要用到应用提供的三个数据字段:
salt – 一个随机的字节(byte)数组用于每一个(单位)的混淆
应用的字符标识, 代表着应用的包名
设备的字符标识, 尽可能来自指定的设备的源数据, 以便于是唯一值
使用AESObfuscator, 首先要导入到你的activity. 定义一个私有静态的常量数组(private static final array)用来保存salt字节并初始化生成20个随机字节.
...
// 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码或相关数据, 你的应用就需要在manifest里添加权限android.permission.READ_PHONE_STATE
请求新权限唯一的目的就是为了获取特定设备的信息用于你的Obfuscator, 考虑做可能会影响你的应用或在Google Play上被过滤(因为有些权限可能引起SDK构建工具添加关联的)
最后, 构建AESObfuscator实例, 传入参数salt、应用标识符、设备标识符. 你可以在构建Policy 和LicenseChecker 的时候直接创建. 例如:
...
// Construct the LicenseChecker with a Policy.
mChecker = new LicenseChecker(
this, new ServerManagedPolicy(this,
new AESObfuscator(SALT, getPackageName(), deviceId)),
BASE64_PUBLIC_KEY // Your public licensing key.
);
...
完整的例子见LVL示例代码的MainActivity.
在Activity中检查签名
当你实现了Policy管理你应用的访问, 下一步就是增加签名认证到你的应用, 创建一个验证请求到服务器如果需要基于响应结果管理你应用的访问. 所有增加签名检查和处理响应结果的工作都在你的main Activity里.
增加签名检查和处理响应结果, 你必须:
导入
以内部类的方式实现LicenseCheckerCallback
创建一个从LicenseCheckerCallback 到UI线程的Handler
实例化LicenseChecker和LicenseCheckerCallback
调用checkAccess()初始化签名检查
给签名认证植入你的公钥
关闭IPC连接的时候调用LicenseChecker的onDestory()
下面描述了这些步骤.
签名检查和响应的描述
在大部分情况下, 你应该把签名检查的工作放在你应用的main Activity的onCreate()中. 这可以确保用户一加载你的应用马上就会进行签名检查.有些时候, 你也可以把签名检查放在其他位置. 例如, 如果你的应用包含了多个可以通过其他Intent启动的Activity的模块, 你可以把签名检查放在这写Activity里面.
一个启动签名检查的方法——在LVL里, 这是你实现的LicenseCheckerCallback接口. 这个接口定义了两个方法, allow() 和 dontAllow(), 它们都会被lib库根据签名检查的结果触发. 你可以根据你的需求实现任何逻辑, 允许或不允许用户访问你的应用. 注意, 这两个方法不确定是否允许访问——这个由你实现的Policy来决定. 当然, 这些方法简单的提供了应用的行为, 怎么允许和禁止(或处理应用的错误).
allow()和dontAllow()方法提供了它们响应结果的“理由”(reason), 这个是Policy中的一个值, LICENSED, NOT_LICENSED, or RETRY. 需要提出来的是, 你应该在dontAllow()中处理接收到RETRY的情况并且提供一个“重试”的按钮给用户, 这可能会因为服务在请求阶段不可用而发生.
http://developer.android.com/images/licensing_flow.png
图6 签名检查的流程图
上面的图表说明了签名检查发生的过程:
在应用main Activity里实例化LicenseCheckerCallback和LicenseChecker等对象. 当构造LicenseChecker的时候, 需要传入Context、Policy的实现类、开发者账号的公钥作为签名检查的参数.
然后在LicenseChecker对象里调用checkAccess(). 方法的实现调用Policy判断SharedPreferences里是否有有效的签名响应数据.
:* 如果有, checkAccess()调用allow()
:* 否则, LicenseChecker会初始化一个签名认证请求发送到签名认证服务器
注意:签名认证服务器总是会返回LICENSED 当你执行一个测试应用的签名检查时
接收到响应数据的时候, LicenseChecker会创建一个LicenseValidator检查签名的签名并提取响应数据中的字段, 然后把它们传进你的Policy做进一步的操作.
:* 如果签名有效, Policy会缓存响应结果到SharedPreferences并通知LicenseValidator等下调用LicenseCheckerCallback对象里的allow()
:* 如果签名无效, Policy会通知LicenseValidator调用LicenseCheckerCallback里的dontallow()
如果发生可恢复的本地或者服务器错误, 例如当前网络无法发送请求, LicenseChecker会传递RETRY为响应结果到Policy的processServerResponse(). 并且allow()和dontAllow()也会接收一个reason参数. allow()的reason值一般是Policy.LICENSED 或Policy.RETRY, dontAllow()的reason值一般是Policy. Policy.LICENSED 或Policy.RETRY或Policy.RETRY. 如果你想给用户显示一个响应值, 那么这些值是非常有用的, 例如提供一个”重试”的按钮当dontAllow()响应Policy.RETRY, 这可能是由于服务不可用
如果应用出现错误, 例如应用尝试以一个无效的包名去检查签名时候, LicenseChecker传递一个错误的响应结果到LicenseCheckerCallback的applicationError()中
注意, 除了在启动的时候进行签名检查并处理结果, 你的应用也需要提供Policy的实现, 如果Policy保存响应结果(例如ServerManagedPolicy), 一个Obfuscator 的实现.
导入
首先, 打开应用的main Activity文件, 从LVL的包中导入LicenseChecker和LicenseCheckerCallback.
import com.android.vending.licensing.LicenseChecker; import com.android.vending.licensing.LicenseCheckerCallback;
如果你需要用LVL提供的默认Policy实现ServerManagedPolicy, 也导入它, 同时导入AESObfuscator. 如果使用自定义的Policy和Obfuscator就不要了.
import com.android.vending.licensing.ServerManagedPolicy; import com.android.vending.licensing.AESObfuscator;
以内部类的方式实现LicenseCheckerCallback
LicenseCheckerCallback是LVL提供的处理签名检查结果的接口. 使用LVL进行签名检查, 必须实现LicenseCheckerCallback和它的方法允许或者不允许访问应用.
签名的检查结果总是会调用LicenseCheckerCallback中的一个方法, 基于响应结果的验证, 服务器验证码的本身, 和任何你的Policy提供的额外的处理. 你的应用可以在任何需要的地方实现方法. 一般最好保持方法简单, 限制它们管理UI状态和应用的访问. 如果你想对签名响应结果进行进一步处理, 例如连接后端服务器或使用自定义的约束, 你应该考虑把你的代码合并到Policy, 而不是把它们放在LicenseCheckerCallback的方法里.
在大多数情况下, 你应该把LicenseCheckerCallback的实现作为一个内部类定义在你的main Activity中.
根据需要实现allow()和dontAllow(). 首先, 你可以在方法里只进行简单的结果处理, 例如在对话框里显示签名的检查结果. 这可以帮助你更快的运行你的应用而且有利于调试. 然后, 当你决定你想要其他的行为之后就可以添加更复杂处理.
这里是一些在dontAllow()里处理没有验证结果的建议:
如果reason是Policy.RETRY, 显示一个”重试”的对话框给用户, 包含一个按钮可以创建一个新的签名认证请求
显示”购买这个应用”的对话框, 包含一个按钮可以引导用户到Google Play上可以购买这个应用的详情页. 有关于设置这种链接的的信息, 见 Linking to Your Products
显示一个Toast通知表明这个应用是有限的, 因为它没有认证通过
下面是LVL中实现LicenseCheckerCallback的例子, 在方法中使用对话框显示签名检查结果.
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));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 is not licensed to use this app.
// Your response should always inform the user that the application
// is not 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让你的应用处理无法重试的错误. 错误列表见Licensing Reference的Server Response Codes. 你可以在任何你需要的地方实现这个方法. 在大多数情况下, 这个方法应该记录错误并调用dontAllow().
创建一个从LicenseCheckerCallback 到UI的Handler线程
在签名检查的时候, LVL会传递一个请求到Google Play, 来处理和签名验证服务器的通信. LVL在异步IPC(使用Binder)上传递请求 , 所以实际处理和网络通信不在你应用的同一个线程中. 同样的, 当Google Play接收到结果时, 它在IPC里调用回调方法, 在你应用进程的IPC线程池里轮流执行 .
LicenseChecker通过Google Play管理你应用的IPC通信, 包括调用发送请求和接收响应的回调函数的方法. LicenseChecker会跟踪打开的签名请求并管理它们的超时设定.
所以它可以适当的处理超时也可以处理传进来的响应, 而不会影响你的UI线程, LicenseChecker实例化的时候会产生一个后台线程. 它会在线程里处理所有的签名检查结果, 无论是是从服务器接收到的响应结果还是超时错误. LVL最后都会从后台线程调用你的LicenseCheckerCallback.
对你的应用来说, 这意味着:
在大多数情况下, 你的LicenseCheckerCallback里的方法会被后台线程触发
这些方法不会修改状态或在UI线程里调用任何处理方法, 除非你在UI线程里创建一个Handler并且把你的回调方法传进去.
如果你想让LicenseCheckerCallback里的方法更新UI线程, 如下所示, 在main Activity里的onCreate()里创建Handler. 在这个例子里, LVL示例应用的LicenseCheckerCallback方法(见上文)调用displayResult()通过Handler的post()更新UI线程.
private Handler mHandler;
1
2
3
4
5
@Override
public void onCreate(Bundle savedInstanceState) {
...
mHandler = new Handler();
}
然后, 在LicenseCheckerCallback方法里, 你可以使用Handler方法传递Runnable或Message对象到Handler对象. 这里是LVL里的例子, 在UI线程里传递Runnable到Handler显示签名状态.
private void displayResult(final String result) {
mHandler.post(new Runnable() {
public void run() {
mStatusText.setText(result);
setProgressBarIndeterminateVisibility(false);
mCheckLicenseButton.setEnabled(true);
}
});
}
初始化LicenseChecker和LicenseCheckerCall -back
在main Activity的onCreate()里, 创建LicenseChecker和LicenseCheckerCallback的私有实例对象. 你必须先实例化LicenseCheckerCallback, 因为你调用LicenseChecker的构造函数的时候需要传递LicenseCheckerCallback的引用进去.
当你实例化LicenseChecker的时候, 你需要传入这些参数:
Context
用于签名检查的Policy实例引用. 在大多数情况下, 你可以使用LVL提供的默认Policy实例, ServerManagedPolicy.
用于认证的你公共账号密钥的字符
如果你使用ServerManagedPolicy, 你不需要直接访问类, 所以你可以像下面的例子里一样在LicenseChecker的构造函数里初始化它. 注意你在构造ServerManagedPolicy的时候需要传入一个Obfuscator实例的引用.
下面是在Activity的onCreate()里初始化LicenseChecker和LicenseCheckerCallback的例子.
public class MainActivity extends Activity {
...
private LicenseCheckerCallback mLicenseCheckerCallback;
private LicenseChecker mChecker;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
// Construct the LicenseCheckerCallback. The library calls this when done.
mLicenseCheckerCallback = new MyLicenseCheckerCallback();
// Construct the LicenseChecker with a Policy.
mChecker = new LicenseChecker(
this, new ServerManagedPolicy(this,
new AESObfuscator(SALT, getPackageName(), deviceId)),
BASE64_PUBLIC_KEY // Your public licensing key.
);
...
}
}
注意只有在本地的缓存里有有效的签名响应数据的时候, LicenseChecker才会在UI线程里调用LicenseCheckerCallback的回调方法. 如果发送了签名检查请求到服务器, 回调方法总会被调用, 甚至在网络错误的时候也会调用.
调用checkAccess()初始化签名检查
在main Activity里, 增加调用LicenseChecker实例的checkAccess()的方法. 在这个方法里, 传入LicenseCheckerCallback实例的引用作为作为参数. 如果你需要在调用处理任何特殊的UI效果或状态管理, 你可以在checkAccess()的封装方法里有效的处理. 下面是LVL里从doCheck()封装方法里调用checkAccess()的例子:
public class MainActivity extends Activity {
...
private LicenseCheckerCallback mLicenseCheckerCallback;
private LicenseChecker mChecker;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
// Construct the LicenseCheckerCallback. The library calls this when done.
mLicenseCheckerCallback = new MyLicenseCheckerCallback();
// Construct the LicenseChecker with a Policy.
mChecker = new LicenseChecker(
this, new ServerManagedPolicy(this,
new AESObfuscator(SALT, getPackageName(), deviceId)),
BASE64_PUBLIC_KEY // Your public licensing key.
);
...
}
}
为签名认证植入你的密钥
对每一个开发者账号, Google Play是自动生成只用于签名检查的2048位的RSA公有/私有密钥. 密钥和开发者的账号是唯一关联的, 并且通过这个账号发布的应用都共享这一个密钥. 虽然密钥和开发者账号关联, 但是它和你登录应用(或它的衍生产品)所用的不一样.
Google Play 开发者网站公开用于签名的公共密钥授权给任何开发者, 但是它保持所有用户的私有密钥在一个安全的地方. 当一个应用为你账号发布的应用请求签名认证, 签名认证服务器会标识响应结果是使用的私有密钥. 当LVL接收到响应的时候, 它会使用应用提供的公共密钥去验证响应结果的签名.
要在应用中添加签名, 你必须获得你开发账号的公钥并复制到你的应用. 下面是怎么获取你账号的公钥:
1.登录Google Play开发者网站. 确保你登录账号上的应用已发布(或将发布).
2.在账号首页, 查找”编辑属性”(Edit profile)链接并点击
3.在编辑属性(Edit profile)页面, 查找”签名”(Licensing)面板. 用于签名的公钥在文本框”公钥”(Public Key)里.
添加公钥到你的应用, 只要在文本框里复制到你的应用里, 赋值给你String 属性BASE64_PUBLIC_KEY. 当你复制的时候确保你选择了完整密钥, 不要遗漏了字符.
示例:
public class MainActivity extends Activity {
private static final String BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... "; //truncated for this example
...
}
关闭IPC连接的时候调用LicenseChecker的onDestory()方法
最后, 让LVL在你的应用Context改变的时候清空, 在Activity的onDesctory()实现里增加一个调用LicenseChecker的onDestory()的方法. 这会让LicenseChecker适时的关闭任何连接到Google Play 应用的IliecensingService的IPC连接, 并且删除任何service和handler的引用.
调用LicenseChecker的onDestory()失败会导致你的应用在生命周期内出现问题. 例如, 当用户在签名检查的时候改变屏幕方向, 应用的Context会被销毁. 如果你的应用不能适时的关闭LicenseChecker的IPC连接, 你用的应用会在接收到响应的时候崩溃. 类似的, 如果用户在签名检查的时候退出应用会导致应用在接收到响应的时候崩溃, 除非你在断开连接的时候正确的调用LicenseChecker的onDestory().
下面是LVL里的例子, mChecker是LicenseChecker的实例:
@Override
protected void onDestroy() {
super.onDestroy();
mChecker.onDestroy();
...
}
如果你继承或修改LicenseChecker, 你可能还需要调用LicenseChecker的finishCheck()清理所有IPC连接.
实现DeviceLimiter
有些时候, 你可能希望你的Policy限制一些实际的设备允许使用单一的签名. 这可以防止用户将签名的应用移动到其他设备上并且在这些设备上通过同一个账号使用应用 . 它还可以防止用户通过提供账号关联的签名给其他人共享应用, 他们随后就可以在他们的设备上登录账号并授权给应用.
LVL支持通过DeviceLimiter接口给每台定义了方法allowDeviceAccess()的设备签名. 当LicenseValidator处理来自签名服务器的响应数据时候, 它会调用allowDeviceAccess(), 把响应数据中的用户ID传进去.
如果你不行限定设备, 这不是必需的工作——LicenseChecker类会自动使用默认的实现NullDeviceLimiter. 顾名思义, NullDeviceLimiter是一个”无操作”的类, 它的allowDeviceAccess()方法对所有用户和设备只会简单的返回LICENSED.
有些时候, 你可能希望你的Policy限制一些实际的设备允许使用单一的签名. 这可以防止用户将签名的应用移动到其他设备上并且在这些设备上通过同一个账号使用应用 . 它还可以防止用户通过提供账号关联的签名给其他人共享应用, 他们随后就可以在他们的设备上登录账号并授权给应用.
LVL支持通过DeviceLimiter接口给每台定义了方法allowDeviceAccess()的设备签名. 当LicenseValidator处理来自签名服务器的响应数据时候, 它会调用allowDeviceAccess(), 把响应数据中的用户ID传进去.
如果你不行限定设备, 这不是必需的工作——LicenseChecker类会自动使用默认的实现NullDeviceLimiter. 顾名思义, NullDeviceLimiter是一个”无操作”的类, 它的allowDeviceAccess()方法对所有用户和设备只会简单的返回LICENSED.
警告: Per-device签名对大多数应用不建议使用, 因为:
它需要你提供后端服务器来管理用户和设备的映射
它可能在不经意间导致用户在另一个设备上无法访问他们合法购买的应用
混淆你的代码
确保你的应用安全, 特别对于使用签名和/或自定义的约束和保护的付费应用来说, 混淆你的应用程序的代码是非常重要的. 适当的混淆你应用的代码可以让恶意用户反编译你的应用程序字节码变得非常困难, 修改它——例如移除签名检查的部分, 然后重新编译.
大多数混淆处理程序对android应用都是有效的, 包括ProGuard也提供了代码优化的特性. 强烈推荐使用了Google Play Licensing的应用使用ProGuard或类似的程序混淆你的代码.
发布签名过的应用
你测试完你的签名之后, 就可以准备在Google Play上发布你的应用了. 按照正常的步骤准备、登录, 然后发布你的应用.
删除Copy Protection
当你上传签名过的应用之后, 记得在应用里删除copy protection如果它现在在使用. 登录开发者网站进入应用上传的详情页, 检查并删除copy protection. 在发布(Publishing)选项部分, 确保Copy Protection单选按钮选项是”关闭”(Off)状态.
获得支持
如果你在实现或部署发布你的应用的时候有问题或遇到问题, 请使用下面表格中的支持资源列表. 通过指导你查询正确的论坛, 你可以更快速的获得你需要的支持.
表2 Google Play 授权服务的开发者支持资源
{|style="border-spacing: 0px;margin: 4px 4px; width: 90%; border-left:1px solid #ccc;border-top:1px solid #ccc; "
|-style="background:#DEE8F1; "
! style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px" | 支持类型
! style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px" | 资源
! style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px" | 主体范围
|- style=" vertical-align:top;"
| style=" border-right:1px solid #ccc;border-bottom:0px solid #ccc; padding:5px 15px; " |
开发和测试问题
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
Google 群组: android-developers
| style="border-right:1px solid #ccc;border-bottom:0px solid #ccc; padding:5px 15px; " |
LVL 下载和集成, lib项目,Policy 问题, 用户体验建议, 处理响应, Obfuscator, IPC, 测试环境设置
|- style=" vertical-align:top;"
| style=" border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
Stack Overflow:http://stackoverflow.com/questions/tagged/android
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
|- style=" vertical-align:top;"
| style=" border-right:1px solid #ccc;border-bottom:0px solid #ccc; padding:5px 15px; " |
账号,发布和部署问题
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
Google Play 帮助论坛
| style="border-right:1px solid #ccc;border-bottom:0px solid #ccc; padding:5px 15px; " |
开发者账号, 签名密钥, 测试账号, 服务器响应, 测试响应, 应用部署和结果
|- style=" vertical-align:top;"
| style=" border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
授权常见问题
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
|- style=" vertical-align:top;"
| style=" border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
LVL 问题跟踪
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
Marketlicensing project 问题跟踪
| style="border-right:1px solid #ccc;border-bottom:1px solid #ccc; padding:5px 15px; " |
LVL源码和接口实现的Bug和问题报告
|}
怎么把一般信息发送到上面的群组列表里去, 见Resource标签页的 Developer Forums文档