1. 程式人生 > >在Android中使用SyncAdapter同步資料全攻略

在Android中使用SyncAdapter同步資料全攻略

參考資料:

SyncAdapter是什麼?

SyncManager是Android提供的一個同步框架,該框架實施了許多最佳做法,它允許Android應用使用Google應用中實現高效同步的一個基本框架。
它實際上是一個數據集中點,將所有的資料傳輸都放到同一個地方,以便作業系統智慧地安排資料傳輸,優化電池效能。
你可以通過SyncAdapter來使用該框架處理同步請求。

為什麼使用SyncAdapter?

SyncAdapter可以智慧安排資料傳輸,如檢查網路連線、下載失敗後重試等。可以根據不同條件自動發起資料傳輸,如伺服器資料變更、定時同步等。
使用SyncAdapter可以加快應用的載入時間、實現離線功能,可以在資料及時同步和減少網路呼叫以節約電池電量之間達到一種平衡局面。

何時使用SyncAdapter?

SyncAdapter適用於需要同步本地資料和線上賬戶資訊的應用,如電子郵件的定時收取、筆記應用的雲備份、天氣應用的及時同步等。
SyncAdapter設計為必須與使用者賬戶繫結,即使你的應用不需要賬戶認證,也需要實現相關的類來處理賬戶,並可以將其隱藏。

如何建立SyncAdapter?

1. 建立Authenticator類

該類繼承了AbstractAccountAuthenticator類,用於管理賬戶認證。如果你的應用不需要賬戶認證,可以提供一個僅包含方法實現的類,Authenticator的資訊將被忽略。
以下是Android提供的無需賬戶認證的樣例程式碼,如果需要建立真實管理使用者賬戶的Authenticator,請參閱

AbstractAccountAuthenticator文件

/*
 * Implement AbstractAccountAuthenticator and stub out all
 * of its methods
 */
public class Authenticator extends AbstractAccountAuthenticator {
    // Simple constructor
    public Authenticator(Context context) {
        super(context);
    }
    // Editing properties is not supported
@Override public Bundle editProperties( AccountAuthenticatorResponse r, String s) { throw new UnsupportedOperationException(); } // Don't add additional accounts @Override public Bundle addAccount( AccountAuthenticatorResponse r, String s, String s2, String[] strings, Bundle bundle) throws NetworkErrorException { return null; } // Ignore attempts to confirm credentials @Override public Bundle confirmCredentials( AccountAuthenticatorResponse r, Account account, Bundle bundle) throws NetworkErrorException { return null; } // Getting an authentication token is not supported @Override public Bundle getAuthToken( AccountAuthenticatorResponse r, Account account, String s, Bundle bundle) throws NetworkErrorException { throw new UnsupportedOperationException(); } // Getting a label for the auth token is not supported @Override public String getAuthTokenLabel(String s) { throw new UnsupportedOperationException(); } // Updating user credentials is not supported @Override public Bundle updateCredentials( AccountAuthenticatorResponse r, Account account, String s, Bundle bundle) throws NetworkErrorException { throw new UnsupportedOperationException(); } // Checking features for the account is not supported @Override public Bundle hasFeatures( AccountAuthenticatorResponse r, Account account, String[] strings) throws NetworkErrorException { throw new UnsupportedOperationException(); } }

2. 建立AuthenticatorService服務

此服務提供給SyncAdapter framework,用於呼叫Authenticator的方法。
onCreat()中建立Authenticator物件,在onBind()中返回一個binder物件用於在Authenticator和framework間傳輸資料。
下面是Android提供的一個樣例程式碼:

/**
 * A bound Service that instantiates the authenticator
 * when started.
 */
public class AuthenticatorService extends Service {
    ...
    // Instance field that stores the authenticator object
    private Authenticator mAuthenticator;
    @Override
    public void onCreate() {
        // Create a new authenticator object
        mAuthenticator = new Authenticator(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 mAuthenticator.getIBinder();
    }
}

3. 新增Authenticator的元資料檔案

元資料寫在一個xml檔案中,用於宣告賬戶型別和一些顯示給使用者的資訊,儲存在/res/xml/目錄。
檔名自定義,一般定義為authenticator.xml,根標籤為<account-authenticator>,一般有以下屬性:

  1. android:accountType
    framework把賬戶型別作為識別SyncAdapter的內部標識,對於需要驗證賬戶的應用,賬戶型別會和賬戶名一起傳送給伺服器進行驗證;對於不需要驗證的應用,也要提供賬戶型別,用於標明一個控制域,framework用這個賬戶型別類管理你的SyncAdapter,但是不會發送給伺服器。
  2. android:icon
    指向用做圖示的Drawable資源。如果在res/xml/syncadapter.xml 設定了android:userVisible="true” 屬性將Sync adapter對使用者可見,則必須要提供一個圖示資源。它將顯示在“設定”應用的“賬號”一項中。
  3. android:smallIcon
    小圖示,根據螢幕尺寸可能在設定中代替icon屬性。
  4. android:label
    標識賬戶型別,一般為應用名。

以下為樣例程式碼:

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:accountType="example.com"
        android:icon="@drawable/ic_launcher"
        android:smallIcon="@drawable/ic_launcher"
        android:label="@string/app_name"/>

4. 在清單檔案中宣告AuthenticatorService

樣例程式碼:

<service
    android:name="com.example.android.syncadapter.AuthenticatorService">
    <intent-filter>
        <action android:name="android.accounts.AccountAuthenticator"/>
    </intent-filter>
    <meta-data
        android:name="android.accounts.AccountAuthenticator"
        android:resource="@xml/authenticator" />
</service>

<intent-filter>設定了通過actionandroid.accounts.AccountAuthenticator啟動的filter。這個action是由系統傳送的。當被觸發時,系統會啟動封裝了你的Authtenticator的AuthenticatorService。
<meta-data>聲明瞭authenticator的元資料。通過android:name 屬性將meta-data與認證框架關聯。android:resource指定元資料檔案。

5. 建立ContentProvider

SyncManager同步框架被設計用來與ContentProvider框架協作,需要一個ContentProvider來儲存本地資料。使用ContentProvider的好處與其建立方法此處不再細述,之後會在另一篇筆記中記錄。
如果你已經將本地資料儲存為別的格式,無法實現ContentProvider,那麼可以建立一個虛擬的ContentProvider,實現必要的方法返回null或0。之後可以通過SyncAdapter按照自己的方式傳輸資料。
虛擬ContentProvider的樣例程式碼:

/*
 * Define an implementation of ContentProvider that stubs out
 * all methods
 */
public class StubProvider extends ContentProvider {
    /*
     * Always return true, indicating that the
     * provider loaded correctly.
     */
    @Override
    public boolean onCreate() {
        return true;
    }
    /*
     * Return no type for MIME type
     */
    @Override
    public String getType(Uri uri) {
        return null;
    }
    /*
     * query() always returns no results
     *
     */
    @Override
    public Cursor query(
            Uri uri,
            String[] projection,
            String selection,
            String[] selectionArgs,
            String sortOrder) {
        return null;
    }
    /*
     * insert() always returns null (no URI)
     */
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }
    /*
     * delete() always returns "no rows affected" (0)
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }
    /*
     * update() always returns "no rows affected" (0)
     */
    public int update(
            Uri uri,
            ContentValues values,
            String selection,
            String[] selectionArgs) {
        return 0;
    }
}

6. 在清單檔案中宣告ContentProvider

provider的宣告方法不再細述,主要是新增android:syncable屬性為true,使其支援同步。

<provider
    android:name="com.example.android.datasync.provider.StubProvider"
    android:authorities="com.example.android.datasync.provider"
    android:exported="false"
    android:syncable="true"/>

至此完成了同步框架所需要的全部依賴項。

7. 建立SyncAdapter類

  1. 繼承AbstractThreadedSyncAdapter基類,在構造方法中做一些初始化設定,如獲取ContentResolver例項等。
  2. onPerformSync()方法中新增資料傳輸的程式碼,框架將自動將其放在後臺執行緒中執行。除同步相關任務外,也應將網路相關的任務放在此處,將網路操作集中處理可以降低頻繁發起網路的功耗。
  3. 可新增輔助方法syncImmediately(),呼叫此方法來立即執行同步,可用於“重新整理”操作:

     public static void syncImmediately(Context context) {
         Bundle bundle = new Bundle();
    
         //將此同步放在同步請求佇列前面,立即進行同步而不延遲
         bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
         //忽略當前設定強制發起同步
         bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
         ContentResolver.requestSync(getSyncAccount(context),
                 context.getString(R.string.content_authority), bundle);
     }
  4. 由於請求同步時需要一個同步賬戶,可新增輔助方法getSyncAccount()來獲取賬戶。
    此方法與第九步新增Account的功能相同,只是將程式碼放在了同步框架程式碼中。
    提供一個虛擬賬戶的示例:

     public static Account getSyncAccount(Context context) {
         // Get an instance of the Android account manager
         AccountManager accountManager =
                 (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
    
         // Create the account type and default account
         Account newAccount = new Account(
                 context.getString(R.string.app_name), context.getString(R.string.sync_account_type));
    
         // If the password doesn't exist, the account doesn't exist
         if ( null == accountManager.getPassword(newAccount) ) {
    
         /*
          * Add the account and account type, no password or user data
          * If successful, return the Account object, otherwise report an error.
          */
             if (!accountManager.addAccountExplicitly(newAccount, "", null)) {
                 return null;
             }
             /*
              * If you don't set android:syncable="true" in
              * in your <provider> element in the manifest,
              * then call ContentResolver.setIsSyncable(account, AUTHORITY, 1)
              * here.
              */
    
         }
         return newAccount;
     }

8. 建立SyncService服務

該服務用於將SyncAdapter開放給framework呼叫,即將SyncAdapter的binder物件傳給framework。通過這個binder,framework即可呼叫onPerformSync()方法。
onCreate()中以單例項形式例項化SyncAdapter,這樣會將SyncAdapter的例項化延遲到framework首次傳輸資料建立Service的時候執行。例項化過程須保證執行緒安全,避免將多次同步響應新增到佇列。
示例程式碼:

/**
 * Define a Service that returns an IBinder for the
 * sync adapter class, allowing the sync adapter framework to call
 * onPerformSync().
 */
public class SyncService extends Service {
    // Storage for an instance of the sync adapter
    private static SyncAdapter sSyncAdapter = null;
    // Object to use as a thread-safe lock
    private static final Object sSyncAdapterLock = new Object();
    /*
     * Instantiate the sync adapter object.
     */
    @Override
    public void onCreate() {
        /*
         * Create the sync adapter as a singleton.
         * Set the sync adapter as syncable
         * Disallow parallel syncs
         */
        synchronized (sSyncAdapterLock) {
            if (sSyncAdapter == null) {
                sSyncAdapter = 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 sSyncAdapter.getSyncAdapterBinder();
    }
}

9. 新增Account

framework要求每個SyncAdapter必須有一個賬戶型別,對應第三步中Authenticator的元資料檔案,需要在Android系統中設定賬戶型別:呼叫addAccountExplicitly()方法,天價一個具有賬戶型別的虛擬賬戶。最好是在開啟應用時的onCreate()中呼叫此方法。以下是Android提供的示例程式碼:

public class MainActivity extends FragmentActivity {
    ...
    ...
    // Constants
    // The authority for the sync adapter's content provider
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // An account type, in the form of a domain name
    public static final String ACCOUNT_TYPE = "example.com";
    // The account name
    public static final String ACCOUNT = "dummyaccount";
    // Instance fields
    Account mAccount;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Create the dummy account
        mAccount = CreateSyncAccount(this);
        ...
    }
    ...
    /**
     * Create a new dummy account for the sync adapter
     *
     * @param context The application context
     */
    public static Account CreateSyncAccount(Context context) {
        // Create the account type and default account
        Account newAccount = new Account(
                ACCOUNT, ACCOUNT_TYPE);
        // Get an instance of the Android account manager
        AccountManager accountManager =
                (AccountManager) context.getSystemService(
                        ACCOUNT_SERVICE);
        /*
         * Add the account and account type, no password or user data
         * If successful, return the Account object, otherwise report an error.
         */
        if (accountManager.addAccountExplicitly(newAccount, null, null)) {
            /*
             * If you don't set android:syncable="true" in
             * in your <provider> element in the manifest,
             * then call context.setIsSyncable(account, AUTHORITY, 1)
             * here.
             */
        } else {
            /*
             * The account exists or some other error occurred. Log this, report it,
             * or handle it internally.
             */
        }
    }
    ...
}

10. 新增SyncAdapter的元資料檔案

元資料制訂了SyncAdapter的賬戶型別、對應ContentProvider的Authority、系統和SyncAdapter相關的部分UI以及其他一些同步相關的標識。檔名一般取syncadapter.xml,儲存在/res/xml/目錄下,根標籤為<sync-adapter>

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:contentAuthority="com.example.android.datasync.provider"
    android:accountType="com.android.example.datasync"

    //賬戶是否在“系統設定”中可見
    android:userVisible="false"

    //是否支援上傳資料
    android:supportsUploading="false"

    //是否允許SyncAdapter多例項同時執行,如果應用需要支援多賬號併發傳輸時才使用此標識,如果沒有併發的資料傳輸則此標識無效
    android:allowParallelSyncs="false"

    //framework是否可以在任意時刻執行SyncAdapter,如果僅希望通過程式控制同步發起,則設為false,然後通過呼叫``requestSync()``發起。
    android:isAlwaysSyncable="true"/>

11. 在清單中宣告SyncAdapter

需要新增四個許可權,並宣告SyncService:

<manifest>
...
    <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"/>
...
    <service
        android:name="com.example.android.datasync.SyncService"
        android:exported="true"
        android:process=":sync">
        <intent-filter>
            <action android:name="android.content.SyncAdapter"/>
        </intent-filter>
        <meta-data android:name="android.content.SyncAdapter"
            android:resource="@xml/syncadapter" />
        </service>
</manifest>

<intent-filter>設定了一個由Action為android.content.SyncAdapter的Intent觸發的過濾器,系統要執行SyncAdapter時會發送這個Intent。當過濾器被觸發時,系統會建立繫結用的Service,本例中即SyncService。
android:exported="true"允許出此應用之外的程序來訪問這個Service。
android:process=":sync"告訴系統在名為sync 的全域性共享的程序中執行這個Service。如果你的應用中有多個SyncAdapter,他們可以共享這個程序,可以降低一些消耗。
<meta-data>元素規定了之前建立SyncAdapter元資料XML檔案。android:name屬性說明這個元資料是給同步框架的。android:resource元素指定了元資料檔案的名字。

如何執行SyncAdapter?

  • 可以用以下幾種方式執行SyncAdapter:
    • 服務端資料變化時
    • 本地資料變化時
    • 系統傳送網路訊息時
    • 固定時間間隔或時間點
    • 手動發起

服務端資料變更時同步

當服務端資料發生變更,服務端傳送一條特殊的訊息到應用的BroadcastReceiver,然後呼叫ContentResolver.requestSync()發起同步。Google Cloud Messaging提供了傳送此訊息的服務端和客戶端元件,使用GCM比輪詢伺服器更可靠更有效率。以下是示例程式碼:

public class GcmBroadcastReceiver extends BroadcastReceiver {
    ...
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider"
    // Account type
    public static final String ACCOUNT_TYPE = "com.example.android.datasync";
    // Account
    public static final String ACCOUNT = "default_account";
    // Incoming Intent key for extended data
    public static final String KEY_SYNC_REQUEST =
            "com.example.android.datasync.KEY_SYNC_REQUEST";
    ...
    @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);
        
            
           

相關推薦

Android使用SyncAdapter同步資料

參考資料: SyncAdapter是什麼? SyncManager是Android提供的一個同步框架,該框架實施了許多最佳做法,它允許Android應用使用Google應用中實現高效同步的一個基本框架。 它實際上是一個數據集中點,將所有的資料傳輸都放到同一個地方,以便作業系統智慧地安排資料傳輸,優化電池效

webBrowser操作網頁元素

1、獲取非input控制元件的值: webBrowser1.Document.All["控制元件ID"].InnerText; 或webBrowser1.Document.GetElementById("控制元件ID").InnerText; 或webBrowser1.Document.GetElement

android螢幕適配的3-動態獲取手機螢幕寬高及動態設定控制元件寬高

1.獲取手機螢幕寬高: DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); int screenWidth = dm.width

android螢幕適配的2--支援手機各種螢幕密度dpi

如何為不同密度的螢幕提供不同的資源和使用密度獨立的單位。 1 使用密度無關畫素 堅決杜絕在佈局檔案中使用絕對畫素來定位和設定大小。因為不同的螢幕有不同的畫素密度,所以使用畫素來設定控制元件大小是有問題的,在不同的裝置上同樣的畫素可能代表不同的物理螢幕尺寸,所以當使用尺寸的時候,總是使用dp或者sp,dp是相對

Android 劉海屏適配

本文由玉剛說寫作平臺提供寫作贊助,版權歸玉剛說微信公眾號所有 原作者:四月葡萄 版權宣告:未經玉剛說許可,不得以任何形式轉載 1.前言 先吐槽一下,劉海屏真醜。然而作為苦逼的開發者,還是要去適配劉海屏的。好了,吐槽完畢,進入正題。 這裡主要是介

Pythonpost提交資料格式

爬蟲除了經常用到的get請求以外,還會用到post請求, 公司裡新來了幾個爬蟲,感覺他們對post提交的格式問題, 不是特別清楚。 關於post提交,我們經常見到的就是在html網頁中使用, 經常遇到兩種格式 1  表單 2  json提交 1 (表單提交)

Android程序保活

在上一篇部落格Android程序保活全攻略(上)中介紹了程序保活的背景和一些方法的思路和實現方式,本篇部落格我將承接上篇部落格,繼續進行介紹。 9) 1畫素懸浮層 **思路:**1畫素懸浮層是傳說的QQ黑科技,監控手機鎖屏解鎖事件,在螢幕鎖屏時啟動1個

android屏幕適配的

屏幕分辨率 縮放 img 關系 我們 http 分辨 tro pix 一. 核心概念與單位詳解 1. 什麽是屏幕尺寸、屏幕分辨率、屏幕像素密度? 屏幕分辨率越大,手機越清晰 2. 什麽是dp、dip、dpi、sp、px?之間的關系是什麽? dip:Density

【轉】Android Studio打包---從入門到精通

UC store 類型 安裝文件 public alt url tool 描述 原文地址:http://blog.csdn.net/zivensonice/article/details/51672846 初出茅廬 手動打包 怎麽手動打包 項目寫完了,現在需要把應用上傳

Android-螢幕適配(絕對詳細)(二)

Android-螢幕適配全攻略(絕對詳細)(二) 關鍵字:非密度制約畫素(dp、sp) 螢幕適配最佳實踐 前言:這篇文章接著 上一篇 繼續講,上篇講到了螢幕適配的核心概念與單位和一種解決方案:解決方案-支援各種螢幕尺寸。本篇繼續講剩餘的其他兩種解

Android-螢幕適配(絕對詳細)

(一) 關鍵字:螢幕適配 px dp dpi sp large限定符 .9.png 2012年到2014年支援Android裝置的種類從3997增長到18796。同時各大廠商定製的螢幕尺寸也非常多。這將非常

maskrcnn標記自己的資料集最

NVIDIA顯示卡驅動+CUDA+CUDNN GPU平臺搭建。 (備註:cuda cudnn的版本一定要對應正確) 在github https://github.com/matterport/Mask_RCNN上下載maskrcnn原始碼。要求:python3以上,keras, tens

Android Studio打包---從入門到精通

初出茅廬 手動打包 怎麼手動打包 專案寫完了,現在需要把應用上傳到市場,問題出現—怎麼把程式碼變成.apk(Android的可安裝檔案)。 1. 建立簽名檔案 2. 填寫好籤名引數 3. 生成APK 注意:簽名的密碼和密匙的密

Android螢幕適配(最權威的官方適配指導)

Android的螢幕適配一直以來都在折磨著我們這些開發者,本篇文章以Google的官方文件為基礎,全面而深入的講解了Android螢幕適配的原因、重要概念、解決方案及最佳實踐,我相信如果你能認真的學習本文,對於Android的螢幕適配,你將有所收穫! Android螢幕適配出現的原因

Android螢幕適配2-我想跟美工談談

[TOC] 前言 俗話說完事開頭難 對一個Android開發而言,專案正式開發的第一步就是拿到圖(高清圖和具體切圖)。第一步至關重要,UI圖做得好、標準高,後面的開發更傾向於按照高的標準去要求自己,開了個好頭。UI本身就做得很爛,很多不合理的設計互動,

Android Studio依賴包aar使用

如有不妥的地方歡迎Gradle大神批評指正。 今天包含以下幾點: - aar 檔案簡介 - 生成方法 - 向工程中新增依賴的方法(即讓Android Studio認出aar的方法) - 呼叫方法 aar 檔案簡介 首先,講講aar: 相信

Android Studio打包----Gradle-Build Variants構建定製版App

上一篇文章 Android Studio打包全攻略—從入門到精通限於篇幅Build Variants的作用分析得還不夠,這篇文章主要探討如何構建特別定製版App。 你肯定看到過這樣的App,類似於:打豆豆小米特別定製版、XXX魅族首發版。 這些App絕大部分介面樣式、功能實現和普通版本都差不多,不過只是

Android微信支付功能整合【

遵循:BY-SA 作者:譚東 時間:2016年10月28日 環境:Windows 7 Android版微信支付官方文件和Demo問題很多,官方也沒有及時更新和細化開發整合文件。 這裡分享我整合Android客戶端微信支付的思路和部分程式碼。希望對大家有幫

Android-螢幕適配(轉)

2012年到2014年支援Android裝置的種類從3997增長到18796。同時各大廠商定製的螢幕尺寸也非常多。這將非常不利於我們進行螢幕適配。這要求我們必須掌握螢幕適配技能,以便使我們的app可以適用於不同螢幕尺寸的裝置上。 從上圖可以看出,主流的解析度是前六種:128

Android權威官方螢幕適配

Android的螢幕適配一直以來都在折磨著我們這些開發者,本篇文章以Google的官方文件為基礎,全面而深入的講解了Android螢幕適配的原因、重要概念、解決方案及最佳實踐,我相信如果你能認真的學習本文,對於Android的螢幕適配,你將有所收穫!