文章目录
  1. 1. 为什么需要Account Manager
  2. 2. 实现类
  3. 3. 调用流程
    1. 3.1. 关键代码
  4. 4. 实现
    1. 4.1. Authenticator
    2. 4.2. AccountAuthenticatorService

对用Android的Authenticator,官方的API Guides是似乎没有很好的介绍文档,Reference中也只是简单地介绍了一下。虽然sync-adapters中对于Authenticator有提及,但是对于其用法也没有一个完整的认识,所以对其进行一定的学习并且记录了下来。

为什么需要Account Manager

我们完全可以自己自己的账户管理机制,提交一个登录表单到服务器然后返回一个认证token。但是通常覆盖不到一些细节,如果用户在另外一客户端修改了密码怎么办;认证token的过期处理;单点登录的实现。这些东西实现起来还是挺麻烦的,但是如果Android的认证框架已经实现了这些功能,并且额外提供了用户数据同步等额外的功能,那还有理由不使用它吗?Stop Trying to Reinvent the Wheel.

实现类

Account相关的类都位于android.accounts包下,以下是其中几个主要的类。

  • AccountManager
    提供访问所有用户账户的注册中心。 不同的在线服务有不同的处理账户和认证的方式,所以AccountManager对不同的account types使用可插拔的authenticator的模块,由第三方提供的Authenticators处理实际的账户认证细节。

    AccountManager还可以为应用生成auth token,所以应用不必直接处理密码。Auth tokens通常被AccountManager缓存并重复利用。

  • AccountAuthenticator
    上面提到的可插拔的authenticator模块,处理特定的account typesAccountManager通过查找到合适的AccountAuthenticator,与其进行通信来实现对特定的account types的所有操作。

  • AccountAuthenticatorActivity
    当需要验证用户的时候,由authenticator调用,来进行“登录/创建 account”的Activity的基类。

调用流程

以添加账户为例

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public AccountManagerFuture<Bundle> addAccount(final String accountType,
final String authTokenType, final String[] requiredFeatures,
final Bundle addAccountOptions,
final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
if (accountType == null) throw new IllegalArgumentException("accountType is null");
final Bundle optionsIn = new Bundle();
if (addAccountOptions != null) {
optionsIn.putAll(addAccountOptions);
}
optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
return new AmsTask(activity, handler, callback) {
public void doWork() throws RemoteException {
mService.addAccount(mResponse, accountType, authTokenType,
requiredFeatures, activity != null, optionsIn);
}
}.start();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void addAccount(IAccountAuthenticatorResponse response, String accountType,
String authTokenType, String[] features, Bundle options)
throws RemoteException {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "addAccount: accountType " + accountType
+ ", authTokenType " + authTokenType
+ ", features " + (features == null ? "[]" : Arrays.toString(features)));
}
checkBinderPermission();
try {
final Bundle result = ③AbstractAccountAuthenticator.this.addAccount(
new AccountAuthenticatorResponse(response),
accountType, authTokenType, features, options);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
result.keySet(); // force it to be unparcelled
Log.v(TAG, "addAccount: result " + AccountManager.sanitizeResult(result));
}
if (result != null) {
④response.onResult(result);
}
} catch (Exception e) {
handleException(response, "addAccount", accountType, e);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public void ⑥finish() {
if (mAccountAuthenticatorResponse != null) {
// send the result bundle back if set, otherwise send an error.
if (mResultBundle != null) {
⑦mAccountAuthenticatorResponse.onResult(mResultBundle);
} else {
mAccountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED,
"canceled");
}
mAccountAuthenticatorResponse = null;
}
super.finish();
}
1
2
3
4
5
6
7
8
9
10
11
12
public void onResult(Bundle result) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
result.keySet(); // force it to be unparcelled
Log.v(TAG, "AccountAuthenticatorResponse.onResult: "
+ AccountManager.sanitizeResult(result));
}
try {
⑧mAccountAuthenticatorResponse.onResult(result);
} catch (RemoteException e) {
// this should never happen
}
}

时序图如下
android_autheticator_flow

  1. 客户端调用AccountManager#addAccount方法添加账户。
  2. AccountManager#addAccount方法中会调用AmsTask#start并返回结果,AmsTask#start的主要内容是调用doWork方法。
  3. AmsTask#doWork方法以AIDL方式调用AbstractAccountAuthenticator#addAccount方法。AbstractAccountAuthenticator#addAccount方法需要用户实现其逻辑。实现逻辑中需要告诉框架到哪个Activity进行具体的账户添加动作,因此需要返回一个Bundle包含以AccountManager.KEY_INTENT为key的Intent,Intent中需包含以AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE为key的AccountAuthenticatorResponse
  4. AccountManager#addAccount方法中后记调用AmsTask#Response#onResult方法
  5. AmsTask#Response#onResult逻辑中会判断AbstractAccountAuthenticator#addAccount返回的Bundle中后否包含AccountManager.KEY_INTENT,如果包含则启动对应的Activity,所以#3中的实现方法必须设置AccountManager.KEY_INTENT
  6. 跳转的Activity需要继承AccountAuthenticatorActivity,该类的实现逻辑中可以调用AccountManager#addAccountExplicitly添加账户,AccountManager#setPassword更新密码,AccountManager#setAuthToken设置token等等。最终需要调用AccountAuthenticatorActivity#setAccountAuthenticatorResult方法设置返回结果并调用AccountAuthenticatorActivity#finish结束当前的Activity
  7. AccountAuthenticatorActivity#finish方法会调用AccountAuthenticatorResponse#onResult并传递上部设置的返回结果。此处的AccountAuthenticatorResponse对象正式之前#3设置的AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSEAccountAuthenticatorActivity会在初始化的时候自动获取

    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);
    
        mAccountAuthenticatorResponse =
                getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
    
        if (mAccountAuthenticatorResponse != null) {
            mAccountAuthenticatorResponse.onRequestContinued();
        }
    }
    
  8. AccountAuthenticatorResponse是之前AmsTask#Response的包装类,其onResult实现会调用AmsTask#Response#onResult方法
  9. 由于这次Activity返回的Bundle并没有包含AccountManager.KEY_INTENT,所以会调用AmsTask#set方法
  10. 客户端在#1调用了AccountManager#addAccount会返回实现了AccountManagerFuture接口的AmsTask对象,调用getResult方法可以获取结果,但是线程会阻塞轮询,直至#9完成为止。

当然并不是所有的Account操作都需要启动Activiy进行辅助的。有些逻辑可以直接在AbstractAccountAuthenticator子类方法中直接完成,然后返回一个结果。这些实现类的返回结果方式都是通过Bundle中设置AccountManager中定义的特殊Key来是实现的,例如KEY_ACCOUNTSKEY_BOOLEAN_RESULT等等。这些方法不启动的Activiy,所以AccountManager的实现中是调用的Future2Task而不是AmsTask

实现

Authenticator

Authenticator需要继承AbstractAccountAuthenticator

###其中几个主要的方法

  • addAccount
    添加账号,需要返回Intent跳转到AccountAuthenticatorActivity进行实际的登录

  • confirmCredentials
    确认账号信息,也需要Activity的辅助,可以在一些账户切换的场合使用

  • updateCredentials
    更新账号信息,也需要Activity的辅助,可以在密码变更,token失效的时候调用

  • getAuthToken
    获取用户token,实现逻辑可以参考官方文档对AccountManager#getAuthToken描述:如果之前已经生成了token并缓存,可以通过AccountManager#peekAuthToken返回之前缓存的token,否则,如果通过AccountManager#getPassword能够获取的用户密码,则直接申请token,否则,跳转到登陆页面。
    getAuthToken有三个重载方法:

    public AccountManagerFuture<Bundle> getAuthToken (Account account, String authTokenType, Bundle options, Activity activity, AccountManagerCallback<Bundle> callback, Handler handler)
    

    这个方法适用于应用在前台运行时调用,必要的情况下,会弹出请求用户输入证书

    public AccountManagerFuture<Bundle> getAuthToken (Account account, String authTokenType, boolean notifyAuthFailure, AccountManagerCallback<Bundle> callback, Handler handler)
    

    API level 14已经废弃,使用下面的方法

    public AccountManagerFuture<Bundle> getAuthToken (Account account, String authTokenType, Bundle options, boolean notifyAuthFailure, AccountManagerCallback<Bundle> callback, Handler handler)
    

    这个方法适用于应用在后台运行时调用,如果需要用户输入证书,可以发出一个通知(notification)。如果notifyAuthFailure设置为true,需要的情况下会发出一个会启动对应Intent的通知;如果设置为false,那么是由应用来处理返回的Intent。

一些上述方法中使用到的参数:

  • accountType
    用于标示账户的类型,通常一种类型绑定到一个AccountAuthenticator。可以多个app使用同一个accountType实现单点登陆。
  • authTokenType
    用于标示authToken的类型,同一账户在不同的app中获取的authToken可能拥有不同的scope,从而可以用authTokenType来标示该authToken的类型。

AccountAuthenticatorService

Authenticator的实现类最后会由AccountManager以AIDL的方式调用。所以实现需要注册响应的Service。AbstractAccountAuthenticator已经已经实现了getIBinder方法,所以只需在onBind调用该方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AccountAuthenticatorService extends Service {
private static AccountAuthenticator AUTHENTICATOR;
public IBinder onBind(Intent intent) {
return intent.getAction().equals(ACTION_AUTHENTICATOR_INTENT) ? getAuthenticator()
.getIBinder() : null;
}
private AccountAuthenticator getAuthenticator() {
if (AUTHENTICATOR == null)
AUTHENTICATOR = new AccountAuthenticator(this);
return AUTHENTICATOR;
}
}

同时还需要在Manifest中注册:

1
2
3
4
5
6
7
8
<service android:name=".account.AccountAuthenticatorService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator"/>
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator"/>
</service>

其中meta-data引用的resource决定显示在Setting & Account中的内容

1
2
3
4
5
6
7
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.jumper"
android:icon="@drawable/app_icon"
android:label="@string/app_name"
android:smallIcon="@drawable/app_icon"
android:accountPreferences="@xml/account_preferences"
/>

android:icon,android:label,android:smallIcon决定在Account中的显示
android:accountType是传入AccountAuthorization#addAccount的值

android:accountPreferences可以定义Setting & Account中偏好界面,从而对账户进行一些偏好设置

1
2
3
4
5
6
7
8
9
10
11
12
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="@string/title_fmt" />
<PreferenceScreen
android:key="key1"
android:title="@string/key1_action"
android:summary="@string/key1_summary">
<intent
android:action="key1.ACTION"
android:targetPackage="key1.package"
android:targetClass="key1.class" />
</PreferenceScreen>
</PreferenceScreen>

文章目录
  1. 1. 为什么需要Account Manager
  2. 2. 实现类
  3. 3. 调用流程
    1. 3.1. 关键代码
  4. 4. 实现
    1. 4.1. Authenticator
    2. 4.2. AccountAuthenticatorService
Fork me on GitHub