8.4.2 ContentResolver的requestSync分析
ContentResolver提供了一个requestSync函数,用于发起一次数据同步请求。在本例中,该函数的调用方法如下:
Account emailSyncAccount=new Account("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, String authority, Bundle extras){
ContentResolver.validateSyncExtrasBundle(extras);
long identityToken=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,
Bundle extras, long delay, boolean onlyThoseWithUnkownSyncableState)
关于scheduleSync的代码将分段分析,其相关代码如下:
[—>SyncManager. java:scheduleSync]
boopublic void scheduleSync(Account requestedAccount,
String requestedAuthority, Bundle extras,
long delay, boolean onlyThoseWithUnkownSyncableState)
//判断是否允许后台数据传输
final boolean backgroundDataUsageAllowed=!mBootCompleted||
getConnectivityManager().getBackgroundDataSetting();
if(extras==null)extras=new Bundle();
//下面将解析同步服务中特有的一些参数信息,下面将逐条解释
//SYNC_EXTRAS_EXPEDITED参数表示是否立即执行。如果设置了该选项,则delay参数不起作用
//delay参数用于设置延迟执行时间,单位为毫秒
Boolean expedited=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
final boolean 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);
}
final boolean 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函数,其参数的命名很容易理解。setBackoff函数的原型如下:
[—>SyncManager. java:setBackoff]
public void setBackoff(Account account, String providerName,
long nextSyncTime, long nextDelay)
在调用这个函数时,Pair<Long, Long>中的两个参数分别对应nextSyncTime和nextDelay,所以,Pair中的第一个参数对应nextSyncTime,第二个参数对应nextDelay。backoff的计算中实际上存在着一种算法。读者不妨先研究setBackoff,然后再和我们一起分享这个算法的相关知识。
知识点二和SyncStorageEngine定义的触发源有关。说白了,触发源就是描述该次同步操作是因何而起的。SyncStorageEngine一共定义了4种类型的触发源,这里笔者直接展示其原文解释:
/Enum value for a local-initiated sync./
public static final int SOURCE_LOCAL=1;
/*Enum value for a poll-based sync(e.g.,upon connection to network)/
public static final int SOURCE_POLL=2;
/Enum value for a user-initiated sync./
public static final int SOURCE_USER=3;
/Enum value for a periodic sync./
public static 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);
}
final boolean masterSyncAutomatically=
mSyncStorageEngine.getMasterSyncAutomatically();
for(String authority:syncableAuthorities){
for(Account account:accounts){
//取出AuthorityInfo中的syncable属性值,如果为1,则其状态为true,
//如果为-1,则其状态为unknown
int isSyncable=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();
final boolean 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);
//获取延迟执行时间
long delayUntil=mSyncStorageEngine.getDelayUntilTime(
account, authority);
final long 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){
long earliestFuturePollTime=Long.MAX_VALUE;
long nextPendingSyncTime=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消息的处理就是调用maybeStartNext-SyncLocked函数。这个函数内容较烦琐,它主要做了以下几项工作。
检查SyncQueue中保存的同步操作对象SyncOperation,判断它们对应的同步服务的状态是否为false,如果为false,则不允许执行该同步操作。
查询ConnectivityManagerService以判断目标同步服务是否使用了网络。如果该服务当前没有使用网络,则不允许执行该同步操作。
判断同步操作对象的执行时间是否已到,如果未到,则不允许执行该操作。
将通过上述判断的同步操作对象SyncOperation与当前系统中正在执行的同步操作上下文对象进行比较。系统当前正在执行的同步操作上下文对象对应的数据类是ActiveSyncContext,它是在同步操作对象之上的一个封装,包含了能和同步服务交互的接口。由于并非所有同步服务都支持多路并发同步操作,因此这里需做一些处理,以避免不必要的同步操作。另外,如果一个仅对应初始化同步服务的同步操作执行时间过长(由系统属性“sync.max_time_per_sync”控制,默认是5分钟),则系统需要做一些处理。
提示 maybeStartNextSyncLocked是笔者在本节留给读者自行分析的函数中最难的一个。读
者务必阅读完下面的分析后,尝试去研究此函数。
1通过上述层层考验后,manageSyncAlarmLocked最后将调用dispatchSyncOperation真正去派发一个同步操作。下面来看dispatchSyncOperation的代码。
[—>SyncManager. java:dispatchSyncOperation]
private boolean dispatchSyncOperation(SyncOperation op){
SyncAdapterType syncAdapterType=SyncAdapterType.
newKey(op. authority, op.account.type);
RegisteredServicesCache. ServiceInfo<SyncAdapterType>
syncAdapterInfo=
mSyncAdapters. getServiceInfo(syncAdapterType);
……
//构造一个ActiveSyncContext对象,它就是前面提到的同步操作上下文对象
ActiveSyncContext activeSyncContext=
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;
}
return true;
}
ActiveSyncContext是SyncManager和同步服务交互的关键类,其家族图谱如图8-16所示。
图 8-16 ActiveSyncContext的UML类图
图8-16中的ActiveSyncContext和图8-8中的Session非常像。ActiveSyncContext的主要工作包括下面两部分。
它将首先通过bindService方式启动SyncService,并在onServiceConnected函数中得到用于和SyncService交互的接口对象,即参与Binder通信的ISyncAdapter Bp端。
ActiveSyncContext是ISyncContext接口的Binder通信的Bn端,它在调用ISync-Adapter的startSync时,会把自己传递给同步服务。同步服务得到的当然是ISyncContext的Bp端对象。当同步服务完成此次同步操作后就会调用ISyncContext的Bp端对象的onFinished函数以通知ActiveSyncContext同步操作的执行结果。
下面来看第一部分的工作。
(3)ActiveSyncContext派发请求
这部分的代码如下:
[—>SyncManager. java:ActiveSyncContext.bindToSyncAdapter]
boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info){
Intent intent=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启动指定的同步服务
final boolean bindResult=mContext.bindService(intent, this,
Context.BIND_AUTO_CREATE|Context.BIND_NOT_FOREGROUND
|Context.BIND_ALLOW_OOM_MANAGEMENT);
if(!bindResult)
mBound=false;
return bindResult;
}
当目标SyncService从其onBind函数返回后,ActiveSyncContext的onServiceConnected将被调用,该函数的内部处理流程如下:
[—>SyncManager. java:ActiveSyncContext.onServiceConnected]
public void onServiceConnected(ComponentName name, IBinder service){
Message msg=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 ActiveSyncContext activeSyncContext,
ISyncAdapter syncAdapter){
activeSyncContext.mSyncAdapter=syncAdapter;
final SyncOperation 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的内部类对象,见下文解释
return sSyncAdapter.getSyncAdapterBinder();
}
在以上代码中,sSyncAdapter的类型是EmailSyncAdapterService中的内部类Sync-AdapterImpl。它的派生关系如图8-17所示。
由图8-17可知:
AbstractThreadSyncAdapter是核心类,其内部有一个成员变量mISyncAdapterIml,该变量用于和ActiveSyncContext交互,是ISyncAdapter Binder通信的Bn端。该对象也是以上代码中onBind函数的返回值。
SyncThread从Thread派生。从这一点可看出,同步服务将创建工作线程来执行具体的同步操作。AbstractThreadSyncAdapter中的mSyncThreads保存该同步服务中所有的SyncThread对象。
图 8-17 SyncAdapterImpl派生关系图
同步操作的结果将通过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对象,用于保存上下文信息
final SyncContext syncContextClient=new SyncContext(syncContext);
boolean alreadyInProgress;
final Account 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子类实现的onPerformSync函数,在本例中,子类是SyncAdapterImpl,代码如下:
[—>EmailSyncAdapterService. java:SyncAdapterImpl.onPerformSync]
public void onPerformSync(Account account, Bundle extras, 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所示。
图 8-18 requestSync流
由图8-18可知,requestSync涉及的对象及调用流程比较烦琐。但从技术上看,则没有什么需要特别注意的地方。