💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
这一节将分析AccountManagerService中的一个重要的函数,即addAccount,其功能是为某项账户添加一个用户。下面以前面提及的Email为例来认识AAS的处理流程。 AccountManagerService是一个运行在SystemServer中的服务,客户端进程必须借助AccountManager提供的API来使用AccountManagerService服务,所以,本例需从AccountManager的addAccount函数讲起。 1. AccountManager的addAccount发起请求 AccountManager 的addAccount函数的参数和返回值较复杂,先看其函数原型: ~~~ public AccountManagerFuture<Bundle>addAccount( finalString accountType, finalString authTokenType, finalString[] requiredFeatures, finalBundle addAccountOptions, finalActivity activity, AccountManagerCallback<Bundle>callback, Handlerhandler) ~~~ 在以上代码中: - addAccount的返回值类型是AccountManagerFuture<Bundle>。其中,AccountManagerFuture是一个模板Interface,其真实类型只有在分析addAccount的实现时才能知道。现在可以告诉读者的是,它和Java并发库(concurrent库)中的FutureTask有关,是对异步函数调用的一种封装[^①]。调用者在后期只要调用它的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, finalString authTokenType, final String[] requiredFeatures, finalBundle addAccountOptions, final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { if(accountType == null) //accountType不能为null thrownew IllegalArgumentException("accountType is null"); finalBundle optionsIn = new Bundle(); if(addAccountOptions != null)//保存客户端传入的addAccountOptions optionsIn.putAll(addAccountOptions); optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName()); //构造一个匿名类对象,该类继承自AmsTask,并实现了doWork函数。addAccount返回前 //将调用该对象的start函数 returnnew AmsTask(activity, handler, callback) { publicvoid doWork() throws RemoteException { //mService用于和AccountManagerService通信 mService.addAcount(mResponse, accountType, authTokenType, requiredFeatures, activity!= null, optionsIn); } }.start(); } ~~~ 在以上代码中,AccountManager的 addAccount函数将返回一个匿名类对象,该匿名类继承自AmsTask类。那么,AmsTask又是什么呢? (1) AmsTask介绍 先来看AmsTask的继承关系,如图8-7所示。 :-: ![](http://img.blog.csdn.net/20150803131511362?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图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函数 }...... returnthis; } ~~~ 匿名类实现的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转发请求 AccountManagerServiceaddAccount的代码如下所示: **AccountManagerService.java::addAccount** ~~~ public void addAcount(finalIAccountManagerResponse response, finalString accountType, final String authTokenType, final String[] requiredFeatures, final boolean expectActivityLaunch, final Bundle optionsIn) { ...... //检查客户端进程是否有“android.permission.MANAGE_ACCOUNTS”的权限 checkManageAccountsPermission(); final intpid = Binder.getCallingPid(); final intuid = Binder.getCallingUid(); //构造一个Bundle类型的options变量,并保存传入的optionsIn finalBundle options = (optionsIn == null) ? new Bundle() : optionsIn; options.putInt(AccountManager.KEY_CALLER_UID, uid); options.putInt(AccountManager.KEY_CALLER_PID, pid); longidentityToken = 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(longnow) { ......//实现toDebugString函数 } }.bind(); }finally { restoreCallingIdentity(identityToken); } } ~~~ 由以上代码可知,AccountManagerService的addAccount函数最后也创建了一个匿名类对象,该匿名类派生自Session。addAccount最后还调用了这个对象的bind函数。其中最重要的内容就是Session。那么,Session又是什么呢? (1) Session介绍 Session家族成员如图8-8所示。 :-: ![](http://img.blog.csdn.net/20150803131528312?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图8-8 Session家族示意图 由图8-8可知: - Session从IAccountAuthenticatorResponse.Stub派生,这表明它将参与Binder通信,并且它是Bn端。那么这个Binder通信的目标是谁呢?,它正是具体的AAS服务。AccountManagerService会将自己传递给AAS,这样,AAS就得到IAccountAuthenticatorResponse的Bp端对象。当AAS完成了具体的账户添加工作后,会通过IAccountAuthenticatorResponse的Bp端对象向Seession返回处理结果。 - 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函数,该函数由AmsTask实现,代码如下: **AccountManagerService.java::Session:bind** ~~~ void bind() { //绑定到mAccountType指定的AAS。在本例中,AAS的类型是“com.android.email” if(!bindToAuthenticator(mAccountType)) { onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bindfailure"); } } ~~~ bindToAuthenticator的代码为: **AccountManagerService.java::Session:bindToAuthenticator** ~~~ private boolean bindToAuthenticator(StringauthenticatorType) { //从mAuthenticatorCache中查询满足指定类型的服务信息 AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo = mAuthenticatorCache.getServiceInfo( AuthenticatorDescription.newKey(authenticatorType)); ...... Intentintent = new Intent(); intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT); //设置目标服务的ComponentName intent.setComponent(authenticatorInfo.componentName); //通过bindService启动指定的服务,成功与否将通过第二个参数传递的 //ServiceConnection接口返回 if(!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) { ...... } returntrue; } ~~~ 由以上代码可知,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) { publicvoid run() throws RemoteException { //调用远端AAS实现的addAccount函数 mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,options); } ~~~ 由以上代码可知,AccountManagerService在addAccount最终将调用AAS实现的addAccount函数。 下面来看本例中满足“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所示。 :-: ![](http://img.blog.csdn.net/20150803131545395?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图8-9 EasAuthenticator家族类图 由图8-9可知: - EasAuthenticator从AbstractAccountAuthenticator类派生。AbstractAccountAuthenticator内部有一个mTransport的成员变量,其类型是AbstractAccountAuthenticator的内部类Transport。在前面的onBind函数中,EasAuthenticator的getIBinder函数返回的就是这个变量。 - Transport类继承自Binder,故它将参与Binder通信,并且是IAccountAuthenticator的Bn端。Session匿名类通过onServiceConnected函数将得到一个IAccountAuthenticator的Bp端对象。 当由AccoutManagerService的addAccount创建的那个Session匿名类调用IAccountAuthenticator Bp端对象的addAccount时,将触发位于Emai进程中的IAccountAuthenticatorBn端的addAccount。下面来分析Bn端的addAccount函数。 (2) EasAuthenticator的 addAccount函数分析 根据上文的描述可知,Emai 进程中首先被触发的是IAccountAuthenticatorBn端的addAccount函数,其代码如下: **AbstractAccountAuthenticator.java::Transport:addAccount** ~~~ private class Transport extendsIAccountAuthenticator.Stub { publicvoid 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 BundleaddAccount(AccountAuthenticatorResponse response, String accountType, StringauthTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { //EasAuthenticatoraddAccount的处理逻辑和Email应用有关。只做简单了解即可 //如果用户传递的账户信息保护了密码和用户名,则走if分支。注意,其中有一些参数名是 //通用的,例如OPTIONS_PASSWORD,OPTIONS_USERNAME等 if(options != null && options.containsKey(OPTIONS_PASSWORD) && options.containsKey(OPTIONS_USERNAME)) { //创建一个Account对象,该对象仅包括两个成员,一个是name,用于表示账户名; //另一个是type,用于表示账户类型 finalAccount 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应用的规则,下面将判断和该账户相关的数据是否需要设置自动数据同步 //首先判断是否需要处理联系人自动数据同步 booleansyncContacts = 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); booleansyncCalendar = false; ......//判断是否需要设置Calendar自动数据同步 booleansyncEmail = 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等 Bundleb = new Bundle(); b.putString(AccountManager.KEY_ACCOUNT_NAME, options.getString(OPTIONS_USERNAME)); b.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountManagerTypes.TYPE_EXCHANGE); returnb; } 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将通过IAccountAuthenticatorResponse的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) { Intentintent = bundle.getParcelable(KEY_INTENT); //如果需要弹出Activity,则要利用客户端传入的那个activity去启动AAS指定的 //Activity。这个Activity由AAS返回的Intent来表示 if(intent != null && mActivity != null) { mActivity.startActivity(intent); } elseif (bundle.getBoolean("retry")) { //如果需要重试,则再次调用doWork try { doWork(); }...... }else{ //将返回结果保存起来,当客户端调用getResult时,就会得到相关结果 set(bundle); } } ~~~ 5. AccountManager的addAccount分析总结 AccountManager的addAccount流程分析起来给人一种行云流水般的感觉。该流程涉及3个模块,分别是客户端、AccountManagerService和AAS。整体难度虽不算大,但架构却比较巧妙。 总结起来addAccount相关流程如图8-10所示。 :-: ![](http://img.blog.csdn.net/20150803131604163?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图8-10 AccountManager的addAccount处理流程 为了让读者看得更清楚,图8-10中略去了一些细枝末节的内容。另外,图8-10中第10步的onServiceConnected函数应由位于SystemServer中的ActivityThread对象调用,为方便阅读起见,这里没有画出ActivityThread的对象。 [^①]:从设计模式角度来说,这属于ActiveObject模式。详细内容可阅读《Pattern.Oriented.Software.Architecture.Volume.2》的第2章“Concurrency Patterns”。