Créer 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.

Le composant de l'adaptateur de synchronisation de votre application encapsule le code des tâches qui transfèrent des données entre l'appareil et un serveur. En fonction de la planification et des déclencheurs que vous fournissez dans votre application, le framework de l'adaptateur de synchronisation exécute le code du composant de l'adaptateur de synchronisation. Pour ajouter un composant d'adaptateur de synchronisation à votre application, vous devez ajouter les éléments suivants:

Classe de l'adaptateur de synchronisation.
Classe qui encapsule votre code de transfert de données dans une interface compatible avec le framework de l'adaptateur de synchronisation.
Service lié.
Composant qui permet au framework de l'adaptateur de synchronisation d'exécuter le code de votre classe d'adaptateur de synchronisation.
Fichier de métadonnées XML de l'adaptateur de synchronisation.
Fichier contenant des informations sur votre adaptateur de synchronisation. Le framework lit ce fichier pour savoir comment charger et planifier votre transfert de données.
Déclarations dans le fichier manifeste de l'application
Fichier XML qui déclare le service lié et pointe vers les métadonnées spécifiques à l'adaptateur.

Cette leçon vous montre comment définir ces éléments.

Créer une classe d'adaptateur de synchronisation

Dans cette partie de la leçon, vous allez apprendre à créer la classe de l'adaptateur de synchronisation qui encapsule le code de transfert de données. La création de la classe comprend l'extension de la classe de base de l'adaptateur de synchronisation, la définition des constructeurs pour la classe et la mise en œuvre de la méthode dans laquelle vous définissez les tâches de transfert de données.

Étendre la classe de l'adaptateur de synchronisation de base

Pour créer le composant d'adaptateur de synchronisation, commencez par étendre AbstractThreadedSyncAdapter et écrire ses constructeurs. Utilisez les constructeurs pour exécuter des tâches de configuration chaque fois que votre composant d'adaptateur de synchronisation est entièrement créé, de la même manière que vous utilisez Activity.onCreate() pour configurer une activité. Par exemple, si votre application utilise un fournisseur de contenu pour stocker des données, utilisez les constructeurs pour obtenir une instance ContentResolver. Étant donné qu'une deuxième forme du constructeur a été ajoutée dans la version 3.0 de la plate-forme Android pour prendre en charge l'argument parallelSyncs, vous devez créer deux formes de constructeur pour maintenir la compatibilité.

Remarque:Le framework de l'adaptateur de synchronisation est conçu pour fonctionner avec les composants de l'adaptateur de synchronisation qui sont des instances singleton. L'instanciation du composant de l'adaptateur de synchronisation est abordée plus en détail dans la section Lier l'adaptateur de synchronisation au framework.

L'exemple suivant montre comment implémenter AbstractThreadedSyncAdapter et ses constructeurs:

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

Ajouter le code de transfert de données

Le composant de l'adaptateur de synchronisation n'effectue pas automatiquement le transfert des données. À la place, il encapsule votre code de transfert de données afin que le framework de l'adaptateur de synchronisation puisse exécuter le transfert de données en arrière-plan, sans aucune intervention de votre application. Lorsque le framework est prêt à synchroniser les données de votre application, il appelle votre implémentation de la méthode onPerformSync().

Pour faciliter le transfert des données du code de votre application principale vers le composant de l'adaptateur de synchronisation, le framework de l'adaptateur de synchronisation appelle onPerformSync() avec les arguments suivants:

Compte
Un objet Account associé à l'événement ayant déclenché l'adaptateur de synchronisation. Si votre serveur n'utilise pas de comptes, vous n'avez pas besoin d'utiliser les informations de cet objet.
Bonus
Un objet Bundle contenant des indicateurs envoyés par l'événement ayant déclenché l'adaptateur de synchronisation.
Autorité
Autorité d'un fournisseur de contenu dans le système. Votre application doit avoir accès à ce fournisseur. Généralement, l'autorité correspond à un fournisseur de contenu dans votre propre application.
Client d'un fournisseur de contenu
Un ContentProviderClient pour le fournisseur de contenu indiqué par l'argument "authority". Un ContentProviderClient est une interface publique légère vers un fournisseur de contenu. Il possède les mêmes fonctionnalités de base qu'un ContentResolver. Si vous utilisez un fournisseur de contenu pour stocker les données de votre application, vous pouvez vous connecter au fournisseur avec cet objet. Sinon, vous pouvez l'ignorer.
Résultat de la synchronisation
Un objet SyncResult que vous utilisez pour envoyer des informations au framework de l'adaptateur de synchronisation.

L'extrait de code suivant montre la structure globale de 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.
     */
}

Bien que l'implémentation réelle de onPerformSync() soit spécifique aux exigences de synchronisation des données et aux protocoles de connexion au serveur de votre application, elle doit effectuer quelques tâches générales:

Connexion à un serveur
Bien que vous puissiez supposer que le réseau est disponible au début du transfert de données, le framework de l'adaptateur de synchronisation ne se connecte pas automatiquement à un serveur.
Télécharger et importer des données
Un adaptateur de synchronisation n'automatise aucune tâche de transfert de données. Si vous souhaitez télécharger des données à partir d'un serveur et les stocker chez un fournisseur de contenu, vous devez fournir le code qui demande les données, les télécharge et les insère dans le fournisseur. De même, si vous souhaitez envoyer des données à un serveur, vous devez les lire à partir d'un fichier, d'une base de données ou d'un fournisseur, puis envoyer la requête d'importation nécessaire. Vous devez également gérer les erreurs réseau qui se produisent pendant l'exécution du transfert de données.
Gérer les conflits de données ou déterminer le niveau d'actualisation des données
Un adaptateur de synchronisation ne gère pas automatiquement les conflits entre les données du serveur et celles de l'appareil. En outre, elle ne détecte pas automatiquement si les données du serveur sont plus récentes que celles de l'appareil, ou inversement. Vous devez fournir vos propres algorithmes pour gérer cette situation.
Effectuer un nettoyage.
Fermez toujours les connexions à un serveur, et nettoyez les fichiers temporaires et les caches à la fin du transfert de données.

Remarque:Le framework de l'adaptateur de synchronisation exécute onPerformSync() sur un thread d'arrière-plan. Vous n'avez donc pas besoin de configurer votre propre traitement en arrière-plan.

En plus des tâches liées à la synchronisation, vous devez essayer de combiner vos tâches réseau habituelles et de les ajouter à onPerformSync(). En concentrant toutes vos tâches réseau dans cette méthode, vous préservez l'énergie de la batterie nécessaire au démarrage et à l'arrêt des interfaces réseau. Pour en savoir plus sur l'optimisation de l'accès réseau, consultez la classe de formation Transférer des données sans vider la batterie, qui décrit plusieurs tâches d'accès réseau que vous pouvez inclure dans votre code de transfert de données.

Lier l'adaptateur de synchronisation au framework

Votre code de transfert de données est maintenant encapsulé dans un composant d'adaptateur de synchronisation, mais vous devez permettre au framework d'accéder à votre code. Pour ce faire, vous devez créer un Service lié qui transmet au framework un objet de liaison Android spécial du composant de l'adaptateur de synchronisation. Avec cet objet de liaison, le framework peut appeler la méthode onPerformSync() et lui transmettre des données.

Instanciez votre composant d'adaptateur de synchronisation en tant que singleton dans la méthode onCreate() du service. En instanciant le composant dans onCreate(), vous différez sa création jusqu'au démarrage du service, ce qui se produit lorsque le framework tente pour la première fois d'exécuter le transfert de données. Vous devez instancier le composant de manière sécurisée au cas où le framework de l'adaptateur de synchronisation met en file d'attente plusieurs exécutions de votre adaptateur de synchronisation en réponse à des déclencheurs ou à une planification.

Par exemple, l'extrait de code suivant vous montre comment créer une classe qui implémente l'élément Service lié, instancie votre composant d'adaptateur de synchronisation et obtient l'objet de liaison 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();
    }
}

Remarque:Pour obtenir un exemple plus détaillé de service lié pour un adaptateur de synchronisation, consultez l'application exemple.

Ajouter le compte requis par le framework

Le framework d'adaptateur de synchronisation nécessite que chaque adaptateur dispose d'un type de compte. Vous avez déclaré la valeur du type de compte dans la section Ajouter le fichier de métadonnées Authenticator. Vous devez maintenant configurer ce type de compte dans le système Android. Pour configurer le type de compte, ajoutez un compte d'espace réservé qui l'utilise en appelant addAccountExplicitly().

Le meilleur endroit pour appeler cette méthode est dans la méthode onCreate() de l'activité d'ouverture de votre application. L'extrait de code suivant vous montre comment procéder:

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.
             */
        }
    }
    ...
}

Ajouter le fichier de métadonnées de l'adaptateur de synchronisation

Pour brancher votre composant d'adaptateur de synchronisation dans le framework, vous devez lui fournir des métadonnées qui le décrivent et fournissent des indicateurs supplémentaires. Les métadonnées spécifient le type de compte que vous avez créé pour votre adaptateur de synchronisation, déclare une autorité de fournisseur de contenu associée à votre application, contrôle une partie de l'interface utilisateur système liée aux adaptateurs de synchronisation et déclare d'autres indicateurs liés à la synchronisation. Déclarez ces métadonnées dans un fichier XML spécial stocké dans le répertoire /res/xml/ de votre projet d'application. Vous pouvez attribuer n'importe quel nom au fichier, mais il est généralement appelé syncadapter.xml.

Ce fichier XML contient un seul élément XML <sync-adapter> possédant les attributs suivants:

android:contentAuthority
Autorité d'URI de votre fournisseur de contenu. Si vous avez créé un fournisseur de contenu bouchon pour votre application dans la leçon précédente Créer un fournisseur de contenu stub, utilisez la valeur que vous avez spécifiée pour l'attribut android:authorities dans l'élément <provider> que vous avez ajouté au fichier manifeste de votre application. Cet attribut est décrit plus en détail dans la section Déclarer le fournisseur dans le fichier manifeste.
Si vous transférez des données d'un fournisseur de contenu vers un serveur avec votre adaptateur de synchronisation, cette valeur doit être identique à l'autorité d'URI de contenu que vous utilisez pour ces données. Cette valeur est également l'une des autorités que vous spécifiez dans l'attribut android:authorities de l'élément <provider> qui déclare votre fournisseur dans le fichier manifeste de votre application.
android:accountType
Type de compte requis par le framework de l'adaptateur de synchronisation. La valeur doit être identique à celle du type de compte que vous avez fournie lors de la création du fichier de métadonnées de l'authentificateur, comme décrit dans la section Ajouter le fichier de métadonnées Authenticator. Il s'agit également de la valeur que vous avez spécifiée pour la constante ACCOUNT_TYPE dans l'extrait de code de la section Add the Account Required by the Framework (Ajouter le compte requis par le framework).
Attributs des paramètres
android:userVisible
Définit la visibilité du type de compte de l'adaptateur de synchronisation. Par défaut, l'icône et le libellé de compte associés au type de compte sont visibles dans la section Accounts (Comptes) de l'application Paramètres du système. Vous devez donc rendre votre adaptateur de synchronisation invisible, sauf si vous avez un type de compte ou un domaine facilement associé à votre application. Si vous rendez votre type de compte invisible, vous pouvez toujours autoriser les utilisateurs à contrôler votre adaptateur de synchronisation via une interface utilisateur dans l'une des activités de votre application.
android:supportsUploading
Elles permettent d'importer des données dans le cloud. Définissez cette valeur sur false si votre application ne télécharge que des données.
android:allowParallelSyncs
Permet à plusieurs instances de votre composant d'adaptateur de synchronisation de s'exécuter en même temps. Utilisez cette option si votre application accepte plusieurs comptes utilisateur et que vous souhaitez autoriser plusieurs utilisateurs à transférer des données en parallèle. Cette option n'a aucun effet si vous n'exécutez jamais plusieurs transferts de données.
android:isAlwaysSyncable
Indique au framework de l'adaptateur de synchronisation qu'il peut exécuter l'adaptateur de synchronisation à tout moment que vous avez spécifié. Si vous souhaitez contrôler de manière automatisée le moment où votre adaptateur de synchronisation peut s'exécuter, définissez cet indicateur sur false, puis appelez requestSync() pour exécuter l'adaptateur de synchronisation. Pour en savoir plus sur l'exécution d'un adaptateur de synchronisation, consultez la leçon Exécuter un adaptateur de synchronisation.

L'exemple suivant présente le code XML d'un adaptateur de synchronisation qui utilise un seul compte d'espace réservé et n'effectue que des téléchargements.

<?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"/>

Déclarer l'adaptateur de synchronisation dans le fichier manifeste

Une fois que vous avez ajouté le composant de l'adaptateur de synchronisation à votre application, vous devez demander les autorisations liées à son utilisation et déclarer la Service liée que vous avez ajoutée.

Étant donné que le composant d'adaptateur de synchronisation exécute un code qui transfère des données entre le réseau et l'appareil, vous devez demander l'autorisation d'accéder à Internet. En outre, votre application doit demander l'autorisation de lire et d'écrire les paramètres de l'adaptateur de synchronisation. Vous pouvez ainsi le contrôler de manière automatisée à partir d'autres composants de votre application. Vous devez également demander une autorisation spéciale permettant à votre application d'utiliser le composant d'authentification que vous avez créé dans la leçon Créer un authentificateur stub.

Pour demander ces autorisations, ajoutez le code suivant au fichier manifeste de votre application en tant qu'éléments enfants de <manifest>:

android.permission.INTERNET
Autorise le code de l'adaptateur de synchronisation à accéder à Internet pour télécharger ou importer des données de l'appareil vers un serveur. Vous n'avez pas besoin d'ajouter à nouveau cette autorisation si vous l'avez déjà demandée.
android.permission.READ_SYNC_SETTINGS
Permet à votre application de lire les paramètres actuels de l'adaptateur de synchronisation. Par exemple, vous avez besoin de cette autorisation pour appeler getIsSyncable().
android.permission.WRITE_SYNC_SETTINGS
Permet à votre application de contrôler les paramètres de l'adaptateur de synchronisation. Vous avez besoin de cette autorisation pour définir des exécutions d'adaptateur de synchronisation périodique à l'aide de addPeriodicSync(). Cette autorisation n'est pas requise pour appeler requestSync(). Pour en savoir plus sur l'exécution de l'adaptateur de synchronisation, consultez la section Exécuter un adaptateur de synchronisation.

L'extrait de code suivant montre comment ajouter les autorisations:

<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>

Enfin, pour déclarer le Service lié que le framework utilise pour interagir avec votre adaptateur de synchronisation, ajoutez le code XML suivant au fichier manifeste de votre application en tant qu'élément enfant de <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>

L'élément <intent-filter> configure un filtre déclenché par l'action d'intent android.content.SyncAdapter, envoyée par le système pour exécuter l'adaptateur de synchronisation. Lorsque le filtre est déclenché, le système démarre le service lié que vous avez créé, qui est SyncService dans cet exemple. L'attribut android:exported="false" n'autorise que votre application et le système à accéder à Service. L'attribut android:process=":sync" indique au système d'exécuter Service dans un processus partagé global nommé sync. Si vous disposez de plusieurs adaptateurs de synchronisation dans votre application, ils peuvent partager ce processus, ce qui réduit les frais généraux.

L'élément <meta-data> fournit le nom du fichier XML de métadonnées de l'adaptateur de synchronisation que vous avez créé précédemment. L'attribut android:name indique que ces métadonnées sont destinées au framework de l'adaptateur de synchronisation. L'élément android:resource spécifie le nom du fichier de métadonnées.

Vous disposez maintenant de tous les composants de votre adaptateur de synchronisation. La leçon suivante vous explique comment indiquer au framework de l'adaptateur de synchronisation d'exécuter l'adaptateur de synchronisation en réponse à un événement ou de façon régulière.