場景解析
信息同步場景很多,如電子郵件的收取、筆記應用的云備份、天氣應用的及時同步。核心訴求就是兩個:
- 把設備數據同步到服務器。
- 把服務器數據同步設備。
解決方案
最簡單的解決方案,就是終端直接發起網絡請求。但在Android設備上會存在 App被殺死,無法及時后臺同步,導致再次啟動app時,數據沒有更新的情形。
有問題就需要解決方案,Android系統默認提供了一套方案——SyncAdapters,其特點如下:
- 將所有的數據傳輸都放到同一個地方,以便操作系統智能地安排數據傳輸,優化電池性能。
- 可以智能安排數據傳輸,如檢查網絡連接、下載失敗后重試等??梢愿鶕煌瑮l件自動發起數據傳輸,如服務器數據變更、定時同步等。
- 使用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同步數據全攻略