Menjalankan adaptor sinkronisasi

Catatan: Kami merekomendasikan WorkManager sebagai solusi untuk sebagian besar kasus penggunaan pemrosesan di latar belakang. Harap baca panduan pemrosesan di latar belakang untuk mengetahui solusi terbaik bagi Anda.

Di tutorial sebelumnya di class ini, Anda telah mempelajari cara membuat komponen adaptor sinkronisasi yang merangkum kode transfer data, dan cara menambahkan komponen tambahan yang memungkinkan Anda mencolokkan adaptor sinkronisasi ke sistem. Kini Anda memiliki semua yang diperlukan untuk menginstal aplikasi yang menyertakan adaptor sinkronisasi, tetapi tidak satu pun kode yang Anda lihat benar-benar menjalankan adaptor sinkronisasi.

Anda harus mencoba menjalankan adaptor sinkronisasi berdasarkan jadwal atau sebagai hasil tidak langsung dari beberapa peristiwa. Misalnya, Anda mungkin ingin adaptor sinkronisasi berjalan sesuai jadwal rutin, baik setelah jangka waktu tertentu atau pada waktu tertentu. Anda mungkin juga ingin menjalankan adaptor sinkronisasi saat ada perubahan pada data yang disimpan di perangkat. Sebaiknya hindari menjalankan adaptor sinkronisasi sebagai hasil langsung dari tindakan pengguna, karena dengan melakukan hal ini, Anda tidak akan mendapatkan manfaat penuh dari kemampuan penjadwalan framework adaptor sinkronisasi. Misalnya, sebaiknya hindari menyediakan tombol refresh pada antarmuka pengguna.

Anda memiliki opsi berikut untuk menjalankan adaptor sinkronisasi:

Saat data server berubah
Jalankan adaptor sinkronisasi sebagai respons terhadap pesan dari server, yang menunjukkan bahwa data berbasis server telah berubah. Opsi ini memungkinkan Anda memuat ulang data dari server ke perangkat tanpa menurunkan performa atau menghabiskan masa pakai baterai dengan melakukan polling pada server.
Saat data perangkat berubah
Jalankan adaptor sinkronisasi saat data berubah di perangkat. Opsi ini memungkinkan Anda mengirim data yang dimodifikasi dari perangkat ke server, dan sangat berguna jika Anda perlu memastikan bahwa server selalu memiliki data perangkat terbaru. Opsi ini mudah diterapkan jika Anda benar-benar menyimpan data di penyedia konten Anda. Jika Anda menggunakan penyedia konten stub, deteksi perubahan data mungkin akan lebih sulit.
Secara berkala
Jalankan adaptor sinkronisasi setelah interval yang Anda pilih berakhir, atau jalankan di waktu tertentu setiap hari.
On demand
Jalankan adaptor sinkronisasi sebagai respons terhadap tindakan pengguna. Namun, untuk memberikan pengalaman pengguna terbaik, Anda harus terutama mengandalkan salah satu opsi yang lebih otomatis. Dengan menggunakan opsi otomatis, Anda akan menghemat resource jaringan dan baterai.

Selanjutnya, tutorial ini menjelaskan setiap opsi secara lebih detail.

Menjalankan adaptor sinkronisasi saat data server berubah

Jika aplikasi Anda mentransfer data dari server dan data server sering berubah, Anda dapat menggunakan adaptor sinkronisasi untuk melakukan download sebagai respons terhadap perubahan data. Untuk menjalankan adaptor sinkronisasi, minta server mengirim pesan khusus ke BroadcastReceiver di aplikasi Anda. Sebagai respons terhadap pesan ini, panggil ContentResolver.requestSync() untuk memberi sinyal pada framework adaptor sinkronisasi agar menjalankan adaptor sinkronisasi Anda.

Google Cloud Messaging (GCM) menyediakan komponen server dan perangkat yang Anda perlukan agar sistem pesan ini berfungsi. Menggunakan GCM untuk memicu transfer lebih andal dan lebih efisien daripada melakukan polling server untuk status. Polling memerlukan Service yang selalu aktif, sedangkan GCM menggunakan BroadcastReceiver yang diaktifkan saat pesan masuk. Meskipun polling secara berkala menggunakan daya baterai meskipun tidak ada pembaruan, GCM hanya mengirim pesan jika diperlukan.

Catatan: Jika Anda menggunakan GCM untuk memicu adaptor sinkronisasi melalui siaran ke semua perangkat tempat aplikasi diinstal, perlu diingat bahwa pesan Anda akan diterima di perangkat tersebut di saat yang hampir bersamaan. Situasi ini dapat menyebabkan beberapa instance adaptor sinkronisasi berjalan secara bersamaan, sehingga menyebabkan server dan jaringan kelebihan beban. Agar tidak terjadi siaran ke semua perangkat, sebaiknya pertimbangkan untuk menunda dimulainya adaptor sinkronisasi selama periode yang unik untuk setiap perangkat.

Cuplikan kode berikut menunjukkan cara menjalankan requestSync() sebagai respons terhadap pesan GCM yang masuk:

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);
            ...
        }
        ...
    }
    ...
}

Menjalankan adaptor sinkronisasi saat data penyedia konten berubah

Jika aplikasi Anda mengumpulkan data di penyedia konten, dan Anda ingin mengupdate server setiap kali mengupdate penyedia, Anda dapat menyiapkan aplikasi agar menjalankan adaptor sinkronisasi secara otomatis. Untuk melakukannya, daftarkan observer untuk penyedia konten. Jika data di penyedia konten Anda berubah, framework penyedia konten akan memanggil observer. Dalam observer, panggil requestSync() untuk memberi tahu framework agar menjalankan adaptor sinkronisasi Anda.

Catatan: Jika menggunakan penyedia konten stub, Anda tidak memiliki data di penyedia konten dan onChange() tidak pernah dipanggil. Dalam hal ini, Anda harus menyediakan mekanisme sendiri untuk mendeteksi perubahan pada data perangkat. Mekanisme ini juga bertanggung jawab untuk memanggil requestSync() saat data berubah.

Untuk membuat observer bagi penyedia konten Anda, perluas class ContentObserver dan implementasikan kedua bentuk metode onChange(). Di onChange(), panggil requestSync() untuk memulai adaptor sinkronisasi.

Untuk mendaftarkan observer, teruskan sebagai argumen dalam panggilan ke registerContentObserver(). Dalam panggilan ini, Anda juga harus meneruskan URI konten untuk data yang ingin dipantau. Framework penyedia konten akan membandingkan URI tontonan ini dengan URI konten yang diteruskan sebagai argumen ke metode ContentResolver yang mengubah penyedia Anda, seperti ContentResolver.insert(). Jika ada kecocokan, penerapan ContentObserver.onChange() akan dipanggil.

Cuplikan kode berikut menunjukkan cara menetapkan ContentObserver yang memanggil requestSync() saat tabel berubah:

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);
        ...
    }
    ...
}

Menjalankan adaptor sinkronisasi secara berkala

Anda dapat menjalankan adaptor sinkronisasi secara berkala dengan menyetel periode waktu tunggu antar-proses, atau dengan menjalankannya pada waktu tertentu, atau keduanya. Menjalankan adaptor sinkronisasi secara berkala memungkinkan Anda mencocokkan interval update server secara kasar.

Demikian pula, Anda dapat mengupload data dari perangkat saat server relatif tidak ada aktivitas, dengan menjadwalkan adaptor sinkronisasi agar berjalan di malam hari. Sebagian besar pengguna membiarkannya menyala dan dicolokkan di malam hari, sehingga waktu ini biasanya tersedia. Selain itu, perangkat tidak sedang menjalankan tugas lain secara bersamaan dengan adaptor sinkronisasi Anda. Namun, jika melakukan pendekatan ini, Anda harus memastikan bahwa setiap perangkat memicu transfer data pada waktu yang sedikit berbeda. Jika semua perangkat menjalankan adaptor sinkronisasi Anda secara bersamaan, kemungkinan Anda akan membebani jaringan data server dan penyedia seluler.

Secara umum, menjalankan proses secara berkala wajar jika pengguna tidak memerlukan update instan, tetapi ingin mendapatkan update rutin. Menjalankan secara berkala juga masuk akal jika Anda ingin menyeimbangkan ketersediaan data terbaru dengan efisiensi operasi adaptor sinkronisasi yang lebih kecil yang tidak menggunakan resource perangkat secara berlebihan.

Untuk menjalankan adaptor sinkronisasi secara berkala, panggil addPeriodicSync(). Tindakan ini akan menjadwalkan adaptor sinkronisasi untuk berjalan setelah jangka waktu tertentu berlalu. Karena framework adaptor sinkronisasi harus memperhitungkan eksekusi adaptor sinkronisasi lainnya dan mencoba memaksimalkan efisiensi baterai, waktu berlalu dapat bervariasi selama beberapa detik. Selain itu, framework tidak akan menjalankan adaptor sinkronisasi jika jaringan tidak tersedia.

Perhatikan bahwa addPeriodicSync() tidak menjalankan adaptor sinkronisasi di waktu tertentu. Untuk menjalankan adaptor sinkronisasi kira-kira pada waktu yang sama setiap hari, gunakan alarm berulang sebagai pemicu. Alarm berulang dijelaskan secara lebih mendetail dalam dokumentasi referensi untuk AlarmManager. Jika Anda menggunakan metode setInexactRepeating() untuk menetapkan pemicu waktu yang memiliki beberapa variasi, Anda tetap harus mengacak waktu mulai untuk memastikan bahwa adaptor sinkronisasi berjalan dari perangkat yang berbeda secara bertahap.

Metode addPeriodicSync() tidak menonaktifkan setSyncAutomatically(), sehingga Anda bisa mendapatkan beberapa sinkronisasi yang berjalan dalam waktu yang relatif singkat. Selain itu, hanya beberapa flag kontrol adaptor sinkronisasi yang diizinkan dalam panggilan ke addPeriodicSync(); flag yang tidak diizinkan dijelaskan dalam dokumentasi referensi untuk addPeriodicSync().

Cuplikan kode berikut menunjukkan cara menjadwalkan pengoperasian adaptor sinkronisasi berkala:

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);
        ...
    }
    ...
}

Menjalankan adaptor sinkronisasi berdasarkan permintaan

Menjalankan adaptor sinkronisasi sebagai respons terhadap permintaan pengguna adalah strategi yang paling tidak disarankan untuk menjalankan adaptor sinkronisasi. Framework ini dirancang khusus untuk menghemat daya baterai saat menjalankan adaptor sinkronisasi sesuai jadwal. Opsi yang menjalankan sinkronisasi sebagai respons terhadap perubahan data menggunakan daya baterai secara efektif, karena daya tersebut digunakan untuk menyediakan data baru.

Sebagai perbandingan, mengizinkan pengguna menjalankan sinkronisasi sesuai permintaan berarti sinkronisasi berjalan dengan sendirinya, sehingga penggunaan resource jaringan dan daya menjadi tidak efisien. Selain itu, menyediakan sinkronisasi sesuai permintaan akan membuat pengguna meminta sinkronisasi meskipun tidak ada bukti bahwa data telah berubah, dan menjalankan sinkronisasi yang tidak memuat ulang data adalah penggunaan daya baterai yang tidak efektif. Secara umum, aplikasi Anda harus menggunakan sinyal lain untuk memicu sinkronisasi atau menjadwalkannya secara berkala, tanpa input pengguna.

Namun, jika Anda tetap ingin menjalankan adaptor sinkronisasi sesuai permintaan, setel tanda adaptor sinkronisasi agar adaptor sinkronisasi berjalan secara manual, lalu panggil ContentResolver.requestSync().

Jalankan transfer berdasarkan permintaan dengan tanda berikut:

SYNC_EXTRAS_MANUAL
Memaksa sinkronisasi manual. Framework adaptor sinkronisasi mengabaikan setelan yang ada, seperti tanda yang ditetapkan oleh setSyncAutomatically().
SYNC_EXTRAS_EXPEDITED
Memaksa sinkronisasi untuk segera dimulai. Jika Anda tidak menyetelnya, sistem dapat menunggu beberapa detik sebelum menjalankan permintaan sinkronisasi, karena sistem mencoba mengoptimalkan penggunaan baterai dengan menjadwalkan banyak permintaan dalam waktu singkat.

Cuplikan kode berikut menunjukkan cara memanggil requestSync() sebagai respons terhadap klik tombol:

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);
    }