Exécuter un adaptateur de synchronisation

Remarque:Nous vous recommandons d'utiliser WorkManager comme solution recommandée pour la plupart des cas d'utilisation du traitement en arrière-plan. Veuillez vous reporter au guide de traitement en arrière-plan pour connaître la solution qui vous convient le mieux.

Dans les leçons précédentes de ce cours, vous avez appris à créer un composant d'adaptateur de synchronisation qui encapsule le code de transfert de données, et à ajouter les composants supplémentaires vous permettant de brancher l'adaptateur de synchronisation au système. Vous disposez désormais de tout ce dont vous avez besoin pour installer une application comprenant un adaptateur de synchronisation, mais aucun des codes que vous avez vu ne permet d'exécuter l'adaptateur de synchronisation.

Vous devez essayer d'exécuter votre adaptateur de synchronisation selon un calendrier ou suite indirectement à un événement. Par exemple, vous souhaitez peut-être que votre adaptateur de synchronisation s'exécute régulièrement, après une certaine période ou à une certaine heure de la journée. Vous pouvez également exécuter votre adaptateur de synchronisation lorsque des modifications sont apportées aux données stockées sur l'appareil. Vous devez éviter d'exécuter votre adaptateur de synchronisation suite à une action de l'utilisateur, car vous ne bénéficierez pas de tous les avantages de la capacité de planification du framework d'adaptateur de synchronisation. Par exemple, évitez de fournir un bouton d'actualisation dans votre interface utilisateur.

Vous disposez des options suivantes pour exécuter votre adaptateur de synchronisation:

Lorsque les données du serveur changent
Exécutez l'adaptateur de synchronisation en réponse à un message d'un serveur indiquant que les données basées sur le serveur ont changé. Cette option vous permet d'interroger le serveur afin d'actualiser les données du serveur sur l'appareil sans dégrader les performances ni gaspiller l'autonomie de la batterie.
En cas de modification des données de l'appareil
Exécutez un adaptateur de synchronisation lorsque les données changent sur l'appareil. Cette option vous permet d'envoyer des données modifiées de l'appareil à un serveur. Elle est particulièrement utile si vous devez vous assurer que le serveur dispose toujours des données les plus récentes de l'appareil. Cette option est simple à mettre en œuvre si vous stockez réellement des données dans votre fournisseur de contenu. Si vous faites appel à un fournisseur de contenu bouchon, il peut être plus difficile de détecter les modifications de données.
À intervalles réguliers
Exécutez un adaptateur de synchronisation après l'expiration de l'intervalle de votre choix ou exécutez-le chaque jour à une certaine heure.
À la demande
Exécutez l'adaptateur de synchronisation en réponse à une action de l'utilisateur. Cependant, pour offrir la meilleure expérience utilisateur possible, vous devez principalement vous appuyer sur l'une des options les plus automatisées. Les options automatisées vous permettent d'économiser la batterie et les ressources réseau.

Le reste de cette leçon décrit chacune de ces options plus en détail.

Exécuter l'adaptateur de synchronisation lorsque les données du serveur changent

Si votre application transfère des données à partir d'un serveur et que les données du serveur changent fréquemment, vous pouvez utiliser un adaptateur de synchronisation pour effectuer des téléchargements en réponse aux modifications de données. Pour exécuter l'adaptateur de synchronisation, demandez au serveur d'envoyer un message spécial à un BroadcastReceiver dans votre application. En réponse à ce message, appelez ContentResolver.requestSync() pour signaler au framework de l'adaptateur de synchronisation qu'il exécute votre adaptateur de synchronisation.

Google Cloud Messaging (GCM) fournit à la fois les composants de serveur et d'appareil dont vous avez besoin pour faire fonctionner ce système de messagerie. L'utilisation de GCM pour déclencher des transferts est plus fiable et plus efficace que l'interrogation de serveurs pour l'état. Bien que l'interrogation nécessite un Service toujours actif, GCM utilise un BroadcastReceiver qui est activé à la réception d'un message. Bien que l'interrogation à intervalles réguliers utilise la batterie même si aucune mise à jour n'est disponible, GCM n'envoie des messages qu'en cas de besoin.

Remarque:Si vous utilisez GCM pour déclencher votre adaptateur de synchronisation via une diffusion sur tous les appareils sur lesquels votre application est installée, n'oubliez pas qu'ils reçoivent votre message à peu près au même moment. Cette situation peut entraîner l'exécution simultanée de plusieurs instances de votre adaptateur de synchronisation, ce qui entraîne une surcharge du serveur et du réseau. Pour éviter cette situation pour une diffusion sur tous les appareils, vous devez envisager de différer le démarrage de l'adaptateur de synchronisation pour une période propre à chaque appareil.

L'extrait de code suivant vous montre comment exécuter requestSync() en réponse à un message GCM entrant:

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

Exécuter l'adaptateur de synchronisation lorsque les données du fournisseur de contenu changent

Si votre application collecte des données auprès d'un fournisseur de contenu et que vous souhaitez mettre à jour le serveur chaque fois que vous mettez à jour le fournisseur, vous pouvez configurer votre application pour qu'elle exécute automatiquement votre adaptateur de synchronisation. Pour ce faire, enregistrez un observateur pour le fournisseur de contenu. Lorsque les données de votre fournisseur de contenu changent, le framework du fournisseur de contenu appelle l'observateur. Dans l'observateur, appelez requestSync() pour indiquer au framework d'exécuter votre adaptateur de synchronisation.

Remarque:Si vous utilisez un fournisseur de contenu bouchon, celui-ci ne contient aucune donnée et onChange() n'est jamais appelé. Dans ce cas, vous devez fournir votre propre mécanisme de détection des modifications apportées aux données de l'appareil. Ce mécanisme permet également d'appeler requestSync() lorsque les données changent.

Pour créer un observateur pour votre fournisseur de contenu, étendez la classe ContentObserver et implémentez les deux formes de sa méthode onChange(). Dans onChange(), appelez requestSync() pour démarrer l'adaptateur de synchronisation.

Pour enregistrer l'observateur, transmettez-le en tant qu'argument dans un appel à registerContentObserver(). Dans cet appel, vous devez également transmettre un URI de contenu pour les données que vous souhaitez surveiller. Le framework du fournisseur de contenu compare cet URI de montre aux URI de contenu transmis en tant qu'arguments aux méthodes ContentResolver qui modifient votre fournisseur, tels que ContentResolver.insert(). En cas de correspondance, votre implémentation de ContentObserver.onChange() est appelée.

L'extrait de code suivant vous montre comment définir un ContentObserver qui appelle requestSync() lorsqu'une table est modifiée:

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

Exécuter l'adaptateur de synchronisation régulièrement

Vous pouvez exécuter votre adaptateur de synchronisation régulièrement en définissant un délai d'attente entre les exécutions, en l'exécutant à certaines heures de la journée, ou les deux. L'exécution régulière de votre adaptateur de synchronisation vous permet d'obtenir approximativement l'intervalle de mise à jour de votre serveur.

De même, vous pouvez importer des données à partir de l'appareil lorsque votre serveur est relativement inactif, en planifiant l'exécution de votre adaptateur de synchronisation la nuit. La plupart des utilisateurs laissent leur appareil allumé et branché la nuit. Ce délai est donc généralement disponible. De plus, l'appareil n'exécute pas d'autres tâches en même temps que votre adaptateur de synchronisation. Toutefois, si vous adoptez cette approche, vous devez vous assurer que chaque appareil déclenche un transfert de données à un moment légèrement différent. Si tous les appareils exécutent votre adaptateur de synchronisation en même temps, vous risquez de surcharger les réseaux de données de votre serveur et de votre opérateur mobile.

En général, les exécutions périodiques sont logiques si vos utilisateurs n'ont pas besoin de mises à jour instantanées, mais s'attendent à des mises à jour régulières. Les exécutions périodiques sont également judicieuses si vous souhaitez équilibrer la disponibilité des données à jour avec l'efficacité des exécutions plus petites des adaptateurs de synchronisation qui n'utilisent pas trop les ressources de l'appareil.

Pour exécuter votre adaptateur de synchronisation à intervalles réguliers, appelez addPeriodicSync(). Cette opération planifie l'exécution de votre adaptateur de synchronisation au bout d'un certain temps. Étant donné que le framework de l'adaptateur de synchronisation doit tenir compte des autres exécutions de l'adaptateur de synchronisation et tente de maximiser l'efficacité de la batterie, le temps écoulé peut varier de quelques secondes. De plus, le framework n'exécutera pas votre adaptateur de synchronisation si le réseau n'est pas disponible.

Notez que addPeriodicSync() n'exécute pas l'adaptateur de synchronisation à un moment précis de la journée. Pour exécuter votre adaptateur de synchronisation à peu près à la même heure tous les jours, utilisez une alarme récurrente comme déclencheur. Les alarmes répétées sont décrites plus en détail dans la documentation de référence sur AlarmManager. Si vous utilisez la méthode setInexactRepeating() pour définir des déclencheurs basés sur l'heure de la journée qui présentent des variations, vous devez quand même randomiser l'heure de début pour vous assurer que l'adaptateur de synchronisation s'exécute à partir de différents appareils et est échelonné.

La méthode addPeriodicSync() ne désactive pas setSyncAutomatically(). Vous pouvez donc obtenir plusieurs exécutions de synchronisation dans un laps de temps relativement court. De plus, seuls quelques indicateurs de contrôle d'adaptateur de synchronisation sont autorisés dans un appel à addPeriodicSync(). Les indicateurs non autorisés sont décrits dans la documentation référencée pour addPeriodicSync().

L'extrait de code suivant vous montre comment planifier des exécutions périodiques de l'adaptateur de synchronisation:

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

Exécuter l'adaptateur de synchronisation à la demande

L'exécution de votre adaptateur de synchronisation en réponse à la requête d'un utilisateur est la stratégie la moins privilégiée pour exécuter un adaptateur de synchronisation. Le framework est spécifiquement conçu pour économiser la batterie lorsqu'il exécute des adaptateurs de synchronisation selon un calendrier. Les options qui exécutent une synchronisation en réponse à des modifications des données utilisent efficacement la batterie, car la puissance sert à fournir de nouvelles données.

En revanche, autoriser les utilisateurs à exécuter une synchronisation à la demande implique que la synchronisation s'exécute seule, ce qui constitue une utilisation inefficace du réseau et de l'alimentation. De plus, la synchronisation à la demande amène les utilisateurs à demander une synchronisation même s'il n'y a aucune preuve que les données ont changé. Exécuter une synchronisation qui n'actualise pas les données constitue une utilisation inefficace de la batterie. En règle générale, votre application doit utiliser d'autres signaux pour déclencher une synchronisation ou les planifier à intervalles réguliers, sans intervention de l'utilisateur.

Toutefois, si vous souhaitez toujours exécuter l'adaptateur de synchronisation à la demande, définissez ses indicateurs pour une exécution manuelle, puis appelez ContentResolver.requestSync().

Exécutez des transferts à la demande avec les options suivantes:

SYNC_EXTRAS_MANUAL
Force une synchronisation manuelle. Le framework de l'adaptateur de synchronisation ignore les paramètres existants, tels que l'option définie par setSyncAutomatically().
SYNC_EXTRAS_EXPEDITED
Force le démarrage immédiat de la synchronisation. Si vous ne définissez pas cette option, le système peut attendre plusieurs secondes avant d'exécuter la requête de synchronisation, car il tente d'optimiser l'utilisation de la batterie en planifiant de nombreuses requêtes sur une courte période.

L'extrait de code suivant vous montre comment appeler requestSync() en réponse à un clic sur un bouton:

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