建立同步處理轉換介面

注意:對於大多數背景處理用途,我們建議使用 WorkManager 做為建議解決方案。請參閱背景處理指南,瞭解最適合您的解決方案。

應用程式中的同步轉換介面元件會封裝程式碼,用於在裝置和伺服器之間傳輸資料的工作。同步轉換介面架構會根據您在應用程式中提供的排程和觸發條件,在同步轉接器元件中執行程式碼。若要在應用程式中新增同步轉換介面元件,您必須新增下列項目:

同步處理轉接程式類別。
此類別會將資料移轉程式碼納入與同步轉換介面架構相容的介面中。
已繫結 Service
允許同步轉換介面架構在同步轉接器類別中執行程式碼的元件。
同步處理轉換介面 XML 中繼資料檔案。
包含同步轉換介面相關資訊的檔案。架構會讀取這個檔案,瞭解如何載入及安排資料移轉時間。
應用程式資訊清單中的宣告。
:宣告繫結服務,並指向同步處理轉接器專屬中繼資料的 XML。

本課程將說明如何定義這些元素。

建立同步轉換介面類別

在這個部分,您將瞭解如何建立能封裝資料移轉程式碼的同步轉換介面類別。建立類別包括擴充同步轉接程式基礎類別、定義類別的建構函式,以及實作定義資料移轉工作的方法。

擴充基本同步轉換介面類別

如要建立同步轉換介面元件,請先擴充 AbstractThreadedSyncAdapter 並編寫其建構函式。每次從頭開始建立同步轉接程式元件時,請使用建構函式執行設定工作,就像使用 Activity.onCreate() 設定活動一樣。舉例來說,如果應用程式使用內容供應器儲存資料,請使用建構函式取得 ContentResolver 例項。由於 Android 平台 3.0 版為了支援 parallelSyncs 引數而新增了第二種形式的建構函式,因此您必須建立兩種形式的建構函式來維持相容性。

注意:同步轉換介面架構適用於單例模式執行個體的同步轉接器元件。如要進一步瞭解如何將同步轉換介面元件執行個體化,請參閱「將同步轉換介面與架構繫結」一節。

以下範例說明如何實作 AbstractThreadedSyncAdapter 及其建構函式:

Kotlin

/**
 * Handle the transfer of data between a server and an
 * app, using the Android sync adapter framework.
 */
class SyncAdapter @JvmOverloads constructor(
        context: Context,
        autoInitialize: Boolean,
        /**
         * Using a default argument along with @JvmOverloads
         * generates constructor for both method signatures to maintain compatibility
         * with Android 3.0 and later platform versions
         */
        allowParallelSyncs: Boolean = false,
        /*
         * If your app uses a content resolver, get an instance of it
         * from the incoming Context
         */
        val mContentResolver: ContentResolver = context.contentResolver
) : AbstractThreadedSyncAdapter(context, autoInitialize, allowParallelSyncs) {
    ...
}

Java

/**
 * Handle the transfer of data between a server and an
 * app, using the Android sync adapter framework.
 */
public class SyncAdapter extends AbstractThreadedSyncAdapter {
    ...
    // Global variables
    // Define a variable to contain a content resolver instance
    ContentResolver contentResolver;
    /**
     * Set up the sync adapter
     */
    public SyncAdapter(Context context, boolean autoInitialize) {
        super(context, autoInitialize);
        /*
         * If your app uses a content resolver, get an instance of it
         * from the incoming Context
         */
        contentResolver = context.getContentResolver();
    }
    ...
    /**
     * Set up the sync adapter. This form of the
     * constructor maintains compatibility with Android 3.0
     * and later platform versions
     */
    public SyncAdapter(
            Context context,
            boolean autoInitialize,
            boolean allowParallelSyncs) {
        super(context, autoInitialize, allowParallelSyncs);
        /*
         * If your app uses a content resolver, get an instance of it
         * from the incoming Context
         */
        contentResolver = context.getContentResolver();
        ...
    }

新增資料移轉程式碼

同步轉換介面元件不會自動進行資料傳輸。而是封裝資料移轉程式碼,讓同步轉接介面架構可以在背景中執行資料傳輸,而不必與應用程式進行相關作業。當架構準備好同步處理應用程式資料時,會叫用 onPerformSync() 方法實作。

為協助將資料從主要應用程式程式碼轉移至同步轉接器元件,同步轉接程式架構會使用下列引數呼叫 onPerformSync()

帳戶
與觸發同步轉換介面的事件相關聯的 Account 物件。如果您的伺服器未使用帳戶,您就不需要使用這個物件中的資訊。
額外項目
Bundle 包含觸發同步轉接程式的事件所傳送的標記。
權威人士評價
系統中的內容供應器的授權。您的應用程式必須有權存取這個供應者。通常,授權類型會對應到您應用程式中的內容供應器。
內容供應器用戶端
憑證授權單位引數指向的內容供應器的 ContentProviderClientContentProviderClient 是內容供應器的輕量公開介面。提供的基本功能與 ContentResolver 相同。如果您是透過內容供應器儲存應用程式資料,可以用這個物件連線至供應器。或者,您可以忽略。
同步處理結果
可用來將資訊傳送至同步轉接程式架構的 SyncResult 物件。

下列程式碼片段顯示 onPerformSync() 的整體結構:

Kotlin

/*
 * Specify the code you want to run in the sync adapter. The entire
 * sync adapter runs in a background thread, so you don't have to set
 * up your own background processing.
 */
override fun onPerformSync(
        account: Account,
        extras: Bundle,
        authority: String,
        provider: ContentProviderClient,
        syncResult: SyncResult
) {
    /*
     * Put the data transfer code here.
     */
}

Java

/*
 * Specify the code you want to run in the sync adapter. The entire
 * sync adapter runs in a background thread, so you don't have to set
 * up your own background processing.
 */
@Override
public void onPerformSync(
        Account account,
        Bundle extras,
        String authority,
        ContentProviderClient provider,
        SyncResult syncResult) {
    /*
     * Put the data transfer code here.
     */
}

雖然 onPerformSync() 的實際實作專門用於應用程式的資料同步需求和伺服器連線通訊協定,但實作作業應執行以下幾項一般工作:

連線至伺服器
雖然您可以在資料移轉開始時假設網路可供使用,但同步轉接器架構不會自動連線到伺服器。
下載及上傳資料
同步轉換介面不會自動處理任何資料移轉工作。如要從伺服器下載資料並儲存在內容供應器中,您必須提供用於要求資料、下載資料,並將資料插入供應器的程式碼。同樣地,如果想將資料傳送至伺服器,就必須從檔案、資料庫或提供者讀取資料,然後傳送必要的上傳要求。您也必須處理資料移轉執行期間發生的網路錯誤。
處理資料衝突或判斷資料目前程度
同步轉換介面不會自動處理伺服器資料與裝置上資料之間的衝突,此外,伺服器不會自動偵測伺服器上資料是否比裝置資料新,反之亦然。請改為提供自己的演算法來處理這種情況。
清除所用資源。
隨時連線到伺服器,並在資料移轉結束時清除暫存檔案和快取。

注意:同步處理轉接器架構會在背景執行緒上執行 onPerformSync(),因此您不必自行設定背景處理。

除了同步處理相關工作之外,建議您嘗試合併一般網路相關工作,並新增至 onPerformSync()。透過這種方式集中所有網路工作,可以節省啟動及停止網路介面所需的電池電力。如要進一步瞭解如何提高網路存取效率,請參閱訓練課程「在不消耗電池的情況下轉移資料」訓練課程,其中說明瞭您可以在資料移轉程式碼中加入的幾種網路存取工作。

將同步轉換介面繫結至架構

現在,資料移轉程式碼已封裝在同步轉接器元件中,但您必須將架構提供給架構存取。為此,您必須建立繫結的 Service,將特殊的 Android 繫結器物件從同步轉接器元件傳遞至架構。透過這個繫結器物件,架構可以叫用 onPerformSync() 方法,並將資料傳送至該方法。

在服務的 onCreate() 方法中,將同步轉換介面元件執行個體化為單例模式。只要將 onCreate() 中的元件執行個體化,您就能將建立元件推遲到服務啟動為止 (發生在架構首次嘗試執行資料移轉的情況)。您需要以安全執行緒的方式將元件例項化,以免同步轉接程式架構將同步轉接程式的多個執行作業排入佇列來回應觸發條件或排程。

例如,下列程式碼片段說明如何建立類別來實作繫結 Service、將同步轉接器元件執行個體化,以及取得 Android 繫結器物件:

Kotlin

package com.example.android.syncadapter
/**
 * Define a Service that returns an [android.os.IBinder] for the
 * sync adapter class, allowing the sync adapter framework to call
 * onPerformSync().
 */
class SyncService : Service() {
    /*
     * Instantiate the sync adapter object.
     */
    override fun onCreate() {
        /*
         * Create the sync adapter as a singleton.
         * Set the sync adapter as syncable
         * Disallow parallel syncs
         */
        synchronized(sSyncAdapterLock) {
            sSyncAdapter = sSyncAdapter ?: SyncAdapter(applicationContext, true)
        }
    }

    /**
     * Return an object that allows the system to invoke
     * the sync adapter.
     *
     */
    override fun onBind(intent: Intent): IBinder {
        /*
         * 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()
         *
         * We should never be in a position where this is called before
         * onCreate() so the exception should never be thrown
         */
        return sSyncAdapter?.syncAdapterBinder ?: throw IllegalStateException()
    }

    companion object {
        // Storage for an instance of the sync adapter
        private var sSyncAdapter: SyncAdapter? = null
        // Object to use as a thread-safe lock
        private val sSyncAdapterLock = Any()
    }
}

Java

package com.example.android.syncadapter;
/**
 * Define a Service that returns an <code><a href="/reference/android/os/IBinder.html">IBinder</a></code> 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();
    }
}

注意:如要查看同步轉換介面繫結服務的詳細範例,請參閱範例應用程式。

新增架構所需的帳戶

根據同步轉換介面架構,每個同步轉換介面都必須具備帳戶類型。您已在新增 Authenticator 中繼資料檔案一節中宣告帳戶類型值。現在您必須在 Android 系統中設定這個帳戶類型。如要設定帳戶類型,請呼叫 addAccountExplicitly(),新增使用該帳戶類型的預留位置帳戶。

呼叫此方法的最佳位置就是應用程式開啟活動的 onCreate() 方法。以下程式碼片段說明如何執行這項操作:

Kotlin

...
// Constants
// The authority for the sync adapter's content provider
const val AUTHORITY = "com.example.android.datasync.provider"
// An account type, in the form of a domain name
const val ACCOUNT_TYPE = "example.com"
// The account name
const val ACCOUNT = "placeholderaccount"
...
class MainActivity : FragmentActivity() {

    // Instance fields
    private lateinit var mAccount: Account
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       ...
        // Create the placeholder account
        mAccount = createSyncAccount()
       ...
    }
    ...
    /**
     * Create a new placeholder account for the sync adapter
     */
    private fun createSyncAccount(): Account {
        val accountManager = getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
        return Account(ACCOUNT, ACCOUNT_TYPE).also { 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, 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.
                 */
            }
        }
    }
    ...
}

Java

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 = "placeholderaccount";
    // Instance fields
    Account mAccount;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Create the placeholder account
        mAccount = CreateSyncAccount(this);
        ...
    }
    ...
    /**
     * Create a new placeholder 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.
             */
        }
    }
    ...
}

新增同步轉換介面中繼資料檔案

如要將同步轉接器元件插入架構,您需要為架構提供描述元件的中繼資料,並提供其他標記。中繼資料會指定您為同步轉接器建立的帳戶類型、宣告與應用程式相關聯的內容供應器授權、控制與同步轉換介面相關的系統使用者介面部分,並宣告其他與同步處理相關的標記。請在應用程式專案中的 /res/xml/ 目錄中,使用儲存的特殊 XML 檔案宣告此中繼資料。您可以為檔案命名,不過名稱通常為 syncadapter.xml

這個 XML 檔案包含單一 XML 元素 <sync-adapter>,具有下列屬性:

android:contentAuthority
內容供應器的 URI 授權。如果您在上個「建立虛設常式內容供應器」課程中,為應用程式建立了虛設常式內容供應器,請在應用程式資訊清單加入的 <provider> 元素中,使用您為 android:authorities 屬性指定的值。如要進一步瞭解這項屬性,請參閱「在資訊清單中宣告提供者」一節。
如果要使用同步轉接器將資料從內容供應器轉移到伺服器,這個值必須和您為這些資料使用的內容 URI 授權單位相同。這個值也會是您在應用程式資訊清單中宣告供應器的 <provider> 元素 android:authorities 屬性中指定的其中一個主機名稱。
android:accountType
同步轉換介面架構所需的帳戶類型。這個值必須與您建立驗證器中繼資料檔案時提供的帳戶類型值相同,詳情請參閱新增 Authenticator 中繼資料檔案一節。這也是您在程式碼片段「Add the Account Required by the Framework」(新增架構所需的帳戶) 一節中,為常數 ACCOUNT_TYPE 指定的值。
設定屬性
android:userVisible
設定同步轉換器帳戶類型的瀏覽權限。根據預設,與帳戶類型相關聯的帳戶圖示和標籤會顯示在系統「設定」應用程式的「帳戶」部分,因此除非您擁有可輕鬆與應用程式建立關聯的帳戶類型或網域,否則您應該將同步轉接器設為隱藏。如果您將帳戶類型設為隱藏,仍可讓使用者在任一應用程式活動中使用使用者介面控制同步轉接程式。
android:supportsUploading
可讓您將資料上傳至雲端。如果應用程式只會下載資料,請設為 false
android:allowParallelSyncs
允許多個同步轉換介面元件執行個體同時執行。 如果您的應用程式支援多個使用者帳戶,而您想讓多位使用者同時轉移資料,請使用這個選項。如果您從未執行多項資料移轉作業,這個標記就不會有任何作用。
android:isAlwaysSyncable
向同步轉接器架構表示,該架構可在您指定的時間執行同步轉換器。如要透過程式輔助方式控制同步轉接程式的執行時機,請將這個標記設為 false,然後呼叫 requestSync() 來執行同步轉接器。如要進一步瞭解如何執行同步轉換介面,請參閱「執行同步轉換器」課程

以下範例為使用單一預留位置帳戶且只下載的同步轉接程式的 XML。

<?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"
        android:allowParallelSyncs="false"
        android:isAlwaysSyncable="true"/>

在資訊清單中宣告同步轉換介面

在應用程式中加入同步轉換介面元件後,您必須要求與該元件相關的權限,並宣告已新增的繫結 Service

同步轉接器元件會執行程式碼,會在網路和裝置之間傳輸資料,因此您必須要求存取網際網路的權限。此外,您的應用程式必須要求取得讀取和寫入同步轉接程式設定的權限,以便您能夠透過應用程式中其他元件的程式控制同步轉接器設定。此外,您還需要要求特殊權限,讓應用程式使用您在「建立 Stub Authenticator」課程中建立的驗證器元件。

如要要求這些權限,請在應用程式資訊清單中加入以下內容,做為 <manifest> 的子項元素:

android.permission.INTERNET
允許同步轉換介面程式碼存取網際網路,以便將裝置下載或上傳至伺服器。如果您先前已要求權限,則不必再次新增這項權限。
android.permission.READ_SYNC_SETTINGS
允許應用程式讀取目前的同步轉換介面設定。舉例來說,您必須具備這項權限才能呼叫 getIsSyncable()
android.permission.WRITE_SYNC_SETTINGS
允許應用程式控制同步轉換介面設定。您必須具備這項權限,才能使用 addPeriodicSync() 設定定期同步處理轉接器執行作業。呼叫 requestSync()「不」需要這項權限。如要進一步瞭解如何執行同步轉換介面,請參閱「執行同步處理轉接器」。

下列程式碼片段說明如何新增權限:

<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"/>
...
</manifest>

最後,如要宣告架構用於與同步轉接器互動的繫結 Service,請在應用程式資訊清單中加入下列 XML,做為 <application> 的子項元素:

        <service
                android:name="com.example.android.datasync.SyncService"
                android:exported="false"
                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>

<intent-filter> 元素會設定由意圖動作 android.content.SyncAdapter 觸發的篩選器,並由系統傳送執行同步轉換器。觸發篩選器時,系統會啟動您建立的繫結服務,在這個範例中為 SyncServiceandroid:exported="false" 屬性僅允許您的應用程式和系統存取 Serviceandroid:process=":sync" 屬性會指示系統在名為 sync 的全域共用程序中執行 Service。應用程式中有多個同步轉換介面可共用此程序,藉此減少負擔。

<meta-data> 元素會提供您先前建立的同步轉接器中繼資料 XML 檔案名稱。android:name 屬性表示此中繼資料適用於同步轉接器架構。android:resource 元素會指定中繼資料檔案名稱。

您現在擁有同步轉換介面的所有元件。下一堂課將說明如何要求同步轉換介面架構執行同步轉換器,藉此回應事件或定期執行。