Android 同步机制解析
2018-06-29 本文已影响31人
沉思的Panda
场景解析
信息同步场景很多,如电子邮件的收取、笔记应用的云备份、天气应用的及时同步。核心诉求就是两个:
- 把设备数据同步到服务器。
- 把服务器数据同步设备。
解决方案
最简单的解决方案,就是终端直接发起网络请求。但在Android设备上会存在 App被杀死,无法及时后台同步,导致再次启动app时,数据没有更新的情形。
有问题就需要解决方案,Android系统默认提供了一套方案——SyncAdapters,其特点如下:
- 将所有的数据传输都放到同一个地方,以便操作系统智能地安排数据传输,优化电池性能。
- 可以智能安排数据传输,如检查网络连接、下载失败后重试等。可以根据不同条件自动发起数据传输,如服务器数据变更、定时同步等。
- 使用SyncAdapter可以加快应用的加载时间、实现离线功能,可以在数据及时同步和减少网络调用以节约电池电量之间达到一种平衡局面。
如何配置SyncAdapter
首先,我们要了解Android系统是如何理解同步这件事的。对系统而言,同步的意思是某个账户
同步
某些数据
。
所以,SyncAdapter需要上层应用提供三类信息:账户、数据、同步动作。具体代码如下:
1. 账户组件Authenticator
分为三个部分:账户认证、配置文件、账户认证服务。最后将账户认证服务注册到Anroid系统中即可。
1.1 账户认证器 StubAuthenticator
系统设置→账户→添加账户
的时候会调用其 addAccount 方法。下面是一个不需要账户认证的实现。
public class StubAuthenticator extends AbstractAccountAuthenticator {
public StubAuthenticator(Context context) {
super(context);
}
@Override
public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse,
String s) {
throw new UnsupportedOperationException();
}
@Override
public Bundle addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse, String s,
String s2, String[] strings, Bundle bundle) throws NetworkErrorException {
return null;
}
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse,
Account account, Bundle bundle) throws NetworkErrorException {
return null;
}
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse accountAuthenticatorResponse,
Account account, String s, Bundle bundle) throws NetworkErrorException {
throw new UnsupportedOperationException();
}
@Override
public String getAuthTokenLabel(String s) {
throw new UnsupportedOperationException();
}
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse,
Account account, String s, Bundle bundle) throws NetworkErrorException {
throw new UnsupportedOperationException();
}
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse,
Account account, String[] strings) throws NetworkErrorException {
throw new UnsupportedOperationException();
}
}
1.2 配置文件为 authenticator.xml
放在 res/xml
目录下,一般内容如下:
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.eutechpro.syncadapterexample"
android:icon="@drawable/ic_launcher"
android:smallIcon="@drawable/ic_launcher"
android:label="@string/app_name" />
-
android:accountType
:账户类型,系统唯一,一般采用系统包名为前缀 -
android:icon
:图标,显示在“设置”应用的“账号”一项中。 -
android:smallIcon
:小图标,根据屏幕尺寸可能在设置中代替icon属性。 -
android:label
:标识账户类型,一般为应用名,显示在“设置”应用的“账号”一项中。
1.3 账户认证服务 StubAuthenticatorService
沟通 SyncAdapter framework 和 Authenticator,提供一个远程程序调用RPC 的 IBinder
public class StubAuthenticatorService extends Service {
private StubAuthenticator authenticator;
@Override
public void onCreate() {
authenticator = new StubAuthenticator(this);
}
/*
* When the system binds to this Service to make the RPC call
* return the authenticator’s IBinder.
*/
@Override
public IBinder onBind(Intent intent) {
return authenticator.getIBinder();
}
}
1.4 将Service注册到系统中
<service android:name="ch.teleboy.sync_app_settings.StubAuthenticatorService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
2. 数据组件ContentProvider。
下面是一个空Provider的实现。
public class StubContentProvider extends ContentProvider {
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(Uri uri, String[] columns, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues contentValues) {
return null;
}
@Override
public int delete(Uri uri, String s, String[] strings) {
return 0;
}
@Override
public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
return 0;
}
}
同样需要将其注册到系统中:
<provider
android:name="ch.teleboy.sync_app_settings.StubContentProvider"
android:authorities="com.eutechpro.syncadapterexample.provider"
android:exported="false"
android:syncable="true"></provider>
3. 同步组件SyncAdapter。
氛围三个部分:同步器、配置文件、同步服务。最后将同步服务注册到系统即可。
3.1 同步器SyncAdapter
最终同步相关代码放在onPerformSync
方法里面。
public class SyncAdapter extends AbstractThreadedSyncAdapter {
public SyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient contentProviderClient, SyncResult syncResult) {
System.out.println("******* onPerformSync *******");
// System.out.println("*******" + syncResult.syncAlreadyInProgress+" *******");
System.out.println("*****************************");
}
}
3.2 配置文件
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="com.eutechpro.syncadapterexample.provider"
android:accountType="com.eutechpro.syncadapterexample"
android:allowParallelSyncs="false"
android:isAlwaysSyncable="true"
android:supportsUploading="false"
android:userVisible="true" />
-
android:contentAuthority
指定要同步的ContentProvider在其AndroidManifest.xml文件中有个android:authorities属性。 -
android:accountType
表示进行同步的账号的类型。 -
android:allowParallelSyncs
是否支持多账号同时同步 -
android:isAlwaysSyncable
设置所有账号的isSyncable为true -
android:supportsUploading
设置是否必须notifyChange通知才能同步 -
android:syncAdapterSettingsAction
指定一个可以设置同步的activity的Action。 -
android:userVisible
设置是否在“设置”中显示
3.3 同步服务
public class SyncAdapterService extends Service {
private static SyncAdapter syncAdapter = null;
// Object to use as a thread-safe lock
private static final Object syncAdapterLock = new Object();
@Override
public void onCreate() {
super.onCreate();
/*
* Create the sync adapter as a singleton.
* Set the sync adapter as syncable
* Disallow parallel syncs
*/
synchronized (syncAdapterLock) {
if (syncAdapter == null) {
syncAdapter = new SyncAdapter(getApplicationContext(), true);
}
}
}
/**
* Return an object that allows the system to invoke the sync adapter.
*/
@Override
public IBinder onBind(Intent intent) {
/*
* Get the object that allows external processes
* to call onPerformSync(). The object is created
* in the base class code when the SyncAdapter
* constructors call super()
*/
return syncAdapter.getSyncAdapterBinder();
}
}
如何使用SyncAdapter
- 建立账户init。
- 主动调用forceRefresh 或者等待系统择机调用。
参考代码如下:
public static void init(Context context) {
newAccount = new Account(ACCOUNT, ACCOUNT_TYPE);
AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
if (accountManager.addAccountExplicitly(newAccount, null, null)) {
System.out.println("添加 acc");
} else {
System.out.println("已经添加过啦");
}
ContentResolver.setSyncAutomatically(newAccount, AUTHORITY, true);
}
public static void forceRefresh() {
Bundle bundle = new Bundle();
bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
ContentResolver.requestSync(newAccount, AUTHORITY, bundle);
}
注意:
xml中的各处的 android:accountType、android:contentAuthority 必须保持一直。
参考:
- Demo:看其备注,是斯洛文尼亚人写的,非常简洁。
- 在Android中使用SyncAdapter同步数据全攻略