账号系统建立 Account Authenticator 如果只需要借助系统更新服务(SyncAdapter)来做定期维护, 那么通过前两部分的介绍, 已经可以达到所预期的目标了.
本期话题将会解决
这些服务全部都是可以跨进程的操作, 完成了这些操作, 我们就可以完成像QQ
新浪微博
一样的功能, 账号系统可以为第三方应用授权.
实现AccountService 以及 Authenticator 先看看所提供的重写方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public class AccountService extends Service { private Authenticator authenticator; public static final String ACCOUNT_TYPE = "org.weyoung.notebook" ; public static final String TOKEN_TYPE = "org.weyoung.notebook.admin" ; @Override public void onCreate () { authenticator = new Authenticator(this ); } @Override public IBinder onBind (Intent intent) { return authenticator.getIBinder(); } class Authenticator extends AbstractAccountAuthenticator { private Context context; private AccountManager accountManager; public Authenticator (Context context) { super (context); this .context = context; accountManager = AccountManager.get(context); } @Override public Bundle editProperties (AccountAuthenticatorResponse accountAuthenticatorResponse, String s) { throw new UnsupportedOperationException(); } @Override public Bundle addAccount (AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { } @Override public Bundle confirmCredentials (AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, Bundle bundle) throws NetworkErrorException { return null ; } @Override public Bundle getAuthToken (AccountAuthenticatorResponse response, Account account, String s, Bundle bundle) throws NetworkErrorException { } @Override public String getAuthTokenLabel (String s) { throw new UnsupportedOperationException(); } @Override public Bundle updateCredentials (AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String s, Bundle bundle) throws NetworkErrorException { throw new UnsupportedOperationException(); } @Override public Bundle hasFeatures (AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String[] strings) throws NetworkErrorException { throw new UnsupportedOperationException(); } } }
通过Authenticator
所提供的方法可以看出, 最核心的就是实现addAccount
与getAuthToken
方法, 这样我们的AccountService
就可以获得授权 与添加账号 功能了
添加账号 先看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 addAccount() { final Bundle bundle = new Bundle(); if (accountManager.getAccountsByType(ACCOUNT_TYPE).length > 0 ) { final Intent intent = new Intent(context, FailActivity.class); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); bundle.putParcelable(AccountManager.KEY_INTENT, intent); return bundle; } final Intent intent = new Intent(context, AuthenticatorActivity.class); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); bundle.putParcelable(AccountManager.KEY_INTENT, intent); return bundle; }
这里返回的addAccount
返回的Bundle必须带有一个Key为AccountManager.KEY_INTENT , 并且这个Intent
含有一个Key为AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE
, Value为参数AccountAuthenticatorResponse
. 这个response主要用来回调成功与否, 内部还可以传递生成账号的信息(Name,Type,Token), 将会在启动的用于注册的AuthenticatorActivity
里使用.
关于AuthenticatorActivity
的实现, 其实很简单, 其继承AccountAuthenticatorActivity
, 这样父类自己帮你在onCreate
的时候获取到了之前传入的AccountAuthenticatorResponse
, 然后在注册成功后调用setAccountAuthenticatorResult
来传出一个Bundle, 内部其实调用了前面所指的response对象的onResult
或者onError
方法.
这个Bundle可以含有若干重要的字段, AccountManager.KEY_ACCOUNT_NAME
AccountManager.KEY_ACCOUNT_TYPE
AccountManager.KEY_AUTHTOKEN
下面看看AuthenticatorActivity
的核心实现吧.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 submit() { final Account account = new Account(name, AccountService.ACCOUNT_TYPE); ......认证 生成token 的逻辑 accountManager.addAccountExplicitly(account, password, null ); accountManager.setAuthToken(account, AccountService.TOKEN_TYPE, authToken); Bundle data = new Bundle(); data.putString(AccountManager.KEY_ACCOUNT_NAME, name); data.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountService.ACCOUNT_TYPE); data.putString(AccountManager.KEY_AUTHTOKEN, authToken); Intent intent = new Intent(); intent.putExtras(data); setAccountAuthenticatorResult(data); finish();
获得认证 先看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 getAuthToken() { String authToken = accountManager.peekAuthToken(account, TOKEN_TYPE); if (TextUtils.isEmpty(authToken)) { final String password = accountManager.getPassword(account); if (password != null ) { try { authToken = 生成token的逻辑; } catch (Exception e) { e.printStackTrace(); } } } if (!TextUtils.isEmpty(authToken)) { final Bundle result = new Bundle(); result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); result.putString(AccountManager.KEY_AUTHTOKEN, authToken); return result; } final Intent intent = new Intent(context, AuthenticatorActivity.class); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_NAME, account.name); final Bundle b = new Bundle(); b.putParcelable(AccountManager.KEY_INTENT, intent); return b; }
由上面的代码可以看见, 对于需要获取token(getAuthToken)的操作, 我们的步骤是从简到繁, 最差情况就是需要重新的登录认证来获得token
跟添加账号相同, 最终账号的信息(Name Type Token)都是通过神奇的Bundle
来传递的
####声明账号服务
与同步服务一样, 账号服务同样需要声明, 以便通过系统的Accout功能操作, 或者为其他第三方程序提供支持.
1 2 3 4 5 6 7 8 9 10 11 <service android:name =".sync.AccountService" android:exported ="true" android:process =":auth" > <intent-filter > <action android:name ="android.accounts.AccountAuthenticator" /> </intent-filter > <meta-data android:name ="android.accounts.AccountAuthenticator" android:resource ="@xml/authenticator" /> </service >
这里跟上一节 的SyncService
类似, 必须声明为exported
, process
不是必须的, 并且需要添加android
约定的过滤器android.accounts.AccountAuthenticator
关于Authenticator
的配置, 也类似的有一个独立的xml.
1 2 3 4 5 6 <account-authenticator xmlns:android ="http://schemas.android.com/apk/res/android" android:accountType ="org.weyoung.notebook" android:icon ="@drawable/ic_launcher" android:smallIcon ="@drawable/ic_launcher" android:label ="@string/app_name" />
这里声明的label
与icon
都会在系统设置Account里面看到, 如上一节 最后面图片的上部分所示.
使用账号服务 构建好了自己的Service跟账号认证Adapter, 下面就剩如何使用了. 使用大致分为两种, 一种是直接申请Token, 一种是添加账号. 当然如果没申请到, 结果跟后面一种类似.
所有的操作都来自于AccountManager
,申请Token主要是通过AccountManager#getAuthToken 系列方法. 这里使用了适合第三方应用使用的AccountManager#getAuthTokenByFeatures , 通过账号类型与Token类型来申请.
添加账号则通过AccountManager#addAccount , 添加指定类型的账号.
当然先通过AccountManager#getAccountsByType )查看是否存在账号, 决定是否是添加, 还是获取
当然这些操作都需要一定的权限声明, 如下所示
1 2 3 4 5 <uses-permission android:name ="android.permission.USE_CREDENTIALS" /> <uses-permission android:name ="android.permission.GET_ACCOUNTS" /> <uses-permission android:name ="android.permission.MANAGE_ACCOUNTS" />
本文通过三次的介绍, 应该可以为想构建自己的账号同步系统的同学指明了道路, 所有代码中间可能做过稍许节选, 添加了中文的注释, 并有一定的伪代码. 整个程序的代码可以通过这里 来获得, 进一步了解整个功能的实现.
春节将至, 祝快乐
Reference: