8.4 数据同步管理SyncManager分析

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

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

8.4.1 初识SyncManager

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

1.SyncManager介绍

这部分的代码如下:

[—>SyncManager.java:SyncManager]


public SyncManager(Context context, boolean factoryTest){

mContext=context;

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

SyncStorageEngine.init(context);

mSyncStorageEngine=SyncStorageEngine.getSingleton();

mSyncAdapters=new SyncAdaptersCache(mContext);

mSyncQueue=new SyncQueue(mSyncStorageEngine, mSyncAdapters);

HandlerThread syncThread=new HandlerThread("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.4 数据同步管理SyncManager分析 - 图1

图 8-11 SyncManager成员类图

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

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

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

右半部分是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(),

new IntentFilter(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,

new ISyncStatusObserver.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,

boolean sync){

……//检查WRITE_SYNC_SETTINGS权限

long identityToken=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

*/

File dataDir=Environment.getSecureDataDirectory();

//创建SyncStorageEngine对象

sSyncStorageEngine=new SyncStorageEngine(context, dataDir);

}


SyncStorageEngine的构造函数代码为:

[—>SyncStorageEngine. java:SyncStorageEngine]


private SyncStorageEngine(Context context, File dataDir){

mContext=context;

sSyncStorageEngine=this;

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

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

File syncDir=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"));

/*

解析上述4个文件,从下面的代码看,似乎是先读(read×××)后写(write×××),这是怎

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

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

*/

readAccountInfoLocked();

readStatusLocked();

readPendingOperationsLocked();

readStatisticsLocked();

readAndDeleteLegacyAccountInfoLocked();

writeAccountInfoLocked();

writeStatusLocked();

writePendingOperationsLocked();writeStatisticsLocked();

}


上述init和SyncStorageEngine的构造函数都比较简单,故不再详述。下面将分析accounts.xml。以下是笔者Kindle Fire(CM9的ROM)机器中的accounts.xml文件,如图8-12所示。

图8-12中两个黑框中内容的作用如下:

第一个框中的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)。

8.4 数据同步管理SyncManager分析 - 图2

图 8-12 accounts.xml内容展示

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


/**

Set by the SyncManager to request that the SyncAdapter initialize itself for

the given account/authority pair.One required initialization step is to

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

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

though it is allowed to do so.

*/

public static 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节已经分析过Registered-ServicesCache了,此处不再赘述。下面展示一个实际的例子,如图8-13所示。

8.4 数据同步管理SyncManager分析 - 图3

图 8-13 android.content.SyncAdapter.xml

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

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

8.4 数据同步管理SyncManager分析 - 图4

图 8-14 Exchange AndroidManifest.xml文件示意

8.4 数据同步管理SyncManager分析 - 图5

图 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(SyncStorageEngine syncStorageEngine,

final SyncAdaptersCache syncAdapters){

mSyncStorageEngine=syncStorageEngine;

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

//PendingOperations表示

ArrayList<SyncStorageEngine.PendingOperation>ops

=mSyncStorageEngine.getPendingOperations();

final int N=ops.size();

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

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

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

final Pair<Long, Long>backoff=

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

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

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

final RegisteredServicesCache.ServiceInfo<SyncAdapterType>

syncAdapterInfo=syncAdapters.getServiceInfo(

SyncAdapterType.newKey(op.authority,

op.account.type));

if(syncAdapterInfo==null)continue;

//构造一个SyncOperation对象

SyncOperation syncOperation=new SyncOperation(

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。