关于账号存储的处理,每个不同的APP都有着不同的处理方式。最为常见的就是使用数据库作为账号管理。使用本地SQLite存储账号的用户名,头像,Token等关于账号的一系列信息。但是,因为数据存储在本地,或许有心术不正的人获取到本地存储的数据库信息(当然了,本地存储的数据库是很容易拿到的),就能够拿到账号的所有信息。信息就这样泄漏了。
另外一种比较安全的方法就是使用Android系统提供的AccountManager存储账号相关信息。So,google一下发现,国内似乎好像很少有关于这个类的介绍,只查到了一篇翻译国外的一篇文章。所有我想把AccountManager的介绍以及如何使用AccountManager,还有使用过程中需要注意的地方。
附:(翻译) Android Accounts Api使用指南
下边就先介绍一下相关的类
1.Accout介绍
Accont其实就是我们需要添加的账号,里边只有两个字段。
其中name表示该账户的用户名,当然了,有很多应用它的用户名是隐藏的,你也可以把它作为昵称来看待。type则代表账户的类型,这个是type必须是独一无二的,当然了,如果说你的两个应用使用的是同一个账户管理体系,这个是可以相同的。
通过传递name和type完成对Account的创建。这个时候可能会疑问,不是说好的存储呢token呢,还有头像的信息,还有其他一些数据呢?其实,本身Account的本身是不存储这样的东西的,如果想存储关于账号的其他数据,需要通过AccountManager.addAccountExplicitly(account, password, userData)
的方式来添加用户的附加信息的。其中userData是一个Bundle类型的数据,通过bundle.put(key,object)
的方式添加用户所需要的附加数据。关于AccountManager的介绍及常用方法,接下来会详细讲解。
2.AccountManager介绍
关于AccountManager类,Android官方的开发文档是这么介绍的
This class provides access to a centralized registry of the user’s online accounts. The user enters credentials (username and password) once per account, granting applications access to online resources with “one-click” approval.
简单的来翻译一下吧
这个类提供了访问用户的网上帐户的统一的管理。每个用户只需要要输入一次凭据(用户名和密码),一键式的授予应用程序访问网络资源。(英语不好,如有错误,请指正)。
通俗的来说,可以使用这个类来完成对账号的增、删、改、查。
3.添加一个账户信息
刚才上文中也已经提到了,就是使用AccountManager.addAccountExplicitly(account, password, userData)
的方式进行添加的。看一下这个方法的源码。
其中IAccountManager
是一个aidl接口类,不难猜测出其中的具体添加账户的服务由Android系统底层服务添加,很抱歉,我在翻阅了大量的资料后,并没有找到相关的源码。不过,通过这样一个方法就可以添加一个账户进入系统的账户。
4.删除一个账户
删除账户有对应的方法在API21前后略有不同,在API21以前采用的是removeAccount(Account account, AccountManagerCallback<Boolean> callback, Handler handler)
,而在API21之后则改成了removeAccount(Account account, Activity activity, AccountManagerCallback<Bundle> callback, Handler handler)
,另外还有一个方法是removeAccountExplicitly(Account account)
其中前两个是异步方式进行的,最后一个方法看官网的方法描述Removes an account directly.(直接删除账户)
(目前我使用的是API22以前异步的方法,其他的两个方法暂时未进行测试,欢迎各路大神测试吐槽)。
关于三个参数介绍:
Account
:表示带移除的账户,看源码之后发现,当account传入null
的时候,会抛出一个异常,所以要确保出传入的account不能为nullAccountManagerCallback<Boolean>
:表示移除完成后的回调,通过future.getResult()
获取回调的返回结果Handler
:对应回调的线程,当然了,如果你传入为null
的话,他的默认回调线程则是为主线程
对于这个方法,我是这样使用的。public static void removeCurrentAccount() {Account account = getCurrentAccount();if (account == null)return;manager.removeAccount(account, new AccountManagerCallback<Boolean>() {public void run(AccountManagerFuture<Boolean> future) {try {Logger.i("移除账号" + future.getResult());//send EventBus or BroaderCast or RxBusRxBus.getDefault().send(new AccountSignStatus(false));} catch (OperationCanceledException e) {e.printStackTrace();} catch (AuthenticatorException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}}, null);
以上两个是比较基础的添加和删除账号信息,剩下的获取账号信息的部分还有修改账号用户名(昵称)我将会用超长的篇幅类介绍和使用关于这些方法的相信使用,因为这些方法是相对来说调用比较频繁的,特别是获取账户信息的方法,同时,在使用这些方法时,我遇到的坑还是相当多的。而同时,在获取账号token也是整个账户管理的核心,同时也是最复杂的。
5.获取用户token信息及用户数据
先简单介绍一下获取token的流程,下面是关于获取账户token的流程示意图
这看起来似乎是一团乱麻,但是其实相当的简单。我会解释一下当我们第一次在设备上用一个账号登入时出现的情况。
第一次登陆
- app向AccountManager请求一个AuthToken。
- AccountManager询问相关的AccountAuthenticiator是否有缓存的AuthToken。
- 因为没有缓存的AuthToken(还没有登入),AccountManager为我们开启一个AccountAuthenticatorActivity,引导用户登入。
- 用户成功登陆,AuthToken从服务端返回。
- Accountmanager将AuthToken缓存起来以便于将来使用。
- app获得她请求的AuthToken.
- 众人皆喜!
如果用户已经登入,那么我们就会在第二步时获得AuthToken。(摘抄(翻译) Android Accounts Api使用指南原文)
看起来是很复杂的事情,不过我会一步一步来解释并实现这样的一个过程。
####第一步 创建属于自己的账号服务
首先我需要编写出一个Authenticator
认证器的类,让这个类继承AbstractAccountAuthenticator
,并实现其中的方法。具体的实现会在接下来进行介绍。
然后创建一个服务类,并在mainifist如下代码所示
第三步,在res文件夹下创建xml文件夹,并添加authenticator.xml
文件
最后配置mainifest清单文件中添加
经过以上的几个步骤,运行APP就可以在Android的设置界面点击添加账户看到定义的账号名称了。类似于这样。(补充一点,某系机型可能看不到,例如说某米,某族等)
OK,上文也提到了,会对实现的Authenticator
进行详解。其实我们主要关注的就是其中的两个方法,一个是addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)
,另外一个就是getAuthToken(final AccountAuthenticatorResponse response, final Account account, String authTokenType, Bundle options)
第一个是在设置界面点击添加账户是跳转到登录页的方法,另外一个就是使用AccountManager.getAuthToken
调用的方法。
对于addAccount方法我的实现是这样的
SignAcitiviy需要继承AccountAuthenticatorActivity,当然了如果你项目中与BaseActivity的时候,你讲AccountAuthenticatorActivity中的相关变量和方法复制到SignAcitiviy也是可以的。 具体的SignAcitivity实现请参考本文头部提到的原文
对于getAuthToken我的实现是这样的
Ok,经过以上的代码编写,你可以使用
和
两个方法了(妈蛋!需要传递这么多的参数,到底鬼什么意思?)
先来介绍一下getAuthToken
这个方法.
根据官方文档的解释
Gets an auth token of the specified type for a particular account, prompting the user for credentials if necessary. This method is intended for applications running in the foreground where it makes sense to ask the user directly for a password.
If a previously generated auth token is cached for this account and type, then it is returned. Otherwise, if a saved password is available, it is sent to the server to generate a new auth token. Otherwise, the user is prompted to enter a password.
官方文档的解释也印证了上边提到的当获取AuthToken
的流程(如果忘记了,请返回上文仔细观看)。实际上这也是在Authenticator
中getAuthToken
的流程逻辑。
最后解释一下传递参数的详细解释
参数名称 | 参数释义 |
---|---|
account | 需指定的账户 |
authTokenType | 账号token的类别,不是账号的类别并且不可以为null |
options | 身份认证专用的请求选项可以为null |
activity | 不解释 |
callback | 调用完成时的回调,如果为null则不回掉 |
handle | 回调的线程,如果传null则回调为主线程 |
接下来介绍一下getAuthTokenByFeatures
这个方法。还是先看一下官方文档
This method gets a list of the accounts matching the specified type and feature set; if there is exactly one, it is used; if there are more than one, the user is prompted to pick one; if there are none, the user is prompted to add one. Finally, an auth token is acquired for the chosen account.
翻译一下就是,这个方法会根据传递的账户类型匹配是否有该类型的账户,如果恰好有一个,则返回改账户的token,如果有多个,则弹出一个对话框让你选择一个,如果没有,会让你新建一个指定类型的账户,最后返回这个账户的token。
其实toke的获取流程还是和getAuthToken
的流程相同。唯一的一点区别在于getAuthToken
需要传递的是一个已知的账户,而getAuthTokenByFeatures
传递的是一个账户类型,由系统帮我们选定账户。
最后还是列举一下传递参数的意思
参数名称 | 参数释义 |
---|---|
accountType | 账户类型 |
authTokenType | 账户token类型 |
features | 该账户需要授权的功能,可以为null |
activity | 不解释 |
addAccountOptions | 身份认证专用的请求选项可以为null 用于添加账户时使用 |
getAuthTokenOptions | 身份认证专用的请求选项可以为null 用于获取账户时使用 |
callback | 调用完成时的回调,如果为null则不回掉 |
handle | 回调的线程,如果传null则回调为主线程 |
上述就是获取账户信息Token的常用方法。下面就来介绍一下获取账户数据的方法,就是像获取用户头像信息等。
获取用户数据比较上来说就简单多了。直接调用manager.getUserData(Account account, String key)
就可以了。这个方法是同步获取的,相比上述获取token的方式简单太多了。参数释义太简单了不再一一赘述。
6.更改用户信息
先解释最简单吧,修改用户数据,也就是修改用户的头像信息这些东西了。调用manager.setUserData(Account account, String key,String value);
就可以了。具体参数释义不解释了。
其实最坑的一点就是修改账号的昵称(用户名)。闲话不多说,先上代码。
代码的逻辑解释一下,当在API21之后使用manager.renameAccount
的方法直接重名昵称(用户名),在API21之前,先把用户数据存储下来,移除账户后添加一个新名字的账户。(目前我是这么做的,希望有更好的方法大神指点)
这么做会遇到一个问题,就是当新名字和旧的名字是相同的时候。出现一个奇怪的现象,重命名成功后,账户列表不显示账户,并且这个账号永久性的添加不成功,只有当卸载APP之后才可以。所有目前的做法是如果是相同昵称,直接返回重命名成功就好了。
7.总结需要注意的几点。
- 不同的账户体系,accounttype千万不要相同。
- 在AccountManager存储token的时候也是以明文进行存储的,保险起见,加一下密。
- 注意重命名昵称的时候不要和旧名字相同。
- 关于账号数据服务器返回数据的能存的尽量存下来,以备后续版本可能会用到。
关于这篇介绍AccountManager,因为本人水平有限,在介绍的时候难免会有错误,欢迎各路大神指正。
Created By Lisao
2016-8-22