執行同步轉換介面

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

在先前的本課程中,您已學到如何建立同步轉換介面元件 包含資料移轉程式碼,以及如何加入其他元件,以便 將同步轉接器插入系統現在您已具備安裝 內含同步轉換介面,但您看到的任何程式碼實際上都不會執行同步處理轉接程式。

建議您按照時間表或 活動。舉例來說,您可以選擇讓同步轉換介面定期執行,無論是在 特定時段或一天中的特定時間。建議你也執行同步處理 轉接器 (如果裝置上儲存的資料有異動)。建議您避免在 同步處理轉換轉接器,因為這麼做並不會 讓同步轉換介面架構的排程功能從中受益。舉例來說, 在使用者介面中顯示重新整理按鈕

您可以採用以下方式執行同步轉換介面:

伺服器資料變更時
執行同步處理轉換介面以回應來自伺服器的訊息,表示該伺服器發出的訊息 資料已變更。這個選項可讓你重新整理伺服器到裝置的資料 而不會因輪詢伺服器而降低效能或浪費電池壽命。
裝置資料有異動時
當資料在裝置上變更時,執行同步轉換介面。此選項可讓您傳送 這項實用服務特別適合從裝置傳送到伺服器,需要確保 讓伺服器隨時能取得最新的裝置資料這個選項相當容易 您必須先將資料供應器確實儲存在內容供應器中如果使用虛設常式 內容供應器,因此要能更難偵測資料變更。
定期
在您選擇的時間間隔結束後執行同步轉接程式,或於特定時間間隔執行 讀取資料。
隨選
執行同步處理轉接程式以回應使用者動作。但為了提供 應優先採用自動化程度更高的自動化選項使用 自動化選項,可節省電池和網路資源。

本課程其餘部分會詳細說明各選項。

在伺服器資料變更時執行同步轉換介面

如果應用程式從伺服器傳輸資料,且伺服器資料經常變動,您可以使用 同步轉換介面,以回應資料變更。如要執行同步轉換介面,請執行下列操作: 伺服器會將特殊訊息傳送至應用程式中的 BroadcastReceiver。 為回應這則訊息,請呼叫 ContentResolver.requestSync() 以指出同步處理轉換介面架構,以便執行 同步處理轉換介面

Google 雲端通訊 (GCM) 可提供 讓這個訊息系統正常運作。使用 GCM 觸發 比輪詢伺服器來獲得狀態的可靠度和效率。輪詢時 要求持續有效的 Service,GCM 會使用 BroadcastReceiver 會在收到郵件時啟用。輪詢時 即使沒有可用的更新,仍會使用電池電力,GCM 只會傳送 傳送訊息

注意:如果您使用 GCM 透過廣播向所有使用者觸發同步轉換介面, 已安裝您應用程式的裝置;請注意,他們會透過 相差甚遠這種情況可能會導致多個同步轉換介面執行個體執行 進而造成伺服器和網路超載如何避免在廣播訊息中發生這類情況 建議你將同步轉換介面的啟動時間延後一段 且每個裝置都有專屬 ID

下列程式碼片段說明如何執行 requestSync()以回應 傳入的 GCM 訊息:

Kotlin

...
// Constants
// Content provider authority
const val AUTHORITY = "com.example.android.datasync.provider"
// Account type
const val ACCOUNT_TYPE = "com.example.android.datasync"
// Account
const val ACCOUNT = "default_account"
// Incoming Intent key for extended data
const val KEY_SYNC_REQUEST = "com.example.android.datasync.KEY_SYNC_REQUEST"
...
class GcmBroadcastReceiver : BroadcastReceiver() {
    ...
    override fun onReceive(context: Context, intent: Intent) {
        // Get a GCM object instance
        val gcm: GoogleCloudMessaging = GoogleCloudMessaging.getInstance(context)
        // Get the type of GCM message
        val messageType: String? = gcm.getMessageType(intent)
        /*
         * Test the message type and examine the message contents.
         * Since GCM is a general-purpose messaging system, you
         * may receive normal messages that don't require a sync
         * adapter run.
         * The following code tests for a a boolean flag indicating
         * that the message is requesting a transfer from the device.
         */
        if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE == messageType
            && intent.getBooleanExtra(KEY_SYNC_REQUEST, false)) {
            /*
             * Signal the framework to run your sync adapter. Assume that
             * app initialization has already created the account.
             */
            ContentResolver.requestSync(mAccount, AUTHORITY, null)
            ...
        }
        ...
    }
    ...
}

Java

public class GcmBroadcastReceiver extends BroadcastReceiver {
    ...
    // Constants
    // 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);
        /*
         * Test the message type and examine the message contents.
         * Since GCM is a general-purpose messaging system, you
         * may receive normal messages that don't require a sync
         * adapter run.
         * The following code tests for a a boolean flag indicating
         * that the message is requesting a transfer from the device.
         */
        if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)
            &&
            intent.getBooleanExtra(KEY_SYNC_REQUEST)) {
            /*
             * Signal the framework to run your sync adapter. Assume that
             * app initialization has already created the account.
             */
            ContentResolver.requestSync(mAccount, AUTHORITY, null);
            ...
        }
        ...
    }
    ...
}

在內容供應器資料變更時執行同步轉接程式

如果您的應用程式在內容供應器中收集資料,且您想在每次需要時更新伺服器 更新供應商,就能將應用程式設為自動執行同步轉換介面。待辦 您需要為內容供應器註冊一個觀察器內容供應器提供的資料 內容供應器架構會呼叫觀察器在觀察器中,呼叫 requestSync() 用來指示架構執行 同步處理轉換器。

注意:如果您使用的是虛設常式內容供應器,則您在 內容供應器,onChange() 則是 呼叫。在此情況下,您必須自行提供用來偵測 裝置資料。此機制也會負責 requestSync() (資料變更時)。

如要為內容供應器建立觀察器,請擴充類別 ContentObserver,並實作 onChange() 方法。於 onChange(),撥號 requestSync(),啟動同步轉換介面。

如要註冊觀察器,請在呼叫 registerContentObserver()。於 您也需要傳入內容 URI 來作為要觀察的資料。內容 提供者架構會將此手錶 URI 與做為引數傳入的內容 URI 進行比較 修改供應器的 ContentResolver 方法,例如 ContentResolver.insert()。如果有相符的配對, 實作 ContentObserver.onChange() 方法。

下列程式碼片段說明如何定義 ContentObserver 會在資料表發生時呼叫 requestSync() 變更:

Kotlin

// Constants
// Content provider scheme
const val SCHEME = "content://"
// Content provider authority
const val AUTHORITY = "com.example.android.datasync.provider"
// Path for the content provider table
const val TABLE_PATH = "data_table"
...
class MainActivity : FragmentActivity() {
    ...
    // A content URI for the content provider's data table
    private lateinit var uri: Uri
    // A content resolver for accessing the provider
    private lateinit var mResolver: ContentResolver
    ...
    inner class TableObserver(...) : ContentObserver(...) {
        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         * This method signature is provided for compatibility with
         * older platforms.
         */
        override fun onChange(selfChange: Boolean) {
            /*
             * Invoke the method signature available as of
             * Android platform version 4.1, with a null URI.
             */
            onChange(selfChange, null)
        }

        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         */
        override fun onChange(selfChange: Boolean, changeUri: Uri?) {
            /*
             * Ask the framework to run your sync adapter.
             * To maintain backward compatibility, assume that
             * changeUri is null.
             */
            ContentResolver.requestSync(account, AUTHORITY, null)
        }
        ...
    }
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Get the content resolver object for your app
        mResolver = contentResolver
        // Construct a URI that points to the content provider data table
        uri = Uri.Builder()
                .scheme(SCHEME)
                .authority(AUTHORITY)
                .path(TABLE_PATH)
                .build()
        /*
         * Create a content observer object.
         * Its code does not mutate the provider, so set
         * selfChange to "false"
         */
        val observer = TableObserver(false)
        /*
         * Register the observer for the data table. The table's path
         * and any of its subpaths trigger the observer.
         */
        mResolver.registerContentObserver(uri, true, observer)
        ...
    }
    ...
}

Java

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // Content provider scheme
    public static final String SCHEME = "content://";
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // Path for the content provider table
    public static final String TABLE_PATH = "data_table";
    // Account
    public static final String ACCOUNT = "default_account";
    // Global variables
    // A content URI for the content provider's data table
    Uri uri;
    // A content resolver for accessing the provider
    ContentResolver mResolver;
    ...
    public class TableObserver extends ContentObserver {
        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         * This method signature is provided for compatibility with
         * older platforms.
         */
        @Override
        public void onChange(boolean selfChange) {
            /*
             * Invoke the method signature available as of
             * Android platform version 4.1, with a null URI.
             */
            onChange(selfChange, null);
        }
        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         */
        @Override
        public void onChange(boolean selfChange, Uri changeUri) {
            /*
             * Ask the framework to run your sync adapter.
             * To maintain backward compatibility, assume that
             * changeUri is null.
             */
            ContentResolver.requestSync(mAccount, AUTHORITY, null);
        }
        ...
    }
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Get the content resolver object for your app
        mResolver = getContentResolver();
        // Construct a URI that points to the content provider data table
        uri = new Uri.Builder()
                  .scheme(SCHEME)
                  .authority(AUTHORITY)
                  .path(TABLE_PATH)
                  .build();
        /*
         * Create a content observer object.
         * Its code does not mutate the provider, so set
         * selfChange to "false"
         */
        TableObserver observer = new TableObserver(false);
        /*
         * Register the observer for the data table. The table's path
         * and any of its subpaths trigger the observer.
         */
        mResolver.registerContentObserver(uri, true, observer);
        ...
    }
    ...
}

定期執行同步轉換介面

如要定期執行同步轉換介面,您可以設定每次執行要經過的等待時間, 或在一天的特定時段執行,或兩者並行執行同步轉換介面 定期調整設定,大致符合伺服器的更新間隔。

同樣地,在伺服器相對閒置時,您可以從裝置上傳資料, 安排在晚上執行同步轉換介面。大多數使用者會讓電源維持開機狀態,並接上電源 所以這個時間通常有空房此外,裝置無法在以下位置執行其他工作: 與同步轉換介面同時執行但採取這種做法時,您就必須確保 每部裝置觸發資料移轉的時間稍有不同。如果所有裝置都搭載 同步轉接器可能會超載您的伺服器和行動服務供應商的資料 更是如此

一般來說,如果使用者不需即時更新,但希望定期執行 定期更新如果想在 Pod 的可用性之間取得平衡,建議您定期執行 即使裝置不會過度使用,也能讓您有效率地執行最新資料 再複習一下,機構節點 是所有 Google Cloud Platform 資源的根節點

如要定期執行同步轉換介面,請呼叫 addPeriodicSync()。這樣就能排定 同步處理轉換介面,自從使用同步轉換介面架構後 都必須考慮其他同步轉換介面執行程序,並盡力提升電池效能, 經過時間可能會延遲幾秒鐘。此外,在下列情況下,該架構將不會執行您的同步轉接程式 網路無法使用。

請注意,addPeriodicSync() 不會 在特定時段執行同步轉換介面。如要以大約 使用重複的鬧鐘做為觸發程序重複鬧鐘的說明請參閱 詳見 AlarmManager 參考說明文件。如果您使用 要設定的方法 setInexactRepeating() 包含某些變化的時段觸發條件,您仍應隨機決定開始時間。 確保同步轉換器透過不同裝置執行。

addPeriodicSync() 方法 停用setSyncAutomatically()。 因此可能在短時間內多次執行同步作業另外,只有少數 瀏覽器呼叫時可使用同步轉接程式控制標記 addPeriodicSync(); 所參照的 addPeriodicSync()

下列程式碼片段說明如何安排定期同步轉接程式執行:

Kotlin

// Content provider authority
const val AUTHORITY = "com.example.android.datasync.provider"
// Account
const val ACCOUNT = "default_account"
// Sync interval constants
const val SECONDS_PER_MINUTE = 60L
const val SYNC_INTERVAL_IN_MINUTES = 60L
const val SYNC_INTERVAL = SYNC_INTERVAL_IN_MINUTES * SECONDS_PER_MINUTE
...
class MainActivity : FragmentActivity() {
    ...
    // A content resolver for accessing the provider
    private lateinit var mResolver: ContentResolver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Get the content resolver for your app
        mResolver = contentResolver
        /*
         * Turn on periodic syncing
         */
        ContentResolver.addPeriodicSync(
                mAccount,
                AUTHORITY,
                Bundle.EMPTY,
                SYNC_INTERVAL)
        ...
    }
    ...
}

Java

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // Account
    public static final String ACCOUNT = "default_account";
    // Sync interval constants
    public static final long SECONDS_PER_MINUTE = 60L;
    public static final long SYNC_INTERVAL_IN_MINUTES = 60L;
    public static final long SYNC_INTERVAL =
            SYNC_INTERVAL_IN_MINUTES *
            SECONDS_PER_MINUTE;
    // Global variables
    // A content resolver for accessing the provider
    ContentResolver mResolver;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Get the content resolver for your app
        mResolver = getContentResolver();
        /*
         * Turn on periodic syncing
         */
        ContentResolver.addPeriodicSync(
                mAccount,
                AUTHORITY,
                Bundle.EMPTY,
                SYNC_INTERVAL);
        ...
    }
    ...
}

視需求執行同步轉換介面

執行同步轉換介面以回應使用者要求,是最不理想的策略 執行同步處理轉換介面這套架構經過特別設計,可節省電池電力 執行同步處理轉換程序執行同步處理作業的選項 變更會有效運用電池電力,因為電力是用來提供新資料。

相較之下,允許使用者隨選執行同步處理後,同步處理作業會自行執行, 消耗網路和電力資源的效率不彰。此外,提供隨選同步機制能引導使用者 並要求同步處理 (即使沒有證據顯示資料已變更),然後執行同步處理作業 ,未重新整理資料會導致電池耗電。一般來說,您的應用程式應 使用其他信號觸發同步作業,或安排定期同步處理,而無須使用者輸入內容。

不過,如果您仍想隨選執行同步轉換介面,請為 手動執行同步轉換介面,然後呼叫 ContentResolver.requestSync()

使用下列旗標執行隨選移轉作業:

SYNC_EXTRAS_MANUAL
強制執行手動同步處理。同步處理轉換介面架構會忽略現有設定。 例如 setSyncAutomatically() 設定的旗標
SYNC_EXTRAS_EXPEDITED
強制立即開始同步處理。如未設定,系統可能會等待數次 幾秒後才會執行同步要求,因為系統會試著將電池用量最佳化 排定在短時間內處理多個要求

下列程式碼片段說明如何呼叫 requestSync():以回應按鈕 點擊:

Kotlin

// Constants
// Content provider authority
val AUTHORITY = "com.example.android.datasync.provider"
// Account type
val ACCOUNT_TYPE = "com.example.android.datasync"
// Account
val ACCOUNT = "default_account"
...
class MainActivity : FragmentActivity() {
    ...
    // Instance fields
    private lateinit var mAccount: Account
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        /*
         * Create the placeholder account. The code for CreateSyncAccount
         * is listed in the lesson Creating a Sync Adapter
         */

        mAccount = createSyncAccount()
        ...
    }

    /**
     * Respond to a button click by calling requestSync(). This is an
     * asynchronous operation.
     *
     * This method is attached to the refresh button in the layout
     * XML file
     *
     * @param v The View associated with the method call,
     * in this case a Button
     */
    fun onRefreshButtonClick(v: View) {
        // Pass the settings flags by inserting them in a bundle
        val settingsBundle = Bundle().apply {
            putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true)
            putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true)
        }
        /*
         * Request the sync for the default account, authority, and
         * manual sync settings
         */
        ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle)
    }

Java

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // 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";
    // Instance fields
    Account mAccount;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        /*
         * Create the placeholder account. The code for CreateSyncAccount
         * is listed in the lesson Creating a Sync Adapter
         */

        mAccount = CreateSyncAccount(this);
        ...
    }
    /**
     * Respond to a button click by calling requestSync(). This is an
     * asynchronous operation.
     *
     * This method is attached to the refresh button in the layout
     * XML file
     *
     * @param v The View associated with the method call,
     * in this case a Button
     */
    public void onRefreshButtonClick(View v) {
        // Pass the settings flags by inserting them in a bundle
        Bundle settingsBundle = new Bundle();
        settingsBundle.putBoolean(
                ContentResolver.SYNC_EXTRAS_MANUAL, true);
        settingsBundle.putBoolean(
                ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
        /*
         * Request the sync for the default account, authority, and
         * manual sync settings
         */
        ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle);
    }