SyncAdapter同步机制
来源:互联网 发布:手机全屏时钟软件 编辑:程序博客网 时间:2024/05/22 07:19
官方文档: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中的注册:
<service android:name=".AuthenticatorService" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator" /> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" /> </service>
对应的服务:
public class AuthenticatorService extends Service { //mAuthenticator目的是作为账号认证 private Authenticator mAuthenticator; public AuthenticatorService() { } @Override public void onCreate() { super.onCreate(); mAuthenticator = new Authenticator(this); } @Override public IBinder onBind(Intent intent) { //主要起作用的mAuthenticator return mAuthenticator.getIBinder(); }}
3.2Authenticator类
Authenticator是一个继承自AbstractAccountAuthenticator的类,AbstractAccountAuthenticator是一个虚类,它定义处理手机“设置”里“账号与同步”中Account的添加、删除和验证等功能的基本接口,并实现了一些基本功能。
AbstractAccountAuthenticator里面有个继承于IAccountAuthenticator.Stub的内部类,以用来对AbstractAccountAuthenticator的远程接口调用进行包装。我们可以通过AbstractAccountAuthenticator的getIBinder()方法,返回内部类的IBinder形式,以便对此类进行远程调用,如上面代码onBind方法中的调用。
其中比较重要需要重载的方法是addAccount():
@Override public 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. */ @Override public 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} */ @Override public 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。常用属性以及作用如下:
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); } @Override public 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的:
@Override public 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();
@Override public void onReceive(Context context, Intent intent) { // Get a GCM object instance GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context); // Get the type of GCM message String 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()的代码如下:
@Override public 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同步机制
- Android SyncAdapter同步实践
- 在Android中使用SyncAdapter同步数据全攻略
- Android自定义账户类型和同步适配器模式 Custom Account Type & SyncAdapter
- 同步机制
- 同步机制
- 同步机制
- 同步机制
- 同步多线程的同步机制
- 内核同步机制-RCU同步机制
- 进程同步机制--管程机制
- 线程的同步机制
- java多线程同步机制
- 多线程同步机制摘要
- 事件同步机制
- java---synchronized同步机制
- Java 同步机制浅谈
- 什么时候需要同步机制?
- C++__自定义数据类型
- 时间合理的分配给自己的提示
- Android Studio中新增整体的activity类文件,重新编译后提示“程序包R不存在”解决
- leetcode 404. Sum of Left Leaves
- Word快捷键大全
- SyncAdapter同步机制
- transient的用途
- 机器学习
- 如何成为一名真正的数据分析师或者数据工程师
- Locally weighted linear regression局部加权线性回归
- c#的几个基本概念
- 抽象代数学习笔记(2)关系
- Linux文件权限介绍
- Tkinter 学习笔记 —— Button 部件