Android 同步机制解析

场景解析

信息同步场景很多,如电子邮件的收取、笔记应用的云备份、天气应用的及时同步。核心诉求就是两个:

  1. 把设备数据同步到服务器。
  2. 把服务器数据同步设备。

解决方案

最简单的解决方案,就是终端直接发起网络请求。但在Android设备上会存在 App被杀死,无法及时后台同步,导致再次启动app时,数据没有更新的情形。

有问题就需要解决方案,Android系统默认提供了一套方案——SyncAdapters,其特点如下:

  1. 将所有的数据传输都放到同一个地方,以便操作系统智能地安排数据传输,优化电池性能。
  2. 可以智能安排数据传输,如检查网络连接、下载失败后重试等。可以根据不同条件自动发起数据传输,如服务器数据变更、定时同步等。
  3. 使用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

  1. 建立账户init。
  2. 主动调用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 必须保持一直。

参考:

  1. Demo:看其备注,是斯洛文尼亚人写的,非常简洁。
  2. 在Android中使用SyncAdapter同步数据全攻略
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容