3

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService

 3 years ago
source link: https://blog.csdn.net/Innost/article/details/47254799
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

由于《深入理解Android 卷一》和《深入理解Android卷二》不再出版,而知识的传播不应该因为纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的全部内容

 

第8章  深入理解ContentService和AccountManagerService

本章主要内容:

·  介绍ContentService

·  介绍AccountManagerService

本章所涉及的源代码文件名及位置:

·  SystemServer.java

frameworks/base/services/java/com/android/server/SystemServer.java

·  ContentService.java

frameworks/base/core/java/android/content/ContentService.java

·  ContentResolver.java

frameworks/base/core/java/android/content/ContentResolver.java

·  UsbSettings.java

packages/apps/Settings/src/com/android/settings/deviceinfo/UsbSettings.java

·  DevelopmentSettings.java

packages/apps/Settings/src/com/android/settings/DevelopmentSettings.java

·  UsbDeviceManager.java

frameworks/base/services/java/com/android/server/usb/UsbDeviceManager.java

·  AccountManagerService.java

frameworks/base/core/java/android/accounts/AccountManagerService.java

·  AccountAuthenticatorCache.java

frameworks/base/core/java/android/accounts/AccountAuthenticatorCache.java

·  RegisteredServicesCache.java

frameworks/base/core/java/android/content/pm/RegisteredServicesCache.java

·  AccountManager.java

frameworks/base/core/java/android/accounts/AccountManager.java

·  EasAuthenticatorService

packages/apps/Email/src/com/android/email/service/EasAuthenticatorService.java

·  SyncManager.java

frameworks/base/core/java/android/content/SyncManager.java

·  SyncStorageEngine.java

frameworks/base/core/java/android/content/SyncStorageEngine.java

·  SyncAdapterCache.java

frameworks/base/core/java/android/content/SyncAdapterCache.java

·  SyncQueue.java

frameworks/base/core/java/android/content/SyncQueue.java

·  EmailSyncAdapterService.java

packages/apps/Exchange/src/com/android/exchange/EmailSyncAdapterService.java

·  AbstractThreadedSyncAdapter.java

frameworks/base/core/java/android/content/AbstractThreadedSyncAdapter.java

8.1  概述

本章将分析ContentService和AccountManagerService,其中,ContentService包含以下两个主要功能:

·  它是Android平台中数据更新通知的执行者。数据更新通知与第7章分析Cursorquery函数实现时提到的ContentObserver有关。这部分内容将在8.2节中分析。

·  它是Android平台中数据同步服务的管理中枢。当用户通过Android手机中的Contacts应用将联系人信息同步到远端服务器时,就需要和ContentService交互。这部分内容是本章的难点,将在8.4节中进行分析

本章要分析的第二个Service是AccountManagerService,它负责管理Android手机中用户的账户,这些账户是用户的online账户,例如用户在Google、Facebook上注册的账户。

本章将先分析ContentService中数据通知机制的实现,然后分析AccountManagerService,最后再介绍ContentService中的数据同步服务。

提示这两个Service的难度都不大,它们在设计结构上有较大的相似性,在内容上也有一定的关联。另外,作为本书最后一章,笔者照例会留一些难度适中的问题或知识点供读者自行分析研究。

8.2  数据更新通知机制分析

何为数据更新通知?先来看日常生活中的一个例子。

笔者所在公司采用BugZilla来管理Bug。在日常工作中,笔者和同事们的一部分工作就是登录BugZilla查询各自名下的Bug并修改它们。如何跟踪自己的Bug呢?其实,以上描述中已经提到了一种方法,即登录BugZilla并查询。除此之外,BugZilla还支持另一种方法,即为每个Bug设置一个关系人列表,一旦该Bug的状态发生变化,BugZilla就会给该Bug关系人列表中的人发送邮件。

上例中提到的第二种方法就是本节要分析的数据更新通知机制。一般说来,领导和项目经理(PM)使用第一种方法居多,因为他们需要不定时地查询和统计全局Bug的情况。而程序员使用第二种方法较多(也许是没办法的事情吧,谁会情愿主动查询自己的Bug呢?)。

类似的通知机制在日常生活中的其他地方还有使用。在操作系统中,这种通知机制同样也广泛存在。例如,在OS中,设计人员一般会安排外部设备以中断的方式通知CPU并让其开展后续处理,而不会让CPU去轮询外设的状态。

现在回到Android平台,如果程序需要监控某数据项的变化,可以采用一个类似while循环的语句不断查询它以判断其值是否发生了变化。显而易见,这种方式的效率很低。有了通知机制以后,程序只需注册一个ContentObserver实例即可。一旦该项数据发生变化,系统就会通过ContentObserver的onChange函数来通知我们。与前面所述的轮询相比,此处的通知方式明显更高效。

通过上面的描述可以知道,通知机制的实施包括两个步骤:第一步,注册观察者;第二步,通知观察者。在Android平台中,这两步都离不开ContentService,下面来认识一下它。

提示在设计模式中,通知机制对应的模式是Observer模式,即观察者模式。

8.2.1  初识ContentService

SystemServer创建ContentService的代码非常简单,如下所示:

[-->SystemServer::ServerThread.run]

public void run() {

   ......

  ContentService.main(context,

                   factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL);

   ......

以上代码中直接调用了ContentService的main函数。在一般情况下,该函数第二个参数为false。此main函数的代码如下:

·  [-->ContentService.java::main]

public static IContentService main(Contextcontext, boolean factoryTest) {

   //构造ContentService实例

  ContentService service = new ContentService(context, factoryTest);

   //将ContentService注册到ServiceManager中,其注册名叫“content”

  ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME,

                                 service);

   returnservice;

ContentService的构造函数的代码如下:

[-->ContentService.java::ContentService]

ContentService(Context context, booleanfactoryTest) {

    mContext = context;

   mFactoryTest = factoryTest;

    getSyncManager(); //获取同步服务管理对象,接下来看它的代码

[-->ContentService.java::getSyncManager]

private SyncManager getSyncManager() {

   synchronized(mSyncManagerLock) {

          //创建一个SyncManager实例,它是ContentService中负责数据同步服务的

          //主力成员。留待8.4节再详细分析它

           if (mSyncManager == null) mSyncManager = new

                                        SyncManager(mContext,mFactoryTest);

       }......

           return mSyncManager;

看完以上代码读者可能会觉得ContentService比较简单。其实,ContentService中最难的功能是数据同步服务,不过该功能的实现都封装在SyncManager及相关类中了,所以在分析通知机制时不会和数据同步服务有太多瓜葛。

下面来分析通知机制实施的第一步,注册ContentObserver。该步骤由ContentResovler提供的registerContentObserver函数来实现。

8.2.2  ContentResovler的registerContentObserver分析

[-->ContentResolver.java::registerContentObserver]

public final void registerContentObserver(Uri uri,

            booleannotifyForDescendents,ContentObserver observer){

   注意registerContentObserver传递的参数,其中:

   uri是客户端设置的它所需要监听的数据项的地址,用Uri来表示

   notifyForDescendents:如果该值为true,则所有地址包含此uri的数据项发生变化时

   都会触发通知。否则只有完全符合该uri地址的数据项发生变化时才会触发通知。以文件夹和

   其中的文件为例,若uri指向某文件夹,则需设置notifyForDescendents为true。即该文件

   夹下的任何文件发生变化,都需要通知监听者。

   observer是客户端设置的监听对象。当数据项发生变化时,该对象的onChange函数将被调用

            调用ContentService的registerContentObserver函数,其第三个参数是

             observer.getContentObserver的返回值,它是什么呢?

           getContentService().registerContentObserver(uri,

                                   notifyForDescendents,

                                   observer.getContentObserver());

        } ......

registerContentObserver最终将调用ContentService的registerContentObserver函数,其中第三个参数是ContentObservergetContentObserver的返回值。这个返回值是什么呢?需请出ContentObserver家族成员。

1.  ContentObserver介绍

ContentObserver家族成员如图8-1所示。

图8-1  ContentObserver家族类图

图8-1中的ContentObserver类和第7章中介绍的ContentProvider类非常类似,内部都定义了一个Transport类参与Binder通信。由图8-1可知,Transport类从IContentObserver.stub派生。从Binder通信角度来看,客户端进程中的Transport将是Bn端。如此,通过registerContentObserver传递到ContentService所在进程的就是Bp端。IContentObserverBp端对象的真实类型是IContentObserver.Stub.Proxy。

注意IContentObserver.java由aidl处理IContentObserver.aidl生成,其位置在out/targer/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/database/IContentObserver.java中。

2.  registerContentObserver函数分析

下面来看ContentService的registerContentObserver函数的代码。

[-->ContentService.java::registerContentObserver]

public void registerContentObserver(Uri uri,boolean notifyForDescendents,

               IContentObserver observer) {

     ......

   synchronized (mRootNode) {

    //ContentService要做的事情其实很简单,就是保存uri和observer的对应关系到

     //其内部变量mRootNode中

    mRootNode.addObserverLocked(uri, observer, notifyForDescendents,

                    mRootNode, Binder.getCallingUid(),

                   Binder.getCallingPid());

mRootNode是ContentService的成员变量,其类型为ObserverNode。ObserverNode的组织形式是数据结构中的树,其叶子节点的类型为ObserverEntry,它保存了uri和对应的IContentObserver对象。本节不关注它们的内部实现,读者若有兴趣,不妨自行研究。

至此,客户端已经为某数据项设置了ContentObserver。再来看通知机制实施的第二步,即通知观察者。

8.2.3  ContentResolver的 notifyChange分析

数据更新的通知由ContentResolver的notifyChange函数触发。看MediaProvider的update函数的代码如下:

[-->MediaProvider.java::update]

public int update(Uri uri, ContentValuesinitialValues, String userWhere,

                     String[] whereArgs) {

   int count;

   int match= URI_MATCHER.match(uri);

  DatabaseHelper database = getDatabaseForUri(uri);

   //找到对应的数据库对象

  SQLiteDatabase db = database.getWritableDatabase();

   ......

   synchronized (sGetTableAndWhereParam) {

    getTableAndWhere(uri, match, userWhere, sGetTableAndWhereParam);

     switch(match) {

         ......

        case VIDEO_MEDIA:

        case VIDEO_MEDIA_ID:{

          ContentValues values = newContentValues(initialValues);

          values.remove(ImageColumns.BUCKET_ID);

          values.remove(ImageColumns.BUCKET_DISPLAY_NAME);

          ......//调用SQLiteDatabase的update函数更新数据库

          count =db.update(sGetTableAndWhereParam.table, values,

                               sGetTableAndWhereParam.where, whereArgs);

         .......

         }......//其他处理

     }......//synchronized处理结束

    if (count > 0 &&!db.inTransaction()) //调用notifyChange触发通知

        getContext().getContentResolver().notifyChange(uri,null);

   returncount;

由以上代码可知,MediaProvider update函数更新完数据库后,将通过notfiyChange函数来通知观察者。notfiyChange函数的代码如下:

[-->ContentResolver.java::notifyChange]

public void notifyChange(Uri uri, ContentObserverobserver) {

     //在一般情况下,observer参数为null。调用另一个notifyChange函数,直接来看它

    notifyChange(uri, observer, true);

public void notifyChange(Uri uri, ContentObserverobserver,

                               boolean syncToNetwork) {

  //第三个参数syncToNetwork用于控制是否需要发起一次数据同步请求

       //调用ContentService的notifyChange函数

       getContentService().notifyChange(

             uri, observer == null ? null : observer.getContentObserver(),

             observer != null && observer.deliverSelfNotifications(),

             syncToNetwork);

        } ......

由以上代码可知,ContentService的notifyChange函数将被调用,其代码如下:

[-->ContentSerivce::notifyChange]

public void notifyChange(Uri uri, IContentObserverobserver,

     boolean observerWantsSelfNotifications, boolean syncToNetwork) {

  longidentityToken = clearCallingIdentity();

         ArrayList<ObserverCall> calls = newArrayList<ObserverCall>();

          //从根节点开始搜索需要通知的观察者,结果保存在calls数组中

         synchronized (mRootNode) {

               mRootNode.collectObserversLocked(uri, 0, observer,

                               observerWantsSelfNotifications,calls);

        final int numCalls = calls.size();

         for(int i=0; i<numCalls; i++) {

              ObserverCall oc = calls.get(i);

                     调用客户端IContentObserver Bn端,即ContentObserver

                     内部类Transport的onChange函数。最后再由Transport调用

                     客户端提供的ContentObserver子类的onChange函数

                   oc.mObserver.onChange(oc.mSelfNotify);

               } ......//异常处理

           if (syncToNetwork) {

               SyncManager syncManager = getSyncManager();

               if (syncManager != null) {

                   //发起一次同步请求,相关内容留待8.4节再分析

                   syncManager.scheduleLocalSync(null,

                                           uri.getAuthority());

        }finally {

           restoreCallingIdentity(identityToken);

8.2.4  数据更新通知机制总结和深入探讨

总结上面所描述的数据更新通知机制的流程如图8-2所示。

图8-2  数据更新通知的流程图

从前面的代码介绍和图8-2所示的流程来看,Android平台中的数据更新通知机制还较为简单。不过此处尚有几个问题想和读者一起探讨。

问题一:由图8-2可知,客户端2调用ContentProvider的update函数将间接触发客户端1的ContentObserver的onChange函数被调用。如果客户端1在onChange函数中耗时过长,会不会导致客户端2阻塞在update函数中呢?

想到这个问题的读者应该是非常细致和认真的了。确实,从前面所示的代码和流程图来看,这个情况几乎是必然会发生的,但是实际上该问题并不存在,原因在于下面这一段代码:

[-->IContentObserver.java::Proxy:onChange]

private static class Proxy implementsandroid.database.IContentObserver {

     privateandroid.os.IBinder mRemote;

     ......

     publicvoid onChange(boolean selfUpdate)

                      throws android.os.RemoteException {

        android.os.Parcel_data = android.os.Parcel.obtain();

           _data.writeInterfaceToken(DESCRIPTOR);

           _data.writeInt(((selfUpdate)? (1) : (0)));

          //调用客户端1的ContentObserver Bn端的onChange函数

           mRemote.transact(Stub.TRANSACTION_onChange,_data, null,

                                        android.os.IBinder.FLAG_ONEWAY);

        } finally {

            _data.recycle();

     ......

以上代码告诉我们,ContentService在调用客户端注册的IContentObserver 的onChange函数时,使用了FLAG_ONEWAY标志。根据第2章对该标志的介绍(参见2.2.1节),使用该标志的Binder调用只需将请求发给binder驱动即可,无需等待客户端onChange函数的返回。因此,即使客户端1在onChange中恶意浪费时间,也不会阻塞客户端2的update函数了。

问题二:这是一个开放性问题,最终需要读者给出合适的答案。

假设服务端有一项功能,需要客户端通过某种方式来控制它的开闭(即禁止或使用该功能),考虑一下有几种方式来实现这个控制机制。

Android平台上至少有三种方法可以实现这个控制机制。

第一种:服务端实现一个API函数,客户端直接调用这个函数来控制。

第二种客户端发送指定的广播,而服务端注册该广播的接收者,然后在这个广播接收者的onReceive函数中去处理。

第三种:服务端输出一个ContentProvider,并为这个功能输出一个uri地址,然后注册一个ContentObserver。客户端可通过更新数据的方式来触发服务端ContentObserver的onChange函数,服务端在该函数中做对应处理即可。

在Android代码中,这三种方法都有地方使用。下面将以Settings应用中和USB相关的功能设置为例来观察第一种和第三种方法的使用情况。

第一个实例和Android 4.0中新支持的USB MTP/PTP功能有关,相关代码如下:

[-->UsbSettings.java::onPreferenceTreeClick]

public booleanonPreferenceTreeClick(PreferenceScreen preferenceScreen,

                             Preference preference) {

   ......

   if(preference == mMtp) {

        mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP,true);

       updateToggles(UsbManager.USB_FUNCTION_MTP);

   } else if(preference == mPtp) {

        mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_PTP, true);

         updateToggles(UsbManager.USB_FUNCTION_PTP);

   returntrue;

由以上代码可知,如果用户从Settings界面中选择了使能MTP,将直接调用UsbManager的setCurrentFunction来使能MTP功能。这个函数的Bn端实现在UsbService中。

不过,同样是USB相关的功能控制,ADB的开关控制却采用了第三种方法,相关代码为:

[-->DevelopmentSettings.java::onClick]

public void onClick(DialogInterface dialog, intwhich) {

  if (which == DialogInterface.BUTTON_POSITIVE){

       mOkClicked = true;

       //设置Settings数据库ADB对应的数据项值为1

       Settings.Secure.putInt(getActivity().getContentResolver(),

                   Settings.Secure.ADB_ENABLED, 1);

   } else

       mEnableAdb.setChecked(false);//界面更新

上面的数据项更新操作将导致UsbDeviceManager做对应处理,其相关代码如下:

[-->UsbDeviceManager.java::onChange]

private class AdbSettingsObserver extendsContentObserver {

   ......

   publicvoid onChange(boolean selfChange) {

        //从数据库中取出对应项的值

        boolean enable =(Settings.Secure.getInt(mContentResolver,

                            Settings.Secure.ADB_ENABLED, 0) > 0);

          //发送MSG_ENABLE_ADB消息,UsbDeviceManager将处理此消息

           mHandler.sendMessage(MSG_ENABLE_ADB, enable);

同样是USB相关的功能,Settings应用却采用了两种截然不同的方法来处理它们。这种做法为笔者目前所从事的项目中USB扩展功能的实现带来了极大困扰,因为我们想采用统一的方法来处理USB相关功能。到底应采用哪种方法比较合适呢?第一种方法和第三种方法各自的适用场景是什么?读者不妨仔细思考并将结论告诉笔者。

问题三:我们在第7章中分析Cursorquery时曾看到过ContentObserver的身影,但是并没有对其进行详细分它。如果现在回过头去分析query流程中和ContentObserver相关的部分,所涉及的流程可能比本节内容还要多。

8.3  AccountManagerService分析

本节将分析AccountManagerService。如前所述,AccountManagerService负责管理手机中用户的online账户,主要工作涉及账户的添加和删除、AuthToken(全称为authentication token。有了它,客户端就无须每次操作都向服务器发送密码了)的获取和更新等。关于AccountManagerSerivce更详细的功能,可阅读SDK文档中AccountManager的说明。

下面看AccountManagerService创建时的代码:

8.3.1  初识AccountManagerService

[-->SystemServer.java::ServerThread.run]

  ......

  //注册AccountManagerService到ServiceManager,服务名为“account”

  ServiceManager.addService(Context.ACCOUNT_SERVICE,

                                    newAccountManagerService(context));

其构造函数的代码如下:

[-->AccountManagerService.java::AccountManagerService]

public AccountManagerService(Context context) {

   //调用另外一个构造函数,其第三个参数将构造一个AccountAuthenticatorCache对象,它是

   //什么呢?见下文分析

   this(context,context.getPackageManager(),

            new  AccountAuthenticatorCache(context));

在AccountManagerService构造函数中创建了一个AccountAuthenticatorCache对象,它是什么?来看下文。

1.  AccountAuthenticatorCache分析

AccountAuthenticatorCache是Android平台中账户验证服务(Account AuthenticatorService,AAS)的管理中心。而AAS则由应用程序通过在AndroidManifest.xml中输出符合指定要求的Service信息而来。稍后读者将看到这些要求的具体格式。

先来看AccountAuthenticatorCache的派生关系,如图8-3所示。

图8-3  AccountAuthenticatorCache类图

由图8-3可知:

·  AccountAuthenticatorCache从RegisteredServicesCache<AuthenticatorDescription>派生。RegisteredServicesCache是一个模板类,专门用于管理系统中指定Service的信息收集和更新,而具体是哪些Service由RegisteredServicesCache构造时的参数指定。AccountAuthenticatorCache对外输出由RegisteredServicesCache模板参数指定的类的实例。在图8-3中应该就是AuthenticatorDescription。

·  AuthenticatorDescription继承了Parcelable接口,这代表它可以跨Binder传递。该类描述了AAS相关的信息。

·  AccountAuthenticatorCache实现了IAccountAuthenticatorCache接口。这个接口供外部调用者使用以获取AAS的信息。

下面看AccountAuthenticatorCache的创建,其相关代码如下:

[-->AccountAuthenticatorCache.java::AccountAuthenticatorCache]

public AccountAuthenticatorCache(Context context){

  ACTION_AUTHENTICATOR_INTENT值为"android.accounts.AccountAuthenticator"

  AUTHENTICATOR_META_DATA_NAME值为"android.accounts.AccountAuthenticator"

  AUTHENTICATOR_ATTRIBUTES_NAME值为"account-authenticator"

  super(context,

           AccountManager.ACTION_AUTHENTICATOR_INTENT,

          AccountManager.AUTHENTICATOR_META_DATA_NAME,

          AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME, sSerializer);

AccountAuthenticatorCache调用在其基类RegisteredServicesCache的构造函数时,传递了3个字符串参数,这3个参数用于控制RegisteredServicesCache从PackageManagerService获取哪些Service的信息。

(1) RegisteredServicesCache分析

[-->RegisteredServicesCache.java::RegisteredServicesCache]

public RegisteredServicesCache(Context context,String interfaceName,

                                     StringmetaDataName, String attributeName,

                            XmlSerializerAndParser<V>serializerAndParser) {

    mContext= context;

    //保存传递进来的参数

   mInterfaceName = interfaceName;

   mMetaDataName = metaDataName;

   mAttributesName = attributeName;

   mSerializerAndParser = serializerAndParser;

    FiledataDir = Environment.getDataDirectory();

    FilesystemDir = new File(dataDir, "system");

   //syncDir指向/data/system/registered_service目录

    FilesyncDir = new File(systemDir, "registered_services");

    //下面这个文件指向syncDir目录下的android.accounts.AccountAuthenticator.xml

   mPersistentServicesFile = new AtomicFile(new File(syncDir,

                                          interfaceName+ ".xml"));

    //生成服务信息

   generateServicesMap();

    finalBroadcastReceiver receiver = new BroadcastReceiver() {

      public void onReceive(Context context1, Intent intent) {

         generateServicesMap();

    //注册Package安装、卸载和更新等广播监听者

   mReceiver = new AtomicReference<BroadcastReceiver>(receiver);

    IntentFilter intentFilter = newIntentFilter();

   intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);

   intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);

   intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);

   intentFilter.addDataScheme("package");

   mContext.registerReceiver(receiver, intentFilter);

    IntentFilter sdFilter = new IntentFilter();

   sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);

   sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);

   mContext.registerReceiver(receiver, sdFilter);

由以上代码可知:

·  成员变量mPersistentServicesFile指向/data/system/registered_service/目录下的一个文件,该文件保存了以前获取的对应Service的信息。就AccountAuthenticator而言,mPersistentServicesFile指向该目录的android.accounts.AccountAuthenticator.xml文件。

·  由于RegisteredServicesCache管理的是系统中指定Service的信息,当系统中有Package安装、卸载或更新时,RegisteredServicesCache也需要对应更新自己的信息,因为有些Service可能会随着APK被删除而不复存在。

generateServiceMap函数将获取指定的Service信息,其代码如下:

[-->RegisteredServicesCache.java::generateServicesMap]

void generateServicesMap() {

   //获取PackageManager接口,用来和PackageManagerService交互

  PackageManager pm = mContext.getPackageManager();

  ArrayList<ServiceInfo<V>> serviceInfos = newArrayList<ServiceInfo<V>>();

   在本例中,查询PKMS中满足Intent Action为"android.accounts.AccountAuthenticator"

   的服务信息。由以下代码可知,这些信息指的是Service中声明的MetaData信息

  List<ResolveInfo> resolveInfos = pm.queryIntentServices(

               new Intent(mInterfaceName),PackageManager.GET_META_DATA);

   for(ResolveInfo resolveInfo : resolveInfos) {

            调用parserServiceInfo函数解析从PKMS中获得的MetaData信息,该函数

            返回的是一个模板类对象。就本例而言,这个函数返回一个

            ServiceInfo<AccountAuthenticator>类型的对象

          ServiceInfo<V> info = parseServiceInfo(resolveInfo);

          serviceInfos.add(info);

   synchronized (mServicesLock) {

       if(mPersistentServices == null)

           readPersistentServicesLocked();

      mServices = Maps.newHashMap();

      StringBuilder changes = new StringBuilder();

       ......//检查mPersistentServices保存的服务信息和当前从PKMS中取出来的PKMS

      //信息,判断是否有变化,如果有变化,需要通知监听者。读者可自行阅读这段代码,

     //注意其中uid的作用

       mPersistentServicesFileDidNotExist = false;

接下来解析Service的parseServiceInfo函数。

(2) parseServiceInfo函数分析

[-->RegisteredServicesCache.java::parseServiceInfo]

private ServiceInfo<V>parseServiceInfo(ResolveInfo service)

                     throws XmlPullParserException, IOException {

  android.content.pm.ServiceInfo si = service.serviceInfo;

  ComponentName componentName = new ComponentName(si.packageName, si.name);

  PackageManager pm = mContext.getPackageManager();

  XmlResourceParser parser = null;

        //解析MetaData信息

       parser = si.loadXmlMetaData(pm, mMetaDataName);

       AttributeSet attrs = Xml.asAttributeSet(parser);

        inttype;

       ......

      StringnodeName = parser.getName();

      //调用子类实现的parseServiceAttributes得到一个真实的对象,在本例中它是

      //AuthenticatorDescription。注意,传递给parseServiceAttributes的第一个

      //参数代表MetaData中的resource信息。详细内容见下文的图例

     V v=parseServiceAttributes(

                     pm.getResourcesForApplication(si.applicationInfo),

                    si.packageName, attrs);

    finalandroid.content.pm.ServiceInfo serviceInfo = service.serviceInfo;

    finalApplicationInfo applicationInfo = serviceInfo.applicationInfo;

    finalint uid = applicationInfo.uid;

    returnnew ServiceInfo<V>(v, componentName, uid);

   } ...... finally {

           if (parser != null) parser.close();

parseServiceInfo将解析Service中的MetaData信息,然后调用子类实现的parseServiceAttributes函数,以获取特定类型Service的信息。

下面通过实例向读者展示最终的解析结果。

(3) AccountAuthenticatorCache分析总结

在Email应用的AndroidManifest.xml中定义了一个AAS,如图8-4所示。

图8-4  Email AAS定义

由图8-4可知,在Email中这个Service对应为EasAuthenticatorService,其Intent匹配的Action为“android.accounts.AccountAuthenticator”,其MetaData的name为“android.accounts.AccountAuthenticator”,而MetaData的具体信息保存在resource资源中,在本例中,它指向另外一个xml文件,即eas_authenticator.xml,此文件的内容如图8-5所示。

图8-5  eas_authenticator.xml的内容

图8-5为Email中eas_authenticator.xml的内容。这个xml中的内容是有严格要求的,其中:

·  accountType标签用于指定账户类型(账户类型和具体应用有关,Android并未规定账户的类型)。

·  icon、smallIcon、label和accountPreferences等用于界面显示。例如,当需要用户输入账户信息时,系统会弹出一个Activity,上述几个标签就用于界面显示。详细情况可阅读SDK文档AbstractAccountAuthenticator的说明。

而android.accounts.AccountAuthenticator.xml的内容如图8-6所示。

图8-6  android.accounts.AccountAuthenticator.xml的内容

由图8-6可知,笔者的测试机器上有3个AAS服务,其中同一个uid有两个服务(即uid为10015对应的两个Service)。

提示uid是在为PackageManagerService解析APK文件时赋予APK的。读者不妨自行阅读frameworks/base/services/java/com/android/server/pm/Settings.java中的newUserIdLPw函数。

下面来看AccountManagerService的构造函数。

2. AccountManagerService构造函数分析

[-->AccountManagerService.java::AccountManagerService]

public AccountManagerService(Context context,PackageManager packageManager,

           IAccountAuthenticatorCache authenticatorCache) {

    mContext= context;

   mPackageManager = packageManager;

  synchronized (mCacheLock) {

      //此数据库文件对应为/data/system/accounts.db

     mOpenHelper = new DatabaseHelper(mContext);

  mMessageThread = new HandlerThread("AccountManagerService");

  mMessageThread.start();

  mMessageHandler = new MessageHandler(mMessageThread.getLooper());

  mAuthenticatorCache = authenticatorCache;

   //为AccountAuthenticatorCache设置一个监听者,一旦AAS服务发生变化,

  //AccountManagerService需要做对应处理

  mAuthenticatorCache.setListener(this, null /* Handler */);

  sThis.set(this);

   //监听ACTION_PACKAGE_REMOVED广播

  IntentFilter intentFilter = new IntentFilter();

  intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);

  intentFilter.addDataScheme("package");

  mContext.registerReceiver(new BroadcastReceiver() {

   publicvoid onReceive(Context context1, Intent intent) {

       purgeOldGrants();

   },intentFilter);

    accounts.db数据库中有一个grants表,用于存储授权信息,该信息用于保存哪些Package

    有权限获取账户信息。下面的函数将根据grants表中的数据查询PKMS,以判断这些

    Package是否还存在。如果系统中已经不存在这些Package,则grants表需要更新

   purgeOldGrants();

  accounts.db中有一个accounts表,该表中存储了账户类型和账户名。其中,账户类型

   就是AuthenticatorDescription中的accountType,它和具体应用有关。下面这个

   函数将比较accounts表中的内容与AccountAuthenticatorCache中服务的信息,如果

   AccountAuthenticatorCache已经不存在对应账户类型的服务,则需要删除accounts表

   中的对应项

  validateAccountsAndPopulateCache();

AccountManagerService的构造函数较简单,有兴趣的读者可自行研究以上代码中未详细分析的函数。下面将通过一个具体的例子来分析AccountManagerService的工作流程。

8.3.2  AccountManageraddAccount分析

这一节将分析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所示。

图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() {

          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();

          //创建一个匿名类对象,该匿名类派生自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所示。

图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);

           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所示。

图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();

           //调用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) {

               ......

               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

                  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的对象。

8.3.3  AccountManagerService的分析总结

本节对AccountManagerService进行分析,从技术上说,本节涉及和Java concurrent类相关的知识。另外,对并发编程来说,架构设计是最重要的,因此读者务必阅读脚注中提到的参考书籍《Pattern.Oriented.Software.Architecture.Volume.2》。

就整体而言,AccountManagerService及相关类的设计非常巧妙,读者不妨重温RegisteredServicesCache的结构及addAccount的处理流程并认真体会。

8.4  数据同步管理SyncManager分析

本节将分析ContentService中负责数据同步管理的SyncManager。SynManager和AccountManagerService之间的关系比较紧密。同时,由于数据同步涉及手机中重要数据(例如联系人信息、Email、日历等)的传输,因此它的控制逻辑非常严谨,知识点也比较多,难度相对比AccountManagerService大。

先来认识数据同步管理的核心类SyncManager。

8.4.1  初识SyncManager

SyncManager的构造函数的代码较长,可分段来看。下面先来介绍第一段的代码。

1.  SyncManager介绍

[-->SyncManager.java::SyncManager]

public SyncManager(Context context, booleanfactoryTest) {

   mContext= context;

  //SyncManager中的几位重要成员登场。见下文的解释

  SyncStorageEngine.init(context);

   mSyncStorageEngine =SyncStorageEngine.getSingleton();

   mSyncAdapters = newSyncAdaptersCache(mContext);

   mSyncQueue = newSyncQueue(mSyncStorageEngine, mSyncAdapters);

   HandlerThread syncThread = newHandlerThread("SyncHandlerThread",

                          Process.THREAD_PRIORITY_BACKGROUND);

  syncThread.start();

  mSyncHandler = new SyncHandler(syncThread.getLooper());

  mMainHandler = new Handler(mContext.getMainLooper());

    mSyncAdapters类似AccountManagerService中的AccountAuthenticatorCache,

    它用于管理系统中和SyncService相关的服务信息。下边的函数为mSyncAdapters增加一个

    监听对象,一旦系统中的SyncService发生变化(例如安装了一个提供同步服务的APK包),则

    SyncManager需要针对该服务发起一次同步请求。同步请求由scheduleSync函数发送,

    后文再分析此函数

  mSyncAdapters.setListener(new

             RegisteredServicesCacheListener<SyncAdapterType>() {

           public void onServiceChanged(SyncAdapterType type,

                                              boolean removed) {

               if (!removed) {

                   scheduleSync(null, type.authority, null, 0,false);

        },mSyncHandler);

在以上代码中,首先见到的是SyncManager的几位重要成员,它们之间的关系如图8-11所示。

图8-11  SyncManager成员类图

由图8-11可知, SyncManager的这几位成员的功能大体可分为3部分:

·  左上角是SyncAdaptersCache类,从功能和派生关系上看,它和AccountManagerService中的AccountAuthenticatorCaches类似。SyncAdaptersCache用于管理系统中SyncService服务的信息。在SyncManager中,SyncService的信息用SyncAdapterType类来表示。

·  左下角是SyncQueue和SyncOperation类,SyncOperation代表一次正在执行或等待执行的同步操作,而SyncQueue通过mOperationsMap保存系统中存在的SyncOperation。

·  右半部分是SyncStorageEngine,由于同步操作涉及,重要数据的传输,加之可能耗时较长,所以SyncStorageEngine提供了一些内部类来保存同步操作中的一些信息。例如PendingOperation代表保存在从本地文件中的那些还没有执行完的同步操作的信息。另外,SyncStrorageEngine还需要对同步操作进行一些统计,例如耗电量统计等。SyncStorageEngine包含的内容较多,读者学习完本章后可自行研究相关内容。

SyncManager家族成员的责任分工比较细,后续分析时再具体讨论它们的作用。先接着看SyncManager的构造函数:

[-->SyncManager.java::SyncManager]

   ......

    //创建一个用于广播发送的PendingIntent,该PendingIntent用于和

    //AlarmManagerService交互

    mSyncAlarmIntent= PendingIntent.getBroadcast(

                          mContext, 0, new Intent(ACTION_SYNC_ALARM),0);

   //注册CONNECTIVITY_ACTION广播监听对象,用于同步操作需要使用网络,所以

   //此处需要监听和网络相关的广播

  IntentFilter intentFilter =

             new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);

       context.registerReceiver(mConnectivityIntentReceiver, intentFilter);

   if(!factoryTest) {

         //监听BOOT_COMPLETED广播

        intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);

        context.registerReceiver(mBootCompletedReceiver, intentFilter);

   //监听BACKGROUND_DATA_SETTING_CHANGED广播。该广播与是否允许后台传输数据有关,

   //用户可在Settings应用程序中设置对应选项

  intentFilter =

               new IntentFilter(ConnectivityManager.

                          ACTION_BACKGROUND_DATA_SETTING_CHANGED);

  context.registerReceiver(mBackgroundDataSettingChanged, intentFilter);

   //监视设备存储空间状态广播。由于SyncStorageEngine会保存同步时的一些信息到存储

   //设备中,所以此处需要监视存储设备的状态

  intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);

  intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);

  context.registerReceiver(mStorageIntentReceiver, intentFilter);

   //监听SHUTDOWN广播。此处设置优先级为100,即优先接收此广播

  intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);

  intentFilter.setPriority(100);

  context.registerReceiver(mShutdownIntentReceiver, intentFilter);

   if(!factoryTest) {//和通知服务交互,用于在状态栏上提示用户

      mNotificationMgr = (NotificationManager)

               context.getSystemService(Context.NOTIFICATION_SERVICE);

      //注意,以下函数注册的广播将针对前面创建的mSyncAlarmIntent

      context.registerReceiver(new SyncAlarmIntentReceiver(),

                          newIntentFilter(ACTION_SYNC_ALARM));

   }......

  mPowerManager = (PowerManager)

               context.getSystemService(Context.POWER_SERVICE);

  //创建WakeLock,防止同步过程中掉电

  mHandleAlarmWakeLock =

              mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,

                                             HANDLE_SYNC_ALARM_WAKE_LOCK);

  mHandleAlarmWakeLock.setReferenceCounted(false);

  mSyncManagerWakeLock =

              mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,

                                            SYNC_LOOP_WAKE_LOCK);

  mSyncManagerWakeLock.setReferenceCounted(false);

   //知识点一:监听SyncStorageEngine的状态变化,如下文解释

  mSyncStorageEngine.addStatusChangeListener(

               ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS,

                     newISyncStatusObserver.Stub() {

           public void onStatusChanged(int which) {

               sendCheckAlarmsMessage();

    //知识点二:监视账户的变化。如果用户添加或删除了某个账户,则需要做相应处理。

    //如下文解释

    if(!factoryTest) {

           AccountManager.get(mContext).addOnAccountsUpdatedListener(

                 SyncManager.this,

               mSyncHandler, false);

           onAccountsUpdated(AccountManager.get(mContext).getAccounts());

在以上代码中,有两个重要知识点。

第一, SyncManager为SyncStorageEngine设置了一个状态监听对象。根据前文的描述,在SyncManager家族中,SyncStorageEngine专门负责管理和保存同步服务中绝大部分的信息,所以当外界修改了这些信息时,SyncStorageEngine需要通知状态监听对象。我们可以通过一个例子了解其中的工作流程。下面的setSyncAutomatically函数的作用是设置是否自动同步某个账户的某项数据,代码如下:

[-->ContentService.java::setSyncAutomatically]

public void setSyncAutomatically(Account account,String providerName,

                                       booleansync) {

    ......//检查WRITE_SYNC_SETTINGS权限

   longidentityToken = clearCallingIdentity();

     SyncManager syncManager = getSyncManager();

       if(syncManager != null) {

            通过SyncManager找到SyncStorageEngine,并调用它的

             setSyncAutomatically函数。在其内部会修改对应账户的同步服务信息,然后通知

             监听者,而这个监听者就是SyncManager设置的那个状态监听对象

           syncManager.getSyncStorageEngine().setSyncAutomatically(

                       account, providerName,sync);

     }finally {

         restoreCallingIdentity(identityToken);

在以上代码中,最终调用的是SyncStorageEngine的函数,但SyncManager也会因状态监听对象被触发而做出相应动作。实际上,ContentService中大部分设置同步服务参数的API,其内部实现就是先直接调用SyncStorageEngine的函数,然后再由SyncStorageEngine通知监听对象。读者在阅读代码时,仔细一些就可明白这一关系。

第二,SyncManager将为AccountManager设置一个账户更新监听对象(注意,此处是AccountManager,而不是AccountManagerService。AccountManager这部分功能的代码不是很简单,读者有必要反复研究)。在Android平台上,数据同步和账户的关系非常紧密,并且同一个账户可以对应不同的数据项。例如在EasAuthenticator的addAccount实现中,读者会发现一个Exchange账户可以对应Contacts、Calendar和Email三种不同的数据项。在添加Exchange账户时,还可以选择是否同步其中的某项数据(通过判断实现addAccount时传递的options是否含有对应的同步选项,例如同步邮件数据时需要设置的OPTIONS_EMAIL_SYNC_ENABLED选项)。由于SyncManager和账户之间的这种紧密关系的存在,SyncManager就必须监听手机中账户的变化情况。

提示上述两个知识点涉及的内容都是一些非常细节的问题,本章拟将它们作为小任务,读者可自行研究它们。

下面来认识一下SyncManager家族中的几位主要成员,首先是SyncStorageEngine。

2.  SyncStorageEngine介绍

SyncStorageEngine负责整个同步系统中信息管理方面的工作。先看其init函数代码:

[-->SyncStorageEngine.java::init]

public static void init(Context context) {

  if(sSyncStorageEngine != null) return;

    得到系统中加密文件系统的路径,如果手机中没有加密的文件系统(根据系统属性

    “persist.security.efs.enabled”的值来判断),则返回的路径为/data,

    目前的Android手机大部分都没有加密文件系统,故dataDir为/data

   FiledataDir = Environment.getSecureDataDirectory();

   //创建SyncStorageEngine对象

  sSyncStorageEngine = new SyncStorageEngine(context, dataDir);

而SyncStorageEngine的构造函数代码为:

[-->SyncStorageEngine.java::SyncStorageEngine]

private SyncStorageEngine(Context context, FiledataDir) {

   mContext= context;

  sSyncStorageEngine = this;

   mCal =Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));

   FilesystemDir = new File(dataDir, "system");

   FilesyncDir = new File(systemDir, "sync");

  syncDir.mkdirs();

   //mAccountInfoFile指向/data/system/sync/accounts.xml

  mAccountInfoFile = new AtomicFile(new File(syncDir,"accounts.xml"));

   //mStatusFile指向/data/system/sync/status.bin,该文件记录

  //一些和同步服务相关的状态信息

  mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));

   //mStatusFile指向/data/system/sync/pending.bin,该文件记录了当前处于pending

   //状态的同步请求

  mPendingFile = new AtomicFile(new File(syncDir,"pending.bin"));

   //mStatusFile指向/data/system/sync/stats.bin,该文件记录同步服务管理运行过程

   //中的一些统计信息

  mStatisticsFile = new AtomicFile(new File(syncDir,"stats.bin"));

   解析上述四个文件,从下面的代码看,似乎是先读(readXXX)后写(writeXXX),这是怎么

   回事?答案在于AtomicFile,它内部实际包含两个文件,其中一个用于备份,防止数据丢失。

   感兴趣的读者可以研究AtomicFile类,其实现非常简单

   readAccountInfoLocked();

  readStatusLocked();

  readPendingOperationsLocked();

  readStatisticsLocked();

  readAndDeleteLegacyAccountInfoLocked();

  writeAccountInfoLocked();

  writeStatusLocked();

  writePendingOperationsLocked();

  writeStatisticsLocked();

上述init和SyncStorage的构造函数都比较简单,故不再详述。下面将讨论一些有关accounts.xml的故事。以下是一个真实机器上的accounts.xml文件,如图8-12所示。

图8-12  accounts.xml内容展示

图8-12所示为笔者KindleFire(CM9的ROM)机器中accounts.xml文件的内容,其中两个黑框中内容的作用如下:

·  第一个框中的listen-for-tickles,该标签和Android平台中的Master Sync有关。Master Sync用于控制手机中是否所有账户对应的所有数据项都自动同步。用户可通过ContentResolver setMasterSyncAutomatically进行设置。

·  第二个框代表一个AuthorityInfo。AuthorityInfo记录了账户和SyncService相关的一些信息。此框中的account为笔者的邮箱,type为“com.google”。另外,从图8-12中还可发现,一个账户(包含account和type两个属性)可以对应多种类型的数据项,例如此框中对应的数据项是“com.android.email.provider”,而此框前面一个AuthorityInfo对应的数据项是“com.google.android.apps.books”。AuthorityInfo中的periodicSync用于控制周期同步的时间,单位是秒,默认是86400秒,也就是1天。另外,AuthorityInfo中还有一个重要属性syncable,它的可选值为true、false或unknown(在代码中,这3个值分别对应整型值1、0和-1)。

syncable的unknown状态是个较难理解的概念,它和参数SYNC_EXTRAS_INITIALIZE有关,官方的解释如下:

 Set by theSyncManager to request that the SyncAdapter initialize itself for

 the givenaccount/authority pair. One required initialization step is to

 ensure thatsetIsSyncable()has been called with a >= 0 value.

  When thisflag is set the SyncAdapter does not need to do a full sync,

 though itis allowed to do so.

 publicstatic final String SYNC_EXTRAS_INITIALIZE = "initialize";

由以上解释可知,如果某个SyncService的状态为unknown,那么在启动它时必须传递一个SYNC_EXTRAS_INITIALIZE选项,SyncService解析该选项后即可知自己尚未被初始化。当它完成初始化后,需要调用setIsSyncable函数设置syncable的状态为1。另外,SyncService初始化完成后,是否可接着执行同步请求呢?目前的设计是,它们并不会立即执行同步,需要用户再次发起请求。读者在后续小节中会看到与此相关的处理。

此处先来看setIsSyncable的使用示例,前面分析的EasAuthenticator addAccount中有如下的函数调用:

  //添加完账户后,将设置对应的同步服务状态为1

  ContentResolver.setIsSyncable(account,EmailContent.AUTHORITY, 1);

  ContentResolver.setSyncAutomatically(account,EmailContent.AUTHORITY,

                                                    syncEmail);

在EasAuthenticator中,一旦添加了账户,就会设置对应SyncService的syncable状态为1。SyncManager将根据这个状态做一些处理,例如立即发起一次同步操作。

注意是否设置syncable状态和具体应用有关,图8-12第二个框中的gmail邮件同步服务就没有因为笔者添加了账户而设置syncable为1。

3.  SyncAdaptersCache介绍

再看SyncAdaptersCache,其构造函数代码如下:

[-->SyncAdaptersCache.java::SyncAdaptersCache]

SyncAdaptersCache(Context context) {

   调用基类RegisteredServicesCache的构造函数,其中SERVICE_INTERFACE和

   和SERVICE_META_DATA的值为“android.content.SyncAdapter”,

   ATTRIBUTES_NAME为字符串"sync-adapter"

 super(context, SERVICE_INTERFACE, SERVICE_META_DATA,

              ATTRIBUTES_NAME, sSerializer);

SyncAdaptersCache的基类是RegisteredServicesCache。8.3.1节已经分析过RegisteredServicesCache了,此处不再赘述。下面展示一个实际的例子,如图8-13所示。

图8-13  android.content.SyncAdapter.xml

图8-13列出了笔者KindleFire上安装的同步服务,其中黑框列出的是“om.android.exchange”,该项服务和Android中Exchange应用有关。Exchange的AndroidManifest.xml文件的信息如图8-14所示。

图8-14  Exchange AndroidManifest.xml示意

图8-14列出了Exchange应用所支持的针对邮件提供的同步服务,即EmailSyncAdapterService。该服务会通过meta-data中的resource来描述自己,这部分内容如图8-15所示。

图8-15  syncadapter_email.xml内容展示

图8-15告诉我们,EmailSyncAdapterService对应的账户类型是“com.android.exchange”,需要同步的邮件数据地址由contentAuthority表示,即本例中的“com.android.email.provider”。注意,EmailSyncAdapterService只支持从网络服务端同步数据到本机,故supportsUploading为false。

再看SyncManager家族中最后一位成员SyncQueue。

4.  SyncQueue介绍

SyncQueue用于管理同步操作对象SyncOperation。SyncQueue的构造函数代码为:

[-->SyncQueue.java::SyncQueue]

public SyncQueue(SyncStorageEnginesyncStorageEngine,

                         finalSyncAdaptersCache syncAdapters) {

  mSyncStorageEngine = syncStorageEngine;

   //从SyncStorageEngine中取出上次没有完成的同步操作信息,这类信息由

  //PendingOperations表示

  ArrayList<SyncStorageEngine.PendingOperation> ops

                           = mSyncStorageEngine.getPendingOperations();

   final intN = ops.size();

   for (inti=0; i<N; i++) {

    SyncStorageEngine.PendingOperation op = ops.get(i);

     //从SyncStorageEngine中取出该同步操作的backoff信息

     finalPair<Long, Long> backoff =

                      syncStorageEngine.getBackoff(op.account,op.authority);

     //从SyncAdaptersCache中取出该同步操作对应的同步服务信息,如果同步服务已经不存在,

     //则无须执行后面的流程

    finalRegisteredServicesCache.ServiceInfo<SyncAdapterType>

           syncAdapterInfo= syncAdapters.getServiceInfo(

                           SyncAdapterType.newKey(op.authority,

                             op.account.type));

    if (syncAdapterInfo == null)  continue;

    //构造一个SyncOperation对象

    SyncOperation syncOperation = newSyncOperation(

            op.account, op.syncSource, op.authority, op.extras, 0,

            backoff != null ? backoff.first : 0,

            syncStorageEngine.getDelayUntilTime(op.account, op.authority),

            syncAdapterInfo.type.allowParallelSyncs());

    syncOperation.expedited = op.expedited;

    syncOperation.pendingOperation = op;

     //将SyncOperation对象保存到mOperationsMap变量中

    add(syncOperation, op);

SyncQueue比较简单,其中一个比较难理解的概念是backoff,后文再对此作解释。

至此,SyncManager及相关家族成员已介绍完毕。下面将通过实例分析同步服务的工作流程。在本例中,将同步Email数据,目标同步服务为EmailSyncAdapterService。

8.4.2 ContentResolver 的requestSync分析

ContentResolver提供了一个requestSync函数,用于发起一次数据同步请求。在本例中,该函数的调用方法如下:

Account emailSyncAccount = newAccount("fanping.deng@gmail",

                                               "com.google");

String emailAuthority ="com.android.email.provider";

Bundle emailBundle = new Bundle();

......//为emailBundle添加相关的参数。这些内容和具体的同步服务有关

//发起Email同步请求

ContentResolver.requesetSync(emailSyncAccount,emailAuthority,emailBundle);

1.  客户端发起请求

ContentResolver requestSync的代码如下:

[-->ContentResolver.java::requestSync]

public static void requestSync(Account account,String authority,

                     Bundle extras) {

   //检查extras携带的参数的数据类型,目前只支持float、int和String等几种类型

  validateSyncExtrasBundle(extras);

         //调用ContentService的requestSync函数

        getContentService().requestSync(account, authority, extras);

    }......

与添加账户(addAccount)相比,客户端发起一次同步请求所要做的工作就太简单了。

下面转战ContentService去看它的requestSync函数。

2.  ContentService 的requestSync函数分析

[-->ContentService.java::requestSync]

public void requestSync(Account account, Stringauthority, Bundle extras) {

   ContentResolver.validateSyncExtrasBundle(extras);

    longidentityToken = clearCallingIdentity();

        SyncManager syncManager = getSyncManager();

         if(syncManager != null) {

               //调用syncManager的scheduleSync

               syncManager.scheduleSync(account, authority, extras,

                          0,false);

        }finally {

           restoreCallingIdentity(identityToken);

ContentService将工作转交给SyncManager来完成,其调用的函数是scheduleSync。

(1) SyncManager的scheduleSync函数分析

先行介绍的scheduleSync函数非常重要。

  scheduleSync一共5个参数,其作用分别如下。

  requestedAccount表明要进行同步操作的账户。如果为空,SyncManager将同步所有账户。

 requestedAuthority表明要同步的数据项。如果为空,SyncManager将同步所有数据项。

  extras指定同步操作中的一些参数信息。这部分内容后续分析时再来介绍。

  delay指定本次同步请求是否延迟执行。单位为毫秒。

  onlyThoseWithUnkownSyncableState是否只同步那些处于unknown状态的同步服务。该参数

  在代码中没有注释。结合前面对syncable为unknown的分析,如果该参数为true,则

  本次同步请求的主要作用就是通知同步服务进行初始化操作

public void scheduleSync(Account requestedAccount,String requestedAuthority,

        Bundleextras, long delay,boolean onlyThoseWithUnkownSyncableState)

关于scheduleSync的代码将分段分析,其相关代码如下:

[-->SyncManager.java::scheduleSync]

boopublic void scheduleSync(AccountrequestedAccount,

            StringrequestedAuthority, Bundle extras,

           long delay, boolean onlyThoseWithUnkownSyncableState)

  //判断是否允许后台数据传输

  finalboolean backgroundDataUsageAllowed = !mBootCompleted ||

               getConnectivityManager().getBackgroundDataSetting();

  if (extras== null) extras = new Bundle();

   //下面将解析同步服务中特有的一些参数信息,下面将逐条解释

   //SYNC_EXTRAS_EXPEDITED参数表示是否立即执行。如果设置了该选项,则delay参数不起作用

   //delay参数用于设置延迟执行时间,单位为毫秒

   Booleanexpedited = extras.getBoolean(

                     ContentResolver.SYNC_EXTRAS_EXPEDITED,false);

   if (expedited)

       delay = -1;

   Account[]accounts;

   if (requestedAccount != null) {

      accounts = new Account[]{requestedAccount};

    } ......

   //SYNC_EXTRAS_UPLOAD参数设置本次同步是否对应为上传。从本地同步到服务端为Upload,

   //反之为download

   finalboolean uploadOnly = extras.getBoolean(

                   ContentResolver.SYNC_EXTRAS_UPLOAD, false);

   //SYNC_EXTRAS_MANUAL等同于SYNC_EXTRAS_IGNORE_BACKOFF加

   //SYNC_EXTRAS_IGNORE_SETTINGS

   final boolean manualSync = extras.getBoolean(

                   ContentResolver.SYNC_EXTRAS_MANUAL, false);
   //如果是手动同步,则忽略backoff和settings参数的影响

   if(manualSync) {

       //知识点一:SYNC_EXTRAS_IGNORE_BACKOFF:该参数和backoff有关,见下文的解释

      extras.putBoolean(

                  ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);

       //SYNC_EXTRAS_IGNORE_SETTINGS:忽略设置

      extras.putBoolean(

                  ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);

   finalboolean ignoreSettings = extras.getBoolean(

                  ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS,false);

   //定义本次同步操作的触发源,见下文解释

   int source;

   if(uploadOnly) {

      source = SyncStorageEngine.SOURCE_LOCAL;

   } else if(manualSync) {

      source = SyncStorageEngine.SOURCE_USER;

   } else if(requestedAuthority == null) {

       source = SyncStorageEngine.SOURCE_POLL;

   } else {

       source = SyncStorageEngine.SOURCE_SERVER;

在以上代码中,有两个知识点需要说明。

知识点一和backoff有关:这个词不太好翻译。和其相关的应用场景是,如果本次同步操作执行失败,则尝试休息一会再执行,而backoff在这个场景中的作用就是控制休息时间。由以上代码可知,当用户设置了手动(Manual)参数后,就无须对这次同步操作使用backoff模式。

另外,在后续的代码中,我们会发现和backoff有关的数据被定义成一个Paire<Long,Long>,即backoff对应两个参数。这两个参数到底有什么用呢?笔者在SyncManager代码中找到了一个函数,其参数的命名很容易理解。该函数是setBackoff,原型如下:

[-->SyncManager.java::setBackoff]

public void setBackoff(Account account, StringproviderName,

                           long nextSyncTime, long nextDelay)

在调用这个函数时,Pair<Long,Long>中的两个参数分别对应nextSyncTime和nextDelay,所以,Pair中的第一个参数对应nextSyncTime,第二个参数对应nextDelay。backoff的计算中实际上存在着一种算法。它是什么呢?读者不妨先研究setBackoff,然后再和我们一起分享。

知识点二和SyncStorageEngine定义的触发源有关。说白了,触发源就是描述该次同步操作是因何而起的。SyncStorageEngine一共定义了4种类型的源,这里笔者直接展示其原文解释:

/* Enumvalue for a local-initiated sync. */

 public static final int SOURCE_LOCAL = 1;

/**  Enum value for a poll-based sync (e.g., upon connectionto network)*/

 public static final int SOURCE_POLL = 2;

/* Enumvalue for a user-initiated sync. */

  public static final int SOURCE_USER = 3;

/* Enumvalue for a periodic sync. */

publicstatic final int SOURCE_PERIODIC = 4;

触发源的作用主要是为了SyncStorageEngine的统计工作。本节不打算深究这部分内容,感兴趣的读者可在学习完本节后自行研究。

关于scheduleSync下一阶段的工作,代码如下:

[-->SyncManager.java::scheduleSync]

   //从SyncAdaptersCache中取出所有SyncService信息

   final HashSet<String>syncableAuthorities = new HashSet<String>();

   for(RegisteredServicesCache.ServiceInfo<SyncAdapterType>

               syncAdapter :mSyncAdapters.getAllServices()) {

           syncableAuthorities.add(syncAdapter.type.authority);

   //如果指定了本次同步的authority,则从上述同步服务信息中找到满足要求的SyncService

   if(requestedAuthority != null) {

        final boolean hasSyncAdapter =

                      syncableAuthorities.contains(requestedAuthority);

           syncableAuthorities.clear();

         if(hasSyncAdapter) syncableAuthorities.add(requestedAuthority);

   finalboolean masterSyncAutomatically =

                           mSyncStorageEngine.getMasterSyncAutomatically();

   for(String authority : syncableAuthorities) {

       for(Account account : accounts) {

         //取出AuthorityInfo中的syncable状态,如果为1,则syncable为true,

        //如果为-1,则状态为unknown

        intisSyncable = mSyncStorageEngine.getIsSyncable(

                                     account,authority);

        if(isSyncable == 0) continue;//syncable为false,则不能进行同步操作

       final RegisteredServicesCache.ServiceInfo<SyncAdapterType>

                  syncAdapterInfo =

                    mSyncAdapters.getServiceInfo(

                             SyncAdapterType.newKey(authority, account.type));

       ......

        //有些同步服务支持多路并发同步操作

       final boolean allowParallelSyncs =

                          syncAdapterInfo.type.allowParallelSyncs();

       finalboolean isAlwaysSyncable = syncAdapterInfo.type.

                                              isAlwaysSyncable();

      //如果该同步服务此时的状态为unknown,而它又是永远可同步的(AlwaysSyncable),

      //那么通过setIsSyncable设置该服务的状态为1

       if(isSyncable < 0 && isAlwaysSyncable) {

            mSyncStorageEngine.setIsSyncable(account, authority, 1);

            isSyncable = 1;

      //如果只操作unknow状态的同步服务,并且该服务的状态不是unknown,则不允许后续操作

      if(onlyThoseWithUnkownSyncableState && isSyncable >= 0)

         continue;

      //如果此同步服务不支持上传,而本次同步又需要上传,则不允许后续操作

       if(!syncAdapterInfo.type.supportsUploading() && uploadOnly)

            continue;

      //判断是否允许执行本次同步操作。如果同步服务状态为unknown,则总是允许发起同步请求,

     //因为这时的同步请求只是为了初始化SyncService

      boolean syncAllowed = (isSyncable < 0) ||ignoreSettings

                 || (backgroundDataUsageAllowed && masterSyncAutomatically

                 && mSyncStorageEngine.getSyncAutomatically(

                              account,authority));

        ......

      //取出对应的backoff参数

     Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(

                                  account,authority);

      //获取延迟执行时间

      longdelayUntil = mSyncStorageEngine.getDelayUntilTime(

                                  account,authority);

      finallong backoffTime = backoff != null ? backoff.first : 0;

      if(isSyncable < 0) {

           Bundle newExtras = new Bundle();

           //如果syncable状态为unknown,则需要设置一个特殊的参数,即

           //SYNC_EXTRAS_INITIALIZE,它将通知SyncService进行初始化操作

           newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);

           scheduleSyncOperation(

                new SyncOperation(account, source, authority, newExtras, 0,

                         backoffTime,delayUntil,allowParallelSyncs));

        if(!onlyThoseWithUnkownSyncableState)

            scheduleSyncOperation(

                   new SyncOperation(account,source, authority, extras, delay,

                         backoffTime, delayUntil,allowParallelSyncs));

         }//for循环结束

scheduleSync函数较复杂,难点在于其策略控制。建议读者反复阅读这部分内容。

scheduleSync最后将构造一个SyncOperation对象,并调用scheduleSyncOperation处理它。scheduleSyncOperation内部会将这个SyncOperation对象保存到mSyncQueue中,然后发送MESSAGE_CHECK_ALARMS消息让mSyncHandler去处理。由于scheduleSyncOperation函数比较简单,因此下面将直接去mSyncHandler的handleMessage函数中分析MESSAGE_CHECK_ALARMS的处理过程。

(2) 处理MESSAGE_CHECK_ALARMS消息

SyncHandler的handleMessage代码如下:

[-->SyncManager.java::SyncHandler:handleMessage]

public void handleMessage(Message msg) {

   longearliestFuturePollTime = Long.MAX_VALUE;

   longnextPendingSyncTime = Long.MAX_VALUE;

          waitUntilReadyToRun();

          mDataConnectionIsConnected = readDataConnectionState();

          //获得WakeLock,防止在同步过程中掉电

          mSyncManagerWakeLock.acquire();

          //处理周期同步的操作

           earliestFuturePollTime= scheduleReadyPeriodicSyncs();

          switch (msg.what) {

             ......

             case SyncHandler.MESSAGE_CHECK_ALARMS:

                //调用maybeStartNextSyncLocked函数,返回一个时间。见下文解释

                nextPendingSyncTime = maybeStartNextSyncLocked();

               break;

             ......

              }//switch结束

     } finally{

         manageSyncNotificationLocked();

          将上边函数调用的返回值传递给manageSyncAlarmLocked,该函数内部与

          AlarmManagerService交互,其实就是定义一个定时提醒。在Alarm超时后,就会广播

          在SyncManager构造函数中定义的那个PendingIntent mSyncAlarmIntent,

          而SyncManager收到该广播后又会做对应处理。相关内容读者可自行阅读

         manageSyncAlarmLocked(earliestFuturePollTime,nextPendingSyncTime);

         mSyncTimeTracker.update();

         mSyncManagerWakeLock.release();

如以上代码所述,MESSAGE_CHECK_ALARMS消息的处理就是调用maybeStartNextSyncLocked函数。这个函数内容较繁琐,它主要做了以下几项工作。

·  检查SyncQueue中保存的同步操作对象SyncOperation,判断它们对应的同步服务的状态是否为false,如果为false,则不允许执行该同步操作。

·  查询ConnectivityManagerService以判断目标同步服务是否使用了网络。如果该服务当前没有使用网络,则不允许执行该同步操作。

·  判断同步操作对象的执行时间是否已到,如果未到,则不允许执行该操作。

·  将通过上述判断的同步操作对象SyncOperation与当前系统中正在执行的同步操作上下文对象进行比较。系统当前正在执行的同步操作上下文对象对应的数据类是ActiveSyncContext,它是在同步操作对象之上的一个封装,包含了能和同步服务交互的接口。由于并非所有同步服务都支持多路并发同步操作,因此这里需做一些处理,以避免不必要的同步操作。另外,如一个仅对应初始化同步服务的同步操作执行时间过长(由系统属性“sync.max_time_per_sync”控制,默认是5分钟),系统也需做一些处理。

提示maybeStartNextSyncLocked是笔者在本节留给读者自行分析的函数中最难的一个。读者务必阅读完下面的分析后,尝试去研究此函数。

通过上述层层考验后,manageSyncAlarmLocked最后将调用dispatchSyncOperation真正去派发一个同步操作。下面来看dispatchSyncOperation的代码。

[-->SyncManager.java::dispatchSyncOperation]

private booleandispatchSyncOperation(SyncOperation op) {

  SyncAdapterType syncAdapterType = SyncAdapterType.

                            newKey(op.authority,op.account.type);

  RegisteredServicesCache.ServiceInfo<SyncAdapterType>

          syncAdapterInfo=

                   mSyncAdapters.getServiceInfo(syncAdapterType);

   ......

   //构造一个ActiveSyncContext对象,它就是前面提到的同步操作上下文对象

   ActiveSyncContextactiveSyncContext =

          new ActiveSyncContext(op,

                 insertStartSyncEvent(op), syncAdapterInfo.uid);

   activeSyncContext.mSyncInfo =

               mSyncStorageEngine.addActiveSync(activeSyncContext);

    // mActiveSyncContexts保存了当前系统中所有的ActiveSyncContext对象

   mActiveSyncContexts.add(activeSyncContext);

    //为该对象绑定到具体的同步服务上

    if(!activeSyncContext.bindToSyncAdapter(syncAdapterInfo)) {

         closeActiveSyncContext(activeSyncContext);

         return false;

     returntrue;

ActiveSyncContext是SyncManager和同步服务交互的关键类,其家族图谱如图8-16所示。

图8-16  ActiveSyncContext的 UML类图

图8-16中的ActiveSyncContext和图8-8中的Session非常像。ActiveSyncContext的主要工作包括下面两部分。

·  它将首先通过bindService方式启动SyncService,并在onServiceConnected函数中得到用于和SyncService交互的接口对象,即参与Binder通信的ISyncAdapterBp端。

·  ActiveSyncContext是ISyncContext接口的Binder通信的Bn端,它在调用ISyncAdapter的startSync时,会把自己传递给同步服务。同步服务得到的当然是ISyncContext的Bp端对象。当同步服务完成此次同步操作后就会调用ISyncContext 的Bp端对象的onFinished函数以通知ActiveSyncContext同步操作的执行结果。

下面再看第一部分的工作。

(3) ActiveSyncContext派发请求

[-->SyncManager.java::ActiveSyncContext.bindToSyncAdapter]

booleanbindToSyncAdapter(RegisteredServicesCache.ServiceInfo info) {

    Intentintent = new Intent();

   intent.setAction("android.content.SyncAdapter");

    //设置目标同步服务的ComponentName

   intent.setComponent(info.componentName);

    intent.putExtra(Intent.EXTRA_CLIENT_LABEL,

                   com.android.internal.R.string.sync_binding_label);

   intent.putExtra(Intent.EXTRA_CLIENT_INTENT,

                 PendingIntent.getActivity(

                   mContext, 0,

                   new Intent(Settings.ACTION_SYNC_SETTINGS),0));

      mBound= true;

      //调用bindService启动指定的同步服务

      finalboolean bindResult = mContext.bindService(intent, this,

                   Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND

                   | Context.BIND_ALLOW_OOM_MANAGEMENT);

       if(!bindResult)

           mBound = false;

      returnbindResult;

当目标SyncService从其onBind函数返回后,ActiveSyncContext的onServiceConnected将被调用,该函数的内部处理流程如下:

[-->SyncManager.java::ActiveSyncContext.onServiceConnected]

public void onServiceConnected(ComponentName name,IBinder service) {

    Messagemsg = mSyncHandler.obtainMessage();

    msg.what= SyncHandler.MESSAGE_SERVICE_CONNECTED;

    //构造一个ServiceConnectionData对象,并发送MESSAGE_SERVICE_CONNECTED消息

    //给mSyncHandler。第二个参数就是SyncService在onBind函数中返回的ISyncAdapter的

    //Binder通信对象。不过在ActiveSyncContext中,它是Bp端

    msg.obj= new ServiceConnectionData(this,

                          ISyncAdapter.Stub.asInterface(service));

   mSyncHandler.sendMessage(msg);

[-->SyncManager.java::SyncHandler.handleMessage]

case SyncHandler.MESSAGE_SERVICE_CONNECTED: {

    ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;

     if(isSyncStillActive(msgData.activeSyncContext))

          //调用runBoundToSyncAdapter函数处理

         runBoundToSyncAdapter(msgData.activeSyncContext,

                             msgData.syncAdapter);

    break;

[-->SyncManager.java::runBoundToSyncAdapter]

private void runBoundToSyncAdapter(final ActiveSyncContextactiveSyncContext,

      ISyncAdapter syncAdapter) {

   activeSyncContext.mSyncAdapter = syncAdapter;

    finalSyncOperation syncOperation = activeSyncContext.mSyncOperation;

        activeSyncContext.mIsLinkedToDeath = true;

        syncAdapter.asBinder().linkToDeath(activeSyncContext, 0);

        //调用目标同步服务的startSync函数

        syncAdapter.startSync(activeSyncContext, syncOperation.authority,

                   syncOperation.account, syncOperation.extras);

       } ......

对SynManager工作的分析到此为止,下面将分析目标同步服务。

3. EmailSyncAdapterService处理请求

在本例中,目标同步服务位于EmailSyncAdapterService中,先看它通过onBind函数返回给ActiveSyncContext的是什么。

(1) onBind分析

[-->EmailSyncAdapterService.java::onBind]

public IBinder onBind(Intent intent) {

     //sSyncAdapter是EmailSyncAdapterService的内部类对象,见下文解释

      returnsSyncAdapter.getSyncAdapterBinder();

在以上代码中,sSyncAdapter的类型是EmailSyncAdapterService中的内部类SyncAdapterImpl。它的派生关系如图8-17所示。

图8-17  SyncAdapterImpl派生关系图

有图8-17可知:

·  AbstractThreadSyncAdapter是核心类,其内部有一个成员变量mISyncAdapterIml,该变量用于和ActiveSyncContext交互,是ISyncAdapter Binder通信的Bn端。该对象也是以上代码中onBind函数的返回值。

·  SyncThread从Thread派生。从这一点可看出,同步服务将创建工作线程来执行具体的同步操作。AbstractThreadSyncAdapter中的mSyncThreads保存该同步服务中所有的SyncThread对象。

·  同步操作的结果将通过SyncResult返给SyncManager。

再看SyncManager runBoundToSyncAdapter函数最后调用的startSync函数。

(2) startSync分析

在SyncService中,首先被调用的函数是ISyncAdapterImpl的startSync函数,其代码为:

[-->AbstractThreadedSyncAdapter.java::ISyncAdapterImpl.startSync]

public void startSync(ISyncContext syncContext,String authority,

              Account account,Bundle extras) {

     //构造一个SyncContext对象,用于保存上下文信息

     finalSyncContext syncContextClient = new SyncContext(syncContext);

     booleanalreadyInProgress;

     finalAccount threadsKey = toSyncKey(account);

    synchronized (mSyncThreadLock) {

        //判断是否存在已经在执行的SyncThread

        if(!mSyncThreads.containsKey(threadsKey)) {

            if (mAutoInitialize

                && extras != null&& extras.getBoolean(

                 ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {

              //一般而言,mAutoInitialize都为true,表示同步服务支持自动初始化

             //如果该服务对应的syncable状态为unknown,则重新设置syncable为1

              if (ContentResolver.getIsSyncable(account, authority) < 0)

                       ContentResolver.setIsSyncable(account,authority, 1);

                //直接返回,不再做后续的处理,实际上后续的流程是可以继续进行的

                syncContextClient.onFinished(new SyncResult());

               return;

                //创建一个新的SyncThread对象

               SyncThread syncThread = new SyncThread(

                           "SyncAdapterThread-" +

                                      mNumSyncStarts.incrementAndGet(),

                        syncContextClient,authority, account, extras);

                mSyncThreads.put(threadsKey, syncThread);

                syncThread.start();//启动工作线程

                alreadyInProgress = false;

           }else {

                alreadyInProgress = true;

   if(alreadyInProgress)

        syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);

假如尚未匹配的工作线程(根据account生成一个key作为标示来查找是否已经存在对应的工作线程),SyncService将创建一个SyncThread,其run函数代码是:

[-->AbstractThreadedSyncAdapter.java::ISyncAdapterImpl.run]

public void run() {

    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

    SyncResult syncResult = new SyncResult();

    ContentProviderClient provider = null;

          if(isCanceled()) return;

          //获得同步操作指定的ContentProvider,provider是ContentProviderClient

         //类型,用于和目标ContentProvider交互

         provider = mContext.getContentResolver().

                       acquireContentProviderClient(mAuthority);

          if (provider != null) {

             //调用AbstractThreadedSyncAdapter子类的onPerformSync函数

             AbstractThreadedSyncAdapter.this.onPerformSync(mAccount,

                               mExtras,mAuthority,provider, syncResult);

              } else

                  syncResult.databaseError = true;

      }finally {

          if (provider != null)

                   provider.release();

          if (!isCanceled()) //通知结果

              mSyncContext.onFinished(syncResult);

        //工作完成,将该线程从mSyncThreads中移除

       synchronized (mSyncThreadLock) {

            mSyncThreads.remove(mThreadsKey);

来看AbstractThreadedSyncAdapter子类实现的onPeroformSync函数,在本例中,子类是SyncAdapterImpl,代码如下:

[-->EmailSyncAdapterService.java::SyncAdapterImpl.onPerformSync]

public void onPerformSync(Account account, Bundleextras, String authority,

         ContentProviderClient provider,SyncResult syncResult) {

        //调用EmailSyncAdapterService performSync完成真正的同步,这部分代码和

       //Email业务逻辑相关,此处不再深入研究

       EmailSyncAdapterService.performSync(mContext, account, extras,

                     authority, provider,syncResult);

   }......

执行完onPerformSync函数后,ISyncAdapterImpl.run返回前会调用mSyncContext.onFinished函数,向位于SyncManager中的ActiveSyncContext通知同步操作的结果。读者可自行研究这部分内容。

4.  ContentResolver requestSync分析总结

总结requestSync的工作流程,如图8-18所示。

图8-18  requestSync流程

由图8-18可知,requestSync涉及的对象及调用流程比较繁琐。但从技术上看,则没有什么需要特别注意的地方。

8.4.3  数据同步管理SyncManager分析总结

本节对ContentService中第二个主要功能即数据同步管理SyncManager进行了研究,这部分内容主要包括3个方面:

·  介绍SyncManager及其相关成员的作用。

·  通过requestSync展示了SyncManager各个模块的作用及交互过程。

·  穿插于上述两方面之中的是数据同步的一些处理策略和规则。笔者未对这部分内容展开更细致的讨论。感兴趣的读者可自行学习。

8.5  本章学习指导

本章对ContentService和AccountManagerService进行了研究,在学习过程中,需注意以下几方面。

·  ContentService包括两个不同部分,一个是数据更新通知机制,另一个是同步服务管理。在这两部分中,ContentService承担了数据更新通知机制的工作,同步服务管理的工作则委托给SyncManager来完成。基于这种分工,本章先分析了ContentService的数据更新通知机制。这部分内容非常简单,读者应能轻松掌握。不过,作为深入学习的入口,笔者有意留了几个问题,旨在让读者仔细思考。

·  由于同步服务管理和AccountManagerService关系密切,因此本章先分析了AccountManagerService。在这部分代码中,读者需要了解RegisteredServicesCache的作用。另外,感兴趣的读者也可以学习如何编写AuthenticatorService,相关文档在SDK关于AbstractAccountAuthenticator类的说明中。

·  SyncManager是本章理解难度最大的知识点。笔者觉得,其难度主要体现在同步策略上。另外,缺乏相关文档资料的参考也是导致学习SyncManager难度较大的原因之一。读者在把本节内容搞清楚的基础上,可自行研究本章没有提及的内容。另外,读者如想学习如何编写同步服务,可参考SDK文档中关于AbstractThreadSyncAdapter的说明。

除上述内容外,AccountManager的ddAccount函数实现中所使用的Java concurrent类,也是读者必须学习并掌握的基础知识。

8.6  本章小结

本章对ContentService和AccountManagerService进行了较为深入的探讨,其中:

·  8.2和8.4节分别分析了ContentService中数据更新通知机制的实现及同步服务管理方面的工作。

·  8.3节分析了AccountManagerService及addAccount的实现。

本章留出以下问题供读者自行研究,这些问题包括:

·  8.2节最后提到的开放性问题和Cursor query中ContentObserver的使用分析。

·  8.4节涉及SyncManager的同步请求处理的策略和maybeStartNextSyncLocked函数分析等。另外,读者如果有兴趣,不妨研究一下SyncStorageEngine中同步信息的统计等内容。


[①] 从设计模式角度来说,这属于ActiveObject模式。详细内容可阅读《Pattern.Oriented.Software.Architecture.Volume.2》的第2章“Concurrency Patterns”。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK