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

由于《深入理解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) {

     try {

          //创建一个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函数将被调用

  */

  try {   

           /*

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

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

           */

           getContentService().registerContentObserver(uri,

                                   notifyForDescendents,

                                   observer.getContentObserver());

        } ......

 }

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

1.  ContentObserver介绍

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

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第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用于控制是否需要发起一次数据同步请求

  try {

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

  try {

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

              try {

                   /*

                     调用客户端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所示。

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第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();

        try{

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

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第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) {

      try {

          /*

            调用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;

   try {

        //解析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所示。

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第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所示。

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第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所示。

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第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所示。

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第7张图片

图8-7  AmsTask继承关系

由图8-7可知:

·  AmsTask继承自FutureTask,并实现了AccountManagerFuture接口。FutureTask是Java concurrent库中一个常用的类。AmsTask定义了一个doWork虚函数,该函数必须由子类来实现。

·  一个AmsTask对象中有一个mResponse成员,该成员的类型是AmsTask中的内部类Response。从Response的派生关系可知,Response将参与Binder通信,并且它是Binder通信的Bn端。而AccountManagerService的addAccount将得到它的Bp端对象。当添加完账户后,AccountManagerService会通过这个Bp端对象的onResult或onError函数向Response通知处理结果。

(2) AmsTask匿名类处理分析

AccountManager的addAccount最终返回给客户端的是一个AmsTask的子类,首先来了解它的构造函数,其代码如下:

[-->AccountManager.java::AmsTask]

public AmsTask(Activity activity, Handler handler,

                       AccountManagerCallback<Bundle> callback){

   ......//调用基类构造函数

   //保存客户端传递的参数

    mHandler= handler;

   mCallback = callback;

   mActivity = activity;

   mResponse = new Response();//构造一个Response对象,并保存到mResponse中

}

下一步调用的是这个匿名类的start函数,代码如下:

[-->AccountManager.java::AmsTask.start]

public final AccountManagerFuture<Bundle>start() {

   try {

          doWork(); //调用匿名类实现的doWork函数

     }......

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

 [深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第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);

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

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第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();

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

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第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所示。

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第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();

   try {

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

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第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所示。

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第13张图片

图8-13  android.content.SyncAdapter.xml

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

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第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);

   try {

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

     try {

        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;

 

   try {

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

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第15张图片

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

    try {

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

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第16张图片

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

    try {

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

  try {

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

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

       EmailSyncAdapterService.performSync(mContext, account, extras,

                     authority, provider,syncResult);

   }......

}

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

4.  ContentResolver requestSync分析总结

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

[深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService_第17张图片

图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”。

你可能感兴趣的:([深入理解Android卷二 全文-第八章]深入理解ContentService和AccountManagerService)