8.3.2 AccountManager addAccount分析
这一节将分析AccountManagerService中的一个重要的函数,即addAccount,其功能是为某项账户添加一个用户。下面以前面提及的Email为例来认识AAS的处理流程。
AccountManagerService是一个运行在SystemServer中的服务,客户端进程必须借助AccountManager提供的API来使用AccountManagerService服务,所以,本例需从AccountManager的addAccount函数讲起。
1.AccountManager的addAccount发起请求
AccountManager的addAccount函数的参数和返回值较复杂,先看其函数原型:
public AccountManagerFuture<Bundle>addAccount(
final String accountType,
final String authTokenType,
final String[]requiredFeatures,
final Bundle addAccountOptions,
final Activity activity,
AccountManagerCallback<Bundle>callback,
Handler handler)
在以上代码中:
addAccount的返回值类型是AccountManagerFuture<Bundle>。其中,AccountManager-
Future是一个模板Interface,其真实类型只有在分析addAccount的实现时才能知道。现在可以告诉读者的是,它和Java并发库(concurrent库)中的FutureTask有关,是对异步函数调用的一种封装[1]。调用者在后期只要调用它的getResult函数即可取得addAccount的调用结果。由于addAccount可能涉及网络操作(例如,AAS需要把账户添加到网络服务器上),所以这里采用了异步调用的方法以避免长时间的阻塞。这也是AccountManagerFuture的getResult不能在主线程中调用的原因。
addAccount的第一个参数accountType代表账户类型。该参数不能为空。就本例而言,它的值为com.android.email。
authTokenType、requiredFeatures和addAccountOptions与具体的AAS服务有关。如果想添加指定账户类型的Account,则须对其背后的AAS有所了解。
activity:此参数和界面有关。例如有些AAS需要用户输入用户名和密码,故需启动一个Activity。在这种情况下,AAS会返回一个Intent,客户端将通过这个Activity启动Intent所标示的Activity。我们将通过下文的分析介绍这一点。
callback和handler:这两个参数与如何获取addAccount返回结果有关。如这两个参数为空,客户端则须单独启动一个线程去调用AccountManagerFuture的getResult函数。addAccount的代码如下:
[—>AccountManager. java:addAccount]
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)//accountType不能为null
throw new IllegalArgumentException("accountType is null");
final Bundle optionsIn=new Bundle();
if(addAccountOptions!=null)//保存客户端传入的addAccountOptions
optionsIn.putAll(addAccountOptions);
optionsIn.putString(KEY_ANDROID_PACKAGE_NAME,
mContext.getPackageName());
//构造一个匿名类对象,该类继承自AmsTask,并实现了doWork函数。addAccount返回前
//将调用该对象的start函数
return new AmsTask(activity, handler, callback){
public void doWork()throws RemoteException{
//mService用于和AccountManagerService通信
mService.addAcount(mResponse, accountType, authTokenType,
requiredFeatures, activity!=null, optionsIn);
}
}.start();
}
在以上代码中,AccountManager的addAccount函数将返回一个匿名类对象,该匿名类继承自AmsTask类。那么,AmsTask又是什么呢?
(1)AmsTask介绍
先来看AmsTask的继承关系,如图8-7所示。
图 8-7 AmsTask继承关系
由图8-7可知:
AmsTask继承自FutureTask,并实现了AccountManagerFuture接口。FutureTask是Java concurrent库中一个常用的类。AmsTask定义了一个doWork虚函数,该函数必须由子类来实现。
一个AmsTask对象中有一个mResponse成员,该成员的类型是AmsTask中的内部类Response。从Response的派生关系可知,Response将参与Binder通信,并且它是Binder通信的Bn端。而AccountManagerService的addAccount将得到它的Bp端对象。当添加完账户后,AccountManagerService会通过这个Bp端对象的onResult或onError函数向Response通知处理结果。
(2)AmsTask匿名类处理分析
AccountManager的addAccount最终返回给客户端的是一个AmsTask的子类,首先来了解它的构造函数,其代码如下:
[—>AccountManager. java:AmsTask]
public AmsTask(Activity activity, Handler handler,
AccountManagerCallback<Bundle>callback){
……//调用基类构造函数
//保存客户端传递的参数
mHandler=handler;
mCallback=callback;
mActivity=activity;
mResponse=new Response();//构造一个Response对象,并保存到mResponse中
}
下一步调用的是这个匿名类的start函数,代码如下:
[—>AccountManager. java:AmsTask.start]
public final AccountManagerFuture<Bundle>start(){
try{
doWork();//调用匿名类实现的doWork函数
}……
return this;
}
匿名类实现的doWork函数即下面这个函数:
[—>AccountManager. java:addAccount返回的匿名类]
public void doWork()throws RemoteException{
//调用AccountManagerService的addAccount函数,其第一个参数是mResponse
mService.addAcount(mResponse, accountType, authTokenType,
requiredFeatures, activity!=null, optionsIn);
}
AccountManager的addAccount函数的实现比较新奇,它内部使用了Java的concurrent类。不熟悉Java并发编程的读者有必要了解相关知识。
下面转到AccountManagerService中去分析addAccount的实现。
2.AccountManagerService addAccount转发请求
AccountManagerService addAccount的代码如下:
[—>AccountManagerService. java:addAccount]
public void addAcount(final IAccountManagerResponse response,
final String accountType, final String authTokenType,
final String[]requiredFeatures, final boolean expectActivityLaunch,
final Bundle optionsIn){
……
//检查客户端进程是否有“android.permission.MANAGE_ACCOUNTS”的权限
checkManageAccountsPermission();
final int pid=Binder.getCallingPid();
final int uid=Binder.getCallingUid();
//构造一个Bundle类型的options变量,并保存传入的optionsIn
final Bundle options=(optionsIn==null)?new Bundle():optionsIn;
options.putInt(AccountManager.KEY_CALLER_UID, uid);
options.putInt(AccountManager.KEY_CALLER_PID, pid);
long identityToken=clearCallingIdentity();
try{
//创建一个匿名类对象,该匿名类派生自Session类。最后调用该匿名类的bind函数
new Session(response, accountType, expectActivityLaunch, true){
public void run()throws RemoteException{
mAuthenticator.addAccount(this, mAccountType,
authTokenType, requiredFeatures, options);
}
protected String toDebugString(long now){
……//实现toDebugString函数
}
}.bind();}finally{
restoreCallingIdentity(identityToken);
}
}
由以上代码可知,AccountManagerService的addAccount函数最后也创建了一个匿名类对象,该匿名类派生自Session。addAccount最后还调用了这个对象的bind函数。其中最重要的内容就是Session。那么,Session又是什么呢?
(1)Session介绍
Session家族成员如图8-8所示。
图 8-8 Session家族示意图
由图8-8可知:
Session从IAccountAuthenticatorResponse.Stub派生,这表明它将参与Binder通信,并且它是Bn端。那么这个Binder通信的目标是谁呢?它正是具体的AAS服务。Account-ManagerService会将自己传递给AAS,这样,AAS就得到IAccountAuthenticator-Response的Bp端对象。当AAS完成了具体的账户添加工作后,会通过IAccount-AuthenticatorResponse的Bp端对象向Session返回处理结果。
Session通过mResponse成员变量指向来自客户端的IAccountManagerResponse接口,当Session收到AAS的返回结果后,又通过IAccountManagerResponse的Bp端对象向客户端返回处理结果。
Session mAuthenticator变量的类型是IAccountAuthenticator,它用于和远端的AAS通信。客户端发起的请求将通过Session经由mAuthenticator调用对应AAS中的函数。
由图8-7和图8-8可知,AccountManagerService在addAccount流程中起桥梁的作用,具体如下:
客户端将请求发送给AccountManagerService,然后AccountManagerService再转发给对应的AAS。
AAS处理完的结果先返回给AccountManagerService,再由AccountManagerService返回给客户端。
由于图8-7和图8-8中定义的类名较相似,因此读者阅读时应仔细一些。
下面来看Session匿名类的处理。
(2)Session匿名类处理分析
首先调用Session的构造函数,代码为:
[—>AccountManagerService. java:Session]
public Session(IAccountManagerResponse response, String accountType,
boolean expectActivityLaunch, boolean stripAuthTokenFromResult){
super();
……
/*
注意其中的参数,expectActivityLaunch由客户端传来,如果用户调用
AccountManager addAccount时传入了activity参数,则该值为true,
stripAuthTokenFromResult的默认值为true
*/
mStripAuthTokenFromResult=stripAuthTokenFromResult;
mResponse=response;
mAccountType=accountType;
mExpectActivityLaunch=expectActivityLaunch;
mCreationTime=SystemClock.elapsedRealtime();
synchronized(mSessions){
//将这个匿名类对象保存到AccountManagerService中的mSessions成员中
mSessions.put(toString(),this);
}
try{//监听客户端死亡消息
response.asBinder().linkToDeath(this,0);
}……
}
获得匿名类对象后,addAccount将调用其bind函数,该函数由Session实现,代码如下:
[—>AccountManagerService. java:Session:bind]
void bind(){
//绑定到mAccountType指定的AAS。在本例中,AAS的类型是“com.android.email”
if(!bindToAuthenticator(mAccountType)){
onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,"bind failure");
}
}
bindToAuthenticator的代码为:
[—>AccountManagerService. java:Session:bindToAuthenticator]
private boolean bindToAuthenticator(String authenticatorType){
//从mAuthenticatorCache中查询满足指定类型的服务信息
AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>
authenticatorInfo=
mAuthenticatorCache.getServiceInfo(
AuthenticatorDescription.newKey(authenticatorType));
……
Intent intent=new Intent();
intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT);
//设置目标服务的componentName
intent.setComponent(authenticatorInfo.componentName);
//通过bindService启动指定的服务,成功与否将通过第二个参数传递的
//ServiceConnection接口返回
if(!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)){
……
}
return true;
}
由以上代码可知,Session的bind函数将启动指定类型的Service,这是通过bindService函数完成的。如果服务启动成功,Session的onServiceConnected函数将被调用,这部分代码如下:
[—>AccountManagerService. java:Session:onServiceConnected]
public void onServiceConnected(ComponentName name, IBinder service){
//得到远端AAS返回的IAccountAuthenticator接口,这个接口用于
//AccountManagerService和该远端AAS交互
mAuthenticator=IAccountAuthenticator.Stub.asInterface(service);
try{
run();//调用匿名类实现的run函数
}……
}
匿名类实现的run函数非常简单,代码如下:
[—>AccountManagerService. java:addAccount返回的匿名类]
new Session(response, accountType, expectActivityLaunch, true){
public void run()throws RemoteException{
//调用远端AAS实现的addAccount函数
mAuthenticator.addAccount(this, mAccountType,
authTokenType, requiredFeatures, options);
}
由以上代码可知,AccountManagerService在addAccount最终将调用AAS实现的add-Account函数。
下面来看本例中满足“com.android.email”类型的服务是如何处理addAccount的请求的。该服务就是Email应用中的EasAuthenticatorService,下面来分析它。
3.EasAuthenticatorService处理请求
EasAuthenticatorService的创建是AccountManagerService调用了bindService完成的,该函数会触发EasAuthenticatorService的onBind函数的调用,这部分代码如下:
[—>EasAuthenticatorService. java:onBind]
public IBinder onBind(Intent intent){
if(AccountManager.ACTION_AUTHENTICATOR_INTENT.equals(
intent.getAction())){
//创建一个EasAuthenticator类型的对象,并调用其getIBinder函数
return new EasAuthenticator(this).getIBinder();
}else return null;
}
下面来分析EasAuthenticator。
(1)EasAuthenticator介绍
EasAuthenticator是EasAuthenticatorService定义的内部类,其家族关系如图8-9所示。
图 8-9 EasAuthenticator家族类图
由图8-9可知:
EasAuthenticator从AbstractAccountAuthenticator类派生。AbstractAccountAuthenti-cator内部有一个mTransport的成员变量,其类型是AbstractAccount-Authenticator的内部类Transport。在前面的onBind函数中,EasAuthenticator的getIBinder函数返回的就是这个变量。
Transport类继承自Binder,故它将参与Binder通信,并且是IAccountAuthenticator的Bn端。Session匿名类通过onServiceConnected函数将得到一个IAccountAuthen-ticator的Bp端对象。
当由AccoutManagerService的addAccount创建的那个Session匿名类调用IAccount-Authenticator Bp端对象的addAccount时,将触发位于Emai进程中的IAccountAuthenticator Bn端的addAccount。下面来分析Bn端的addAccount函数。
(2)EasAuthenticator的addAccount函数分析
根据上文的描述可知,Emai进程中首先被触发的是IAccountAuthenticator Bn端的addAccount函数,其代码如下:
[—>AbstractAccountAuthenticator. java:Transport:addAccount]
private class Transport extends IAccountAuthenticator.Stub{
public void addAccount(IAccountAuthenticatorResponse response,
String accountType, String authTokenType,
String[]features, Bundle options)
throws RemoteException{
//检查权限
checkBinderPermission();
try{
//调用AbstractAccountAuthenticator子类实现的addAccount函数
final Bundle result=
AbstractAccountAuthenticator.this.addAccount(
new AccountAuthenticatorResponse(response),
accountType, authTokenType, features, options);
//如果返回的result不为空,则调用response的onResult返回结果。
//这个response是IAccountAuthenticatorResponse类型,它的onResult
//将触发位于Session匿名类中mResponse变量的onResult函数被调用
if(result!=null)
response.onResult(result);
}……
}
本例中AbstractAccountAuthenticator子类(即EasAuthenticator)实现的addAccount函数,代码如下:
[—>EasAuthenticatorService. java:EasAuthenticator.addAccount]
public Bundle addAccount(AccountAuthenticatorResponse response,
String accountType, String authTokenType,
String[]requiredFeatures, Bundle options)
throws NetworkErrorException{
//EasAuthenticator addAccount的处理逻辑和Email应用有关,读者只做简单了解即可。
//如果用户传递的账户信息保护了密码和用户名,则走if分支。注意,其中有一些参数名是
//通用的,例如OPTIONS_PASSWORD, OPTIONS_USERNAME等
if(options!=null&&options.containsKey(OPTIONS_PASSWORD)
&&options.containsKey(OPTIONS_USERNAME)){
//创建一个Account对象,该对象仅包括两个成员,一个是name,用于表示账户名;
//另一个是type,用于表示账户类型
final Account account=new
Account(options.getString(OPTIONS_USERNAME),
AccountManagerTypes.TYPE_EXCHANGE);
//调用AccountManager的addAccountExplicitly将account对象和password传递
//给AccountManagerService处理。读者可自行研究这个函数,在其内部将这些信息写入
//accounts.db的account表中
AccountManager.get(EasAuthenticatorService.this).
addAccountExplicitly(account,
options.getString(OPTIONS_PASSWORD),null);
//根据Email应用的规则,下面将判断和该账户相关的数据是否需要设置自动数据同步
//首先判断是否需要处理联系人自动数据同步
boolean syncContacts=false;
if(options.containsKey(OPTIONS_CONTACTS_SYNC_ENABLED)&&
options.getBoolean(OPTIONS_CONTACTS_SYNC_ENABLED))
syncContacts=true;
ContentResolver.setIsSyncable(account,
ContactsContract.AUTHORITY,1);
ContentResolver.setSyncAutomatically(account,
ContactsContract.AUTHORITY, syncContacts);
boolean syncCalendar=false;
……//判断是否需要设置Calendar自动数据同步
boolean syncEmail=false;
//如果选项中包含Email同步相关的功能,则需要设置Email数据同步的相关参数
if(options.containsKey(OPTIONS_EMAIL_SYNC_ENABLED)&&
options.getBoolean(OPTIONS_EMAIL_SYNC_ENABLED))
syncEmail=true;
/*
下面这两个函数将和ContentService中的SyncManager交互。注意这
两个函数:第一个函数setIsSyncable将设置Email对应的同步服务功能标志,
第二个函数setSyncAutomatically将设置是否自动同步Email。
数据同步的内容留待8.4节再详细分析
*/
ContentResolver.setIsSyncable(account, EmailContent.AUTHORITY,1);
ContentResolver.setSyncAutomatically(account, EmailContent.AUTHORITY,
syncEmail);
//构造返回结果,注意,下面这些参数名是Android统一定义的,如KEY_ACCOUNT_NAME等
Bundle b=new Bundle();
b.putString(AccountManager.KEY_ACCOUNT_NAME,
options.getString(OPTIONS_USERNAME));
b.putString(AccountManager.KEY_ACCOUNT_TYPE,
AccountManagerTypes.TYPE_EXCHANGE);
return b;
}else{
//如果没有传递password,则需要启动一个Activity,该Activity对应的Intent
//由actionSetupExchangeIntent返回。注意下面几个通用的参数,如
//KEY_ACCOUNT_AUTHENTICATOR_RESPONSE和KEY_INTENT
Bundle b=new Bundle();
Intent intent=AccountSetupBasics.actionSetupExchangeIntent(
EasAuthenticatorService.this);
intent.putExtra(
AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
b.putParcelable(AccountManager.KEY_INTENT, intent);
return b;
}
}
不同的AAS有自己特定的处理逻辑,以上代码向读者展示了EasAuthenticatorService的工作流程。虽然每个AAS的处理方式各有不同,但Android还是定义了一些通用的参数,例如,OPTIONS_USERNAME用于表示用户名,OPTIONS_PASSWORD用于表示密码等。关于这方面的内容,读者可查阅SDK中AccountManager的文档说明。
4.返回值的处理流程
在EasAuthenticator的addAccount返回处理结果后,AbstractAuthenticator将通过IAccount-AuthenticatorResponse的onResult将其返回给由AccountManagerService创建的Session匿名类对象。来看Session的onResult函数,其代码如下:
[—>AccountManagerService. java:Session:onResult]
public void onResult(Bundle result){
mNumResults++;
//从返回的result中取出相关信息,关于下面这个if分支处理和状态栏中相关的逻辑,
//读者可自行研究
if(result!=null&&!TextUtils.isEmpty(
result.getString(AccountManager.KEY_AUTHTOKEN))){
String accountName=result.getString(AccountManager.KEY_ACCOUNT_NAME);
String accountType=
result.getString(AccountManager.KEY_ACCOUNT_TYPE);
if(!TextUtils.isEmpty(accountName)&&
!TextUtils.isEmpty(accountType)){
Account account=new Account(accountName, accountType);
cancelNotification(
getSigninRequiredNotificationId(account));
}}
IAccountManagerResponse response;
//如果客户端传递了activity参数,则mExpectActivityLaunch为true。如果
//AAS返回的结果中包含KEY_INTENT,则表明需要弹出Activity以输入账户和密码
if(mExpectActivityLaunch&&result!=null
&&result.containsKey(AccountManager.KEY_INTENT)){
response=mResponse;
}else{
/*
getResponseAndClose返回的也是mResponse,不过它会调用unBindService
断开和AAS服务的连接。就整个流程而言,此时addAccount已经完成AAS和
AccountManagerService的工作,故无须再保留和AAS服务的连接。而由于上面的if
分支还需要输入用户密码,因此以AAS和AccountManagerService之间的工作
还没有真正完成
*/
response=getResponseAndClose();
}
if(response!=null){
try{
……
if(mStripAuthTokenFromResult)
result.remove(AccountManager.KEY_AUTHTOKEN);
//调用位于客户端的IAccountManagerResponse的onResult函数
response.onResult(result);
}……
}
}
客户端的IAccountManagerResponse接口由AmsTask内部类Response实现,其代码为:[—>AccountManager.java:AmsTask:Response.onResult]
public void onResult(Bundle bundle){
Intent intent=bundle.getParcelable(KEY_INTENT);
//如果需要弹出Activity,则要利用客户端传入的Activity去启动AAS指定的
//Activity。这个Activity由AAS返回的Intent来表示
if(intent!=null&&mActivity!=null){
mActivity.startActivity(intent);
}else if(bundle.getBoolean("retry")){
//如果需要重试,则再次调用doWork
try{
doWork();
}……
}else{
//将返回结果保存起来,当客户端调用getResult时,就会得到相关结果
set(bundle);
}
}
5.AccountManager的addAccount分析总结
AccountManager的addAccount流程分析起来会给人一种行云流水般的感觉。该流程涉及3个模块,分别是客户端、AccountManagerService和AAS。整体难度虽不算大,但架构却比较巧妙。
总结起来addAccount相关流程如图8-10所示。
图 8-10 AccountManager的addAccount处理流程
为了让读者看得更清楚,图8-10中略去了一些细枝末节的内容。另外,图8-10中第10步的onServiceConnected函数应由位于SystemServer中的ActivityThread对象调用,为方便阅读,这里没有画出ActivityThread对象。
[1]从设计模式角度来说,这属于Active Object模式。详细内容可阅读《Pattern.Oriented.Software.Architecture.Volume.2》的第2章“Concurrency Patterns”。