SyncAdapter同步机制
官方文档:https://developer.android.com/training/sync-adapters/index.html
1.简介
在Android设备和web服务器之间同步数据会使你的应用更实用,更吸引用户,例如,将手机数据传到服务端实现数据备份,将数据从服务端取回让用户能够脱机使用。在某些情况下,用户会发现这样会更方便:通过web修改信息然后在手机上就可以继续使用,或者隔一段时间将手机上的数据上传到一个总存储区。
虽然你可以在应用中设计自己的数据传输系统,但也应该考虑一下用Android的sync adater框架。它可以协助管理和自动发起数据传输,也可以协调不同应用的同步操作。使用这个同步框架比自己设计数据传输策略有如下优势:
- 插件架构:
可以将数据传输的代码以可调用组件的形式添加到系统中。
- 自动执行:
可以根据不同条件自动发起数据传输,比如数据变更,间隔一定时间,或者是每天定时。而且,系统会将暂时不能运行的操作添加到队列里,在可能的情况下重新发起。
- 自动检查网络:
系统只会在有网络的情况下发起数据传输
- 优化电池性能:
可以集中处理数据传输任务。将你的应用的数据传输与其他应用的传输结合,减少系统切换网络的次数,从而降低功耗。
- 账号管理认证:
如果你的应用需要用户认证功能,你可以选择在数据传输中整合进账号管理认证。
1.1能用来做什么
- Facebook可以定期更新朋友的最新信息,将最新近况和心情短语集成入联系人中。
- 笔记应用的云备份
- 账户信息的同步
2.同步框架结构
账号同步框架组成部分有三,如上。三者缺一不可。账号是入口,里面可以进行账号验证操作,当然不需要这个功能,相应方法返回false或者null即可。通过了账号认证之后,到了同步管理,里面来进行数据的同步的操作,至于数据发生冲突的具体逻辑需要你来处理。还有个StubProvider,是用来配合同步更新操作的。
3.账号管理
3.1AuthenticationService类
- AuthenticationService是一个继承Service的服务,目的是提供跨进程调用,供系统同步框架调用。
- 固定的Action为android.accounts.AccountAuthenticator
下面是manifest中的注册:
<serviceandroid:name=".AuthenticatorService"android:enabled="true"android:exported="true"><intent-filter><action android:name="android.accounts.AccountAuthenticator" /></intent-filter><meta-dataandroid:name="android.accounts.AccountAuthenticator"android:resource="@xml/authenticator" /></service>
对应的服务:
public class AuthenticatorService extends Service {//mAuthenticator目的是作为账号认证private Authenticator mAuthenticator;public AuthenticatorService() {}@Overridepublic void onCreate() {super.onCreate();mAuthenticator = new Authenticator(this);}@Overridepublic IBinder onBind(Intent intent) {//主要起作用的mAuthenticatorreturn mAuthenticator.getIBinder();}
}
3.2Authenticator类
Authenticator是一个继承自AbstractAccountAuthenticator的类,AbstractAccountAuthenticator是一个虚类,它定义处理手机“设置”里“账号与同步”中Account的添加、删除和验证等功能的基本接口,并实现了一些基本功能。
AbstractAccountAuthenticator里面有个继承于IAccountAuthenticator.Stub的内部类,以用来对AbstractAccountAuthenticator的远程接口调用进行包装。我们可以通过AbstractAccountAuthenticator的getIBinder()方法,返回内部类的IBinder形式,以便对此类进行远程调用,如上面代码onBind方法中的调用。
其中比较重要需要重载的方法是addAccount():
@Overridepublic Bundle addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse, String s, String s1, String[] strings, Bundle bundle) throws NetworkErrorException {Log.d(TAG, "Authenticator addAccount : ");Intent intent = new Intent("com.jiahui.xx.syncadapter");intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, accountAuthenticatorResponse);bundle.putParcelable(AccountManager.KEY_INTENT, intent);return bundle;//return null;}
如上图,这个addAccount()在用户进入设置-账户-添加账户的时候触发的,这里面把自己设置账户的页面的信息封装给bundle,然后传出去即可。如果返回null表示不做任何触发。
3.3authenticator.xml
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"android:accountType="com.crazyman.accountsyncdemo.type"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:smallIcon="@mipmap/ic_launcher" />
4.同步管理
sync机制的使用和账号管理很类似,也是基于binder机制的跨进程通信。首先它需要一个Service,这个服务提供一个Action给系统以便系统能找到它;然后就是继承和实现AbstractThreadedSyncAdapter,此类中包含实现了ISyncAdapter.Stub内部类,这个内部类封装了远程接口调用,这个类getSyncAdapterBinder()方法,返回内部类的IBinder形式,以便对AbstractThreadedSyncAdapte进行远程调用;在manifest中需要对Service注册,而且指定meta-data,这个meta-data是一个xml文件,在SampleSyncAdapter实例中,它的名字是syncadapter.xml,这个文件指定了账号和被监听的contentprovider。下面分别介绍这几个文件:
4.1SyncService
public class SyncService extends Service {private static final String TAG = "SyncService";private static final Object sSyncAdapterLock = new Object();private static SyncAdapter sSyncAdapter = null;/*** Thread-safe constructor, creates static {@link SyncAdapter} instance.*/@Overridepublic void onCreate() {super.onCreate();Log.i(TAG, "Service created");synchronized (sSyncAdapterLock) {if (sSyncAdapter == null) {sSyncAdapter = new SyncAdapter(getApplicationContext(), true);}}}@Override/*** Logging-only destructor.*/public void onDestroy() {super.onDestroy();Log.i(TAG, "Service destroyed");}/*** Return Binder handle for IPC communication with {@link SyncAdapter}.** <p>New sync requests will be sent directly to the SyncAdapter using this channel.** @param intent Calling intent* @return Binder handle for {@link SyncAdapter}*/@Overridepublic IBinder onBind(Intent intent) {return sSyncAdapter.getSyncAdapterBinder();}
}
- SyncService是一个继承普通Service的服务,用来给远端进程提供服务,在onBind方法中返回IBinder。
Manifest.xml注册如下:
<service
android:name=".syncadapter.SyncService"android:exported="true"><intent-filter><action
android:name="android.content.SyncAdapter" /></intent-filter><meta-data
android:name="android.content.SyncAdapter"android:resource="@xml/syncadapter" /></service>
- 一个适配器只能同步一个Authority,若想使一个账户同步多个Authority,可以向系统注册多个绑定同一账户的sync-adapter。
4.2syncadapter.xml
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"android:accountType="com.crazyman.accountsyncdemo.type"android:allowParallelSyncs="false"android:contentAuthority="com.crazyman.accountsyncdemo.provider"android:isAlwaysSyncable="true"android:supportsUploading="false"android:userVisible="false" />
syncadapter.xml文件指定了此Service所监听的contentprovider的Authority,还指定了监听此Authority的账号类型accountType。常用属性以及作用如下:
属性 | 描述 |
---|---|
android:contentAuthority | 指定要同步的ContentProvider所需的权限 |
android:accountType | 表示进行同步的账号的类型 |
android:userVisible | 设置是否在“设置–账户”中显示,当设置为true时,用户可手动关闭同步功能 |
android:supportsUploading | 设置是否必须notifyChange通知才能同步 |
android:allowParallelSyncs | 是否支持并发同步 |
android:isAlwaysSyncable |
默认是false,framework是否可以在任意时刻运行SyncAdapter,如果仅希望通过程序控制同步发起,则设为false,然后通过调用requestSync() 发起
|
4.3SyncAdapter.java
public class SyncAdapter extends AbstractThreadedSyncAdapter {private static final String TAG = SyncAdapter.class.getSimpleName();public SyncAdapter(Context context, boolean autoInitialize) {super(context, autoInitialize);}@Overridepublic void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {Log.d(TAG, "SyncAdapter onPerformSync : 同步数据开始");// TODO: 2017/7/1 执行具体数据同步工作Log.d(TAG, "SyncAdapter onPerformSync : 同步数据结束");}
}
- AbstractThreadedSyncAdapter内部提供startSync()和cancelSync()两个方法,两个方法主要是被远端系统进程调用。startSync()将会启动一个线程,通过在该线程中调用AbstractThreadedSyncAdapter的 onPerformSync()方法来执行同步操作。所以上面onPerformSync方法中的操作都是在新线程中执行的。
- cancelSync()将会中断同步操作。
5.StubProvider
前面说了,整个框架必须有个ContentProvider作为组成部分,当然你实际也可以不使用,在syncadapter的:
@Overridepublic void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {Log.d(TAG, "SyncAdapter onPerformSync : 同步数据开始");// TODO: 2017/7/1 执行具体数据同步工作Log.d(TAG, "SyncAdapter onPerformSync : name: " + account.name + " type: " + account.type);Log.d(TAG, "SyncAdapter onPerformSync : " + authority);Log.d(TAG, "SyncAdapter onPerformSync : 同步数据结束");}
里面传递过来的参数provider就是配置的Contentprivder。
而且这个StubProvider需要在Manifest.xml中注册,还有需要设置属性syncable = true:
<provider
android:name="com.crazyman.accountsyncdemo.StubProvider"android:authorities="com.crazyman.accountsyncdemo.provider"android:exported="false"android:syncable="true" />
如果不在Manifest.xml中设置,也可以在代码中设置
ContentResolver.setIsSyncable(account, "com.crazyman.accountsyncdemo.provider", 1);
两者一致,最后一个参数的意思是:
@param syncable >0 denotes syncable, 0 means not syncable, <0 means unknowns
6.同步机制运作流程
同步框架有几种数据同步的情况需要处理:
1. 数据在服务端变化,-看6.1;
2. 数据在客户端变化,-看6.2;
3. 系统连接TCL长连接,-看6.3;
4. 设置周期发送同步,-看6.4
5. 手动强制同步,-看6.5
6.1服务端如何同步到客户端
例如像Facebook这种项目,除了移动端app,还有web版,假设在web版数据变化了,如何通知到移动端呢?首先这个数据的检查需要自己动手做,Google原生提供了GCM的机制,可以发送notify到移动端,我们只需要在移动端进行监听对应的广播就可以了:
1.判断消息类型,判断是否需要同步
2.调用ContentResolver.requestSync();
@Overridepublic void onReceive(Context context, Intent intent) {// Get a GCM object instanceGoogleCloudMessaging gcm =GoogleCloudMessaging.getInstance(context);// Get the type of GCM messageString messageType = gcm.getMessageType(intent);/** Test the message type and examine the message contents.* Since GCM is a general-purpose messaging system, you* may receive normal messages that don't require a sync* adapter run.* The following code tests for a a boolean flag indicating* that the message is requesting a transfer from the device.*/if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)&&intent.getBooleanExtra(KEY_SYNC_REQUEST)) {/** Signal the framework to run your sync adapter. Assume that* app initialization has already created the account.*/ContentResolver.requestSync(ACCOUNT, AUTHORITY, null);...}...}
ContentResolver.requestSync()这个方法最后会调用到ContentService.syncAsUser(),然后调用到SyncManager.scheduleSync()进行同步
6.2客户端如何同步数据到服务端
当URI对应的数据变化时如何通知:
1. 在配置syncadapter.xml的时候,配了一个ContentProvider的权限;
2. 当该权限对应的ContentProvider的数据变化时候,在客户端处调用ContentResolver.notifyChange(Android.net.Uri,android.database.ContentObserver, boolean)这个方法来通知我们;
3. ContentResolver.notifyChange->getContentService().notifyChange()->ContentService.notifyChange();
下面观察ContentService.notifyChange()的代码如下:
@Overridepublic void notifyChange(Uri uri, IContentObserver observer,boolean observerWantsSelfNotifications, int flags,int userHandle) {try {ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();synchronized (mRootNode) {mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,flags, userHandle, calls);}final int numCalls = calls.size();for (int i=0; i<numCalls; i++) {ObserverCall oc = calls.get(i);try {oc.mObserver.onChange(oc.mSelfChange, uri, userHandle);if (DEBUG) Slog.d(TAG, "Notified " + oc.mObserver + " of " + "update at "+ uri);} catch (RemoteException ex) {synchronized (mRootNode) {Log.w(TAG, "Found dead observer, removing");IBinder binder = oc.mObserver.asBinder();final ArrayList<ObserverNode.ObserverEntry> list= oc.mNode.mObservers;int numList = list.size();for (int j=0; j<numList; j++) {ObserverNode.ObserverEntry oe = list.get(j);if (oe.observer.asBinder() == binder) {list.remove(j);j--;numList--;}}}}}if ((flags& ContentResolver.NOTIFY_SYNC_TO_NETWORK) != 0) {SyncManager syncManager = getSyncManager();if (syncManager != null) {syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uid,uri.getAuthority());}}synchronized (mCache) {final String providerPackageName = getProviderPackageName(uri);invalidateCacheLocked(userHandle, providerPackageName, uri);}} finally {restoreCallingIdentity(identityToken);}}
上面一共做了2个事情:
1. 对该ContentProvider的所有感兴趣的Observer进行通知;
2. 对SyncManager注册的对该URI感兴趣的syncadapter进行通知。
6.3系统连接TCL长连接
ContentResolver.setSyncAutomatically(account, "com.crazyman.accountsyncdemo.provider", true);
开启这个选项之后,会在网络连接的状态下进行自动同步
6.4设置周期发送同步
ContentResolver.addPeriodicSync(account, "com.crazyman.accountsyncdemo.provider", Bundle.EMPTY, 60 * 3);
固定间隔一个时间进行自动同步,如上方法,最后一个参数代表时间,单位是秒
6.5客户端强制发起同步
Bundle settingsBundle = new Bundle();settingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);settingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle);
flag:
SYNC_EXTRAS_MANUAL
强制发起手动同步,同步框架会忽略当前的一些设置,比如自动同步开关状态。
SYNC_EXTRAS_EXPEDITED
强制立即发起同步。如果不设置这个选项,系统为了优化功耗可能会等待几秒钟,将一段时间内的几次同步合并发起。
7.权限设置
一般需要下面几个权限
<!-- 一般同步数据需要连接网络 --><uses-permission android:name="android.permission.INTERNET" /><!-- 同步框架相应函数的使用需要如下读写权限以及账户设置权限 --><uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /><uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" /><uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
8.常见问题
8.1同步框架发起条件
手动同步和自动同步需要条件:
Provider的isSyncable = true
SyncAdapter的isAlwaysSyncable = trye
自动同步还需要:
系统总同步开关( getMasterSyncAutomatically = true)
SyncAdapter同步开关( getSyncAutomatically = true)
ContentResolver 的notifyChange()发起自动同步时会带SYNC_EXTRAS_UPLOAD标志,Android设计原意是仅将本地数据更新至服务端。此时SyncAdapter中的supportsUploading若是false,则不能发起自动同步。
8.2设置-账户可见问题
当文件adapter.xml设置属性 android:userVisible=”true” ,同步设置选项会对用户可见,用户可以选择手动关闭自动同步功能。
8.3账户添加-空白页面停留问题
在设置-账户-添加账户,会触发对应应用的Authenticator类的addAccount()方法,addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse, String s, String s1, String[] strings, Bundle bundle);
一般情况下在这个方法的bundle带上跳转到另外的账户设置页面intent进行账户添加,当然也可以在这个方法体里面直接做添加账户的操作,然后返回null。这种情况下处理完添加账户的逻辑,该页面不会退出,所以需要调用对应的成功设置方法。
Account account = new Account("联系人", "com.crazyman.accountsyncdemo.type");AccountManager accountManager = (AccountManager) mContext.getSystemService(ACCOUNT_SERVICE);boolean isCreated = accountManager.addAccountExplicitly(account, null, null);//accountAuthenticatorResponse.onResult防止停留在空白画面if (isCreated) {Bundle result = new Bundle();result.putString(AccountManager.KEY_ACCOUNT_NAME, "联系人");result.putString(AccountManager.KEY_ACCOUNT_TYPE, "com.crazyman.accountsyncdemo.type");accountAuthenticatorResponse.onResult(result);}return null;
参考来自:
http://blog.csdn.net/inconsolabl/article/details/48054341
http://www.jianshu.com/p/dc9a2693478e
http://www.2cto.com/kf/201411/352718.html
http://www.cnblogs.com/fengzhblog/p/3177002.html
SyncAdapter同步机制相关推荐
- 10、同步机制遵循的原则_我要遵循的10条原则
10.同步机制遵循的原则 by Haseeb Qureshi 由Haseeb Qureshi 我要遵循的10条原则 (10 Principles I Want to Live By) I just c ...
- Java多线程的同步机制(synchronized)
一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个 ...
- Nature Neuroscience|群际冲突的脑间同步机制
本文来源:"认知神经科学与学习国家重点实验室"官网 编辑:Yezi 审阅:mingzlee7 马燚娜课题组在<Nature Neuroscience> 发表论文揭示群际 ...
- Linux 多线程同步机制:互斥量、信号量、条件变量
互斥量:互斥量提供对共享资源的保护访问,它的两种状态:lock和unlock,用来保证某段时间内只有一个线程使用共享资源,互斥量的数据类型是pthread_mutex_t 主要涉及函数:pthread ...
- Java高级-线程同步机制实现
2019独角兽企业重金招聘Python工程师标准>>> 前言 我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread). 线程(Threa ...
- 8天玩转并行开发——第四天 同步机制(上)
在并行计算中,不可避免的会碰到多个任务共享变量,实例,集合.虽然task自带了两个方法:task.ContinueWith()和Task.Factory .ContinueWhenAll()来实现任务 ...
- 转载--线程同步机制及比较
转自:http://blog.csdn.net/eulb/article/details/2177500 先来回答第一个问题,线程实际主要应用于四个主要领域,当然各个领域之间不是绝对孤立的,他们有可能 ...
- Linux内核同步机制之(四):spin lock【转】
转自:http://www.wowotech.net/kernel_synchronization/spinlock.html 一.前言 在linux kernel的实现中,经常会遇到这样的场景:共享 ...
- Java线程同步机制synchronized关键字的理解
由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问. 需要明确的几个问题: ...
最新文章
- Keras学习笔记:序列式模型
- 当HTTP状态代码不足时:处理Web API错误报告
- pyspider all 启动失败:ValueError: Invalid configuration
- 【2016年第4期】大数据时代的简约计算
- Python——使用“_”下划线作为参数的占位符
- LeetCode 109. Convert Sorted List to Binary Search Tree
- 如何知道自己的php安装在哪,如何知道安装了哪些PHP扩展
- Atitit 知识与数据 信息 加工方法总结 目录 1.1. 信息加工是指通过判别、筛选、分类、排序、分析和研究等一系列过程	1 1.2. 多种聚合方法	1 2. 首先通过聚类信息 专题化 分组聚
- python词云图详细教程
- 计算机c盘小了,电脑C盘空间太小怎么办|电脑中使用分区助手扩大C盘空间的方法...
- 330tsl是什么意思_19款探岳330tsl两区豪华型怎么样?
- javascript满天小星星
- n 以内与 n 互素的元素集合必然形成一个循环群
- PHP - AES 加密解密
- 一种基于Android、iOS系统的移动端车牌识别方法,实现手机拍照识别车牌
- Ubuntu 16.04下安装Caffe解决 undefined symbol: _ZN5boost6python6detail11init_moduleER11PyModuleDefPFvvE
- TMC2240步进电机驱动芯片
- 成功人士分析问题的11种思维方式
- C语言/771.宝石与石头
- 硬盘对拷后没法启动怎么办