Pourquoi ContentResolver.requestSync ne déclenche-t-il pas une synchronisation?

112

J'essaie d'implémenter le modèle d'adaptateur Content-Provider-Sync comme indiqué dans Google IO - diapositive 26. Mon fournisseur de contenu fonctionne et ma synchronisation fonctionne lorsque je la déclenche à partir de l'application Dev Tools Sync Tester, mais lorsque j'appelle ContentResolver. requestSync (compte, autorité, bundle) depuis mon ContentProvider, ma synchronisation n'est jamais déclenchée.

ContentResolver.requestSync(
        account, 
        AUTHORITY, 
        new Bundle());

Edit - extrait de manifeste ajouté Mon manifeste xml contient:

<service
    android:name=".sync.SyncService"
    android:exported="true">
    <intent-filter>
        <action
            android:name="android.content.SyncAdapter" />
    </intent-filter>
    <meta-data android:name="android.content.SyncAdapter"
    android:resource="@xml/syncadapter" />
</service>

--Éditer

Mon syncadapter.xml associé à mon service de synchronisation contient:

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"  
    android:contentAuthority="AUTHORITY"
    android:accountType="myaccounttype"
    android:supportsUploading="true"
/>

Je ne sais pas quel autre code serait utile. Le compte passé à requestSync est de "myaccounttype" et l'AUTORITÉ passée à l'appel correspond à mon adaptateur syc xml.

ContentResolver.requestSync est-il le moyen correct de demander une synchronisation? Il semble que l'outil de test de synchronisation se lie directement au service et appelle la synchronisation, mais cela semble aller à l'encontre de l'objectif de l'intégration avec l'architecture de synchronisation.

Si c'est la bonne façon de demander une synchronisation, pourquoi le testeur de synchronisation fonctionnerait-il, mais pas mon appel à ContentResolver.requestSync? Dois-je passer quelque chose dans le bundle?

Je teste dans l'émulateur sur des appareils exécutant 2.1 et 2.2.

Ben
la source
3
Mon problème était que mon point de rupture dans l'adaptateur de synchronisation n'était pas atteint ... Puis j'ai réalisé que j'essayais de déboguer un service ... J'espère que cela aide d'autres comme moi.
dangalg
2
Les points d'arrêt dans le service de l'adaptateur de synchronisation échoueront à se déclencher. En effet, le service d'adaptateur de synchronisation s'exécute dans un processus distinct. C'est ce à quoi @danglang faisait allusion. Voir aussi cette question: stackoverflow.com/questions/8559458/…
Konstantin Schubert
1
Dans mon cas, la suppression android:process=":sync"du service de synchronisation laisse le débogueur atteindre les points de bec. Le service de synchronisation lui-même fonctionnait avant cela, car je pouvais voir les messages du journal de la onPerformSyncméthode au nom d'un autre processus.
Sergey
Le WiFi est également désactivé, donc si vous essayez de vous synchroniser avec des données simulées, vérifiez votre connexion.
Allan Veloso

Réponses:

280

L'appel requestSync()ne fonctionnera que sur une paire {Account, ContentAuthority} connue du système. Votre application doit suivre un certain nombre d'étapes pour indiquer à Android que vous êtes capable de synchroniser un type de contenu spécifique à l'aide d'un type de compte spécifique. Il le fait dans le fichier AndroidManifest.

1. Informez Android que votre package d'application permet la synchronisation

Tout d'abord, dans AndroidManifest.xml, vous devez déclarer que vous disposez d'un service de synchronisation:

<service android:name=".sync.mySyncService" android:exported="true">
   <intent-filter>
      <action android:name="android.content.SyncAdapter" /> 
    </intent-filter>
    <meta-data 
        android:name="android.content.SyncAdapter" 
        android:resource="@xml/sync_myapp" /> 
</service>

L'attribut name de la <service>balise est le nom de votre classe pour connecter la synchronisation ... J'en parlerai dans une seconde.

Le paramètre exporté sur true le rend visible par les autres composants (nécessaire, vous ContentResolverpouvez donc l' appeler).

Le filtre d'intention lui permet d'attraper un intent demandant une synchronisation. (Cela Intentvient du ContentResolvermoment où vous appelez ContentResolver.requestSync()ou des méthodes de planification associées.)

L' <meta-data>étiquette sera discutée ci-dessous.

2. Fournissez à Android un service utilisé pour trouver votre SyncAdapter

Donc la classe elle-même ... Voici un exemple:

public class mySyncService extends Service {

    private static mySyncAdapter mSyncAdapter = null;

    public SyncService() {
        super();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (mSyncAdapter == null) {
            mSyncAdapter = new mySyncAdapter(getApplicationContext(), true);
        }
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return mSyncAdapter.getSyncAdapterBinder();
    }
}

Votre classe doit étendre Serviceou l'une de ses sous-classes, doit implémenter public IBinder onBind(Intent)et doit renvoyer a SyncAdapterBinderquand elle est appelée ... Vous avez besoin d'une variable de type AbstractThreadedSyncAdapter. Donc, comme vous pouvez le voir, c'est à peu près tout dans cette classe. La seule raison pour laquelle il existe est de fournir un service, qui offre une interface standard pour Android pour interroger votre classe sur ce que vous SyncAdapterêtes.

3. Fournissez un class SyncAdapterpour effectuer réellement la synchronisation.

mySyncAdapter est l'endroit où la vraie logique de synchronisation elle-même est stockée. Sa onPerformSync()méthode est appelée au moment de la synchronisation. Je suppose que vous l'avez déjà en place.

4. Établir une liaison entre un type de compte et une autorité de contenu

En regardant à nouveau AndroidManifest, cette <meta-data>balise étrange dans notre service est l'élément clé qui établit la liaison entre un ContentAuthority et un compte. Il fait référence en externe à un autre fichier xml (appelez-le comme vous le souhaitez, quelque chose de pertinent pour votre application.) Regardons sync_myapp.xml:

<?xml version="1.0" encoding="utf-8" ?> 
<sync-adapter 
    xmlns:android="http://schemas.android.com/apk/res/android"   
    android:contentAuthority="com.android.contacts"
    android:accountType="com.google" 
    android:userVisible="true" /> 

Ok, alors qu'est-ce que ça fait? Il indique à Android que l'adaptateur de synchronisation que nous avons défini (la classe qui a été appelée dans l'élément de nom de la <service>balise qui comprend la <meta-data>balise qui fait référence à ce fichier ...) synchronisera les contacts à l'aide d'un compte de style com.google.

Toutes vos chaînes contentAuthority doivent toutes correspondre et correspondre à ce que vous synchronisez - Il doit s'agir d'une chaîne que vous définissez, si vous créez votre propre base de données, ou vous devez utiliser des chaînes d'appareils existantes si vous synchronisez types de données (comme les contacts ou les événements de calendrier ou tout le reste.) Ce qui précède ("com.android.contacts") se trouve être la chaîne ContentAuthority pour les données de type de contacts (surprise, surprise.)

accountType doit également correspondre à l'un de ces types de comptes connus qui sont déjà saisis, ou il doit correspondre à celui que vous créez (cela implique la création d'une sous-classe de AccountAuthenticator pour obtenir l'authentification sur votre serveur ... Vaut un article, lui-même.) Encore une fois, "com.google" est la chaîne définie identifiant ... les informations d'identification du compte de style google.com (encore une fois, cela ne devrait pas être une surprise.)

5. Activer la synchronisation sur une paire Compte / ContentAuthority donnée

Enfin, la synchronisation doit être activée. Vous pouvez le faire dans la page Comptes et synchronisation du panneau de configuration en accédant à votre application et en cochant la case à côté de votre application dans le compte correspondant. Vous pouvez également le faire dans un code de configuration dans votre application:

ContentResolver.setSyncAutomatically(account, AUTHORITY, true);

Pour que la synchronisation se produise, votre paire compte / autorité doit être activée pour la synchronisation (comme ci-dessus) et l'indicateur global de synchronisation globale sur le système doit être défini, et l'appareil doit disposer d'une connectivité réseau.

Si la synchronisation de votre compte / autorité ou la synchronisation globale sont désactivées, l'appel de RequestSync () a un effet - Il définit un indicateur indiquant que la synchronisation a été demandée et sera effectuée dès que la synchronisation sera activée.

De plus, par mgv , définir ContentResolver.SYNC_EXTRAS_MANUALsur true dans le bundle extras de votre requestSync demandera à android de forcer une synchronisation même si la synchronisation globale est désactivée (soyez respectueux de votre utilisateur ici!)

Enfin, vous pouvez configurer une synchronisation planifiée périodique, toujours avec les fonctions ContentResolver.

6. Examiner les implications de plusieurs comptes

Il est possible d'avoir plus d'un compte du même type (deux comptes @ gmail.com configurés sur un appareil ou deux comptes facebook, ou deux comptes Twitter, etc.). Vous devriez considérer les implications applicatives de cela. .. Si vous avez deux comptes, vous ne voudrez probablement pas essayer de les synchroniser tous les deux dans les mêmes tables de base de données. Vous devez peut-être spécifier qu'un seul peut être actif à la fois, vider les tables et resynchroniser si vous changez de compte. (via une page de propriétés qui demande quels comptes sont présents). Peut-être que vous créez une base de données différente pour chaque compte, peut-être des tables différentes, peut-être une colonne clé dans chaque table. Toutes les applications sont spécifiques et dignes de réflexion. ContentResolver.setIsSyncable(Account account, String authority, int syncable)pourrait être intéressant ici. setSyncAutomatically()contrôle si une paire compte / autorité est cochée ounon cochée , alors que setIsSyncable()fournit un moyen de décocher et de griser la ligne afin que l'utilisateur ne puisse pas l'activer. Vous pouvez définir un compte Syncable et l'autre non Syncable (dsabled).

7. Soyez conscient de ContentResolver.notifyChange ()

Une chose délicate. ContentResolver.notifyChange()est une fonction utilisée par ContentProviders pour informer Android que la base de données locale a été modifiée. Cela remplit deux fonctions, premièrement, cela entraînera la mise à jour des curseurs suivant cet uri de contenu, et à son tour la requery et l'invalidation et le redessiner d'un ListView, etc ... C'est très magique, la base de données change et votre ListViewjuste mise à jour automatiquement. Impressionnant. De plus, lorsque la base de données change, Android demandera Sync pour vous, même en dehors de votre horaire normal, afin que ces modifications soient supprimées de l'appareil et synchronisées avec le serveur le plus rapidement possible. Aussi génial.

Il y a cependant un cas de pointe. Si vous tirez du serveur et poussez une mise à jour dans le ContentProvider, il appellera consciencieusement notifyChange()et Android dira, "Oh, les changements de base de données, mieux vaut les mettre sur le serveur!" (Doh!) Bien écrit ContentProvidersaura des tests pour voir si les changements viennent du réseau ou de l'utilisateur, et définira le syncToNetworkdrapeau booléen sur false si c'est le cas, pour éviter cette double synchronisation inutile. Si vous introduisez des données dans un ContentProvider, il vous incombe de déterminer comment faire fonctionner cela - Sinon, vous finirez par toujours effectuer deux synchronisations quand une seule est nécessaire.

8. Sentez-vous heureux!

Une fois que toutes ces métadonnées xml sont en place et que la synchronisation est activée, Android saura comment tout connecter pour vous et la synchronisation devrait commencer à fonctionner. À ce stade, beaucoup de choses agréables se mettront en place et cela ressemblera beaucoup à de la magie. Prendre plaisir!

jcwenger
la source
11
ContentResolver.setSyncAutomatically (compte, AUTHORITY, true);
jcwenger
1
Pas de problème, content de pouvoir vous aider. Encore une fois du point de vue du style, si "AUTHORITY" et "myaccounttype" sont les chaînes que vous utilisez (et pas seulement des exemples pour copier-coller sur le site), vous devez absolument nettoyer vos conventions de dénomination. Ces chaînes doivent être uniques sur tout le périphérique, et vous rencontrerez de réels problèmes si un autre programmeur crée paresseusement un paquet avec une chaîne correspondante pour l'autorité et que vous obtenez un conflit. À votre santé!
jcwenger
22
Vous pouvez demander une synchronisation même si les paramètres de synchronisation globale sont désactivés. Ajoutez simplement un ContentResolver.SYNC_EXTRAS_MANUALjeu à true au
bundle
2
@kaciula: Je n'en connais aucun, mais l'appareil se souviendra qu'il doit se synchroniser, et il en lancera une dès que la synchronisation globale sera activée. Vous ne devriez vraiment pas essayer de l'emporter sur l'utilisateur sur celui-ci - D'autant plus que "Global sync off" est l'un des moyens clés pour économiser la batterie dans des situations critiques. Si vous craignez vraiment que les données ne soient pas synchronisées, envisagez une fenêtre contextuelle indiquant à l'utilisateur POURQUOI les données ne bougent pas, si elles sont restées en place depuis un certain temps. De cette façon, vous pouvez éduquer les utilisateurs qui ont accidentellement mal configuré leurs appareils et rappeler aux utilisateurs expérimentés au cas où ils l'oublieraient.
jcwenger
2
Cela vaut peut-être la peine d'ajouter que si vous voulez utiliser addPeriodicSync (), cela semble fonctionner UNIQUEMENT si vous définissez AUSSISyncAutomatically () - j'ai ajouté cela par désespoir, en essayant de faire fonctionner QUELQUE CHOSE. Je sais que cela ne faisait pas partie de la question initiale, mais c'est une réponse tellement complète!
android.weasel
0

Je calmerais setIsSyncable après la setAuthTokenméthode . Mais la setAuthTokenfonction retournée avant a setIsSyncableété atteinte. Une fois la commande modifiée, tout fonctionnait bien!

Andris
la source