为什么要使用AccountManager和AccountAuthenticator
建立账户管理系统,可以使用SharedPreference来存储、更新、删除AuthToken,也可以用来存储、更新、删除账户和密码。既然这样,为什么还要使用AccountManager和AccountAuthenticator?
原因有一下几点:
- AccountManager是Android系统提供的账户管理框架,十分强大和方便,可以管理不同类型账户,不同类型AuthenToken
- AccountAuthenticator把账户的验证过程、AuthToken的获取过程分离出来,降低程序的耦合性
- 使用AccountAuthenticator会在”设置”中添加一个账户入口,感觉很酷炫。
AccountManager和Authenticator之间的关系
AccountManager和Authenticator的方法相对应,比如,AccountManager的addAccount()方法会调用Authenticator的addAccount()方法,Authenticator的方法会返回一个Bundle给AccountManager处理。具体细节后面会介绍。
使用AccountManager管理账号
顾名思义,AccountManager是用来管理用户账户。使用AccountManager有两个要点:Bundle key常量的含义、如何使用账户管理方法。
AccountManager定义了许多Bundle Key常量,可以用作Intent的key用于Activity之间传递参数;也可以作为Authenticator返回Bundle的Key,这种情况,AccountManager会根据Key的情况作出相应操作,比如,跳转到验证身份页面,返回Accont Type,AuthToken,Account Name(getAuthenToken()有返回AuthToken时必须返回Account Type和Account Name,不然会提示“the type and name should not be empty”)等。
介绍一些常用Bundle key:
|Key |含义|
|— |—-|
|AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE |可以调用onResult()和onError()来相应用户提供的信息|
|AccountManager.KEY_INTENT |开启新Activity和用户进行交互|
|AccountManager.KEY_AUTHTOKEN |令牌|
|AccountManager.KEY_ACCOUNT_TYPE |账户类型|
|AccountManager.KEY_ACCOUNT_NAME |账户名|
|AccountManager.KEY_ERROR_CODE |错误码(必须大于0)|
|AccountManager.KEY_ERROR_MESSAGE |错误信息(必须和错误码一起使用)|
介绍一些常用AccountManager管理账户方法:
获取AuthToken:
1 | account 账户 |
获取账户列表:
1 | type 账户列表类型 |
从AccountManager的缓存中移除AuthToken:
1 | accountType 账户类型 |
获取密码:
1 | account 账户 |
接下来是重点,创建自己的Authenticator,用来验证用户信息或者与从服务器获取信息。
创建Authenticator
步骤:
- 继承AbstractAcccountAuthenticator
- 重写addAccount(),getAuthenToken()方法。如果有需要还可以重写其他方法
重写addAccount()方法:
1 |
|
重写getAuthenToken()方法,要注意,如果之前成功获取过AuthenToken会缓存,之后不会在调用getAuthenToken()方法,除非调用invalidateAuthenToken():
1 |
|
3.为Authenticator创建Service
1 | public class AuthenticatorService extends Service{ |
4.为了把Authenticator组件添加到账户管理框架,需要添加Metadata文件描述组件,在res/xml/目录下声明组件
1 | <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" |
accountType很重要,用来唯一标识Authenticator,AccountManager的方法中有accountType的参数需要和此处保持一致。
5.在Manifest文件声明之前创建的Service
1 | <service android:name=".AuthenticatorService"> |
源码分析
从源码角度分析AccountManager和Authenticator之间的关系,主要介绍AccountManager的addAccount()和getAuthToken()方法。
首先分析addAccount(),addAccount()源码如下。
1 | /* @param accountType The type of account to add; must not be null |
新建了个AmsTask实例,重写了dowork()方法,
doWrok()中AccountManagerService实例调用了addAccount()方法,有个参数mResponse是AmTask实例的成员变量。先看下AmsTask主要实现。
1 | private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> { |
AmsTask继承FutureTask,当setException(Throwable t)被调用后,调用get()方法会抛出该异常。
AccountManagerService的addAccount()方法的主要实现如下。
1 |
|
看到重点了,Session实例重写run()方法中调用了Authenticator的addAccount()方法,并且绑定Authenticator的Service。接下来看看Authenticator的addAccount()方法(并不是我们重写的addAccount()方法)。
1 | public void addAccount(IAccountAuthenticatorResponse response, String accountType,String authTokenType, String[] features, Bundle options) |
这里才正真调用了我们重写的addAccount()方法,会返回Bundle,Session的onResult()方法会先处理Bundle,满足条件(mExpectActivityLaunch && result != null&& result.containsKey(AccountManager.KEY_INTENT))还可能调用response的onError()或onResult方法,这里的onError()方法和onResult()方法是IAccountManagerResponse.Stub实现的方法。先看看Session的onResult()方法。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
73
74
75
76
77
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
mNumResults++;
Intent intent = null;
if (result != null&& (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
checkKeyIntent(Binder.getCallingUid(),intent);
// Omit passwords if the caller isn't permitted to see them.
if (!mIsPasswordForwardingAllowed) {
result.remove(AccountManager.KEY_PASSWORD);
}
}
IAccountManagerResponse response;
if (mExpectActivityLaunch && result != null
&& result.containsKey(AccountManager.KEY_INTENT)) {
response = mResponse;
} else {
response = getResponseAndClose();
}
if (response == null) {
return;
}
if (result == null) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, getClass().getSimpleName() + " calling onError() on response "+ response);
}
sendErrorResponse(response,AccountManager.ERROR_CODE_INVALID_RESPONSE,"null bundle returned");
return;
}
if ((result.getInt(AccountManager.KEY_ERROR_CODE, -1) > 0) && (intent == null)) {
// All AccountManager error codes are greater
// than 0
sendErrorResponse(response,result.getInt(AccountManager.KEY_ERROR_CODE),result.getString(AccountManager.KEY_ERROR_MESSAGE));
return;
}
// Strip auth token from result.
result.remove(AccountManager.KEY_AUTHTOKEN);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG,getClass().getSimpleName() + " calling onResult() on response " + response);
}
// Get the session bundle created by authenticator. The
// bundle contains data necessary for finishing the session
// later. The session bundle will be encrypted here and
// decrypted later when trying to finish the session.
Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
if (sessionBundle != null) {
String accountType = sessionBundle.getString(AccountManager.KEY_ACCOUNT_TYPE);
if (TextUtils.isEmpty(accountType)
|| !mAccountType.equalsIgnoreCase(accountType)) {
Log.w(TAG, "Account type in session bundle doesn't match request.");
}
// Add accountType info to session bundle. This will
// override any value set by authenticator.
sessionBundle.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccountType);
// Encrypt session bundle before returning to caller.
try {
CryptoHelper cryptoHelper = CryptoHelper.getInstance();
Bundle encryptedBundle = cryptoHelper.encryptBundle(sessionBundle);
result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, encryptedBundle);
} catch (GeneralSecurityException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.v(TAG, "Failed to encrypt session bundle!", e);
}
sendErrorResponse(response, AccountManager.ERROR_CODE_INVALID_RESPONSE,
"failed to encrypt session bundle");
return;
}
}
sendResponse(response, result);
}
}
Session的onResult()方法看到KEY_ERROR_CODE大于0才会setException()。
IAccountManagerResponse.Stub的具体实现。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/** Handles the responses from the AccountManager */
private class Response extends IAccountManagerResponse.Stub {
public void onResult(Bundle bundle) {
Intent intent = bundle.getParcelable(KEY_INTENT);
if (intent != null && mActivity != null) {
// since the user provided an Activity we will silently start intents
// that we see mActivity.startActivity(intent);
// leave the Future running to wait for the real response to this request
} else if (bundle.getBoolean("retry")) {
try {
doWork();
} catch (RemoteException e) {
// this will only happen if the system process is dead, which means
// we will be dying ourselves
}
} else {
set(bundle);
}
}
public void onError(int code, String message) {
if (code == ERROR_CODE_CANCELED ||
code == ERROR_CODE_USER_RESTRICTED
|| code == ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE) {
// the authenticator indicated that this request was canceled or we were
// forbidden to fulfill; cancel now
cancel(true /* mayInterruptIfRunning */);
return;
}
setException(convertErrorToException(code, message));
}
}
addAccount流程如图
[
getAuthToken流程和addAccount流程差不多,可自行分析。
demo地址:
https://github.com/wslaimin/AccountManagerSample.git