Warum löst ContentResolver.requestSync keine Synchronisierung aus?

112

Ich versuche, das Content-Provider-Sync-Adapter-Muster zu implementieren, wie in Google IO - Folie 26 beschrieben. Mein Content-Provider funktioniert und meine Synchronisierung funktioniert, wenn ich es über die Dev Tools Sync Tester-Anwendung auslöse, jedoch wenn ich ContentResolver aufrufe. requestSync (Konto, Berechtigung, Bundle) von meinem ContentProvider, meine Synchronisierung wird niemals ausgelöst.

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

Bearbeiten - Manifest-Snippet hinzugefügt Meine Manifest-XML enthält:

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

--Bearbeiten

Meine mit meinem Synchronisierungsdienst verknüpfte Datei syncadapter.xml enthält:

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

Ich bin mir nicht sicher, welcher andere Code nützlich wäre. Das an requestSync übergebene Konto ist vom Typ "myaccounttype" und die an den Aufruf übergebene AUTHORITY stimmt mit meiner Syc-Adapter-XML überein.

Ist ContentResolver.requestSync der richtige Weg, um eine Synchronisierung anzufordern? Es sieht so aus, als ob das Sync-Tester-Tool direkt an den Dienst gebunden ist und Aufrufe die Synchronisierung starten, aber das scheint den Zweck der Integration in die Sync-Architektur zu vereiteln.

Wenn dies der richtige Weg ist, um eine Synchronisierung anzufordern, warum sollte dann der Synchronisierungstester funktionieren, aber nicht mein Aufruf von ContentResolver.requestSync? Muss ich etwas im Bundle weitergeben?

Ich teste im Emulator auf Geräten mit 2.1 und 2.2.

Ben
quelle
3
Mein Problem war, dass mein Haltepunkt im Synchronisierungsadapter nicht erreicht wurde ... Dann wurde mir klar, dass ich versuchte, einen Dienst zu debuggen ... Ich hoffe, dies hilft anderen wie mir.
Dangalg
2
Haltepunkte im Synchronisierungsadapterdienst können nicht ausgelöst werden. Dies liegt daran, dass der Synchronisierungsadapterdienst in einem separaten Prozess ausgeführt wird. Dies ist, was @danglang angedeutet hat. Siehe auch diese Frage: stackoverflow.com/questions/8559458/…
Konstantin Schubert
1
In meinem Fall ließ das Entfernen android:process=":sync"aus dem Synchronisierungsdienst den Debugger die Schnabelpunkte treffen. Der Synchronisierungsdienst selbst funktionierte zuvor, da ich Protokollnachrichten von der onPerformSyncMethode im Auftrag eines anderen Prozesses sehen konnte.
Sergey
Eine weitere Ursache ist, dass WLAN deaktiviert ist. Wenn Sie also versuchen, mit verspotteten Daten zu synchronisieren, überprüfen Sie Ihre Verbindung.
Allan Veloso

Antworten:

280

Das Aufrufen requestSync()funktioniert nur für ein dem Konto bekanntes Paar {Account, ContentAuthority}. Ihre App muss eine Reihe von Schritten durchlaufen, um Android mitzuteilen, dass Sie in der Lage sind, eine bestimmte Art von Inhalten mit einer bestimmten Art von Konto zu synchronisieren. Dies geschieht im AndroidManifest.

1. Benachrichtigen Sie Android, dass Ihr Anwendungspaket die Synchronisierung bietet

Zunächst müssen Sie in AndroidManifest.xml erklären, dass Sie über einen Synchronisierungsdienst verfügen:

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

Das Namensattribut des <service>Tags ist der Name Ihrer Klasse, um die Synchronisierung herzustellen ... Ich werde gleich darauf eingehen.

Wenn Sie exported true festlegen, wird es für andere Komponenten sichtbar (erforderlich) ContentResolver es aufgerufen werden kann).

Mit dem Absichtsfilter kann eine Absicht erfasst werden, die eine Synchronisierung anfordert. (Dies Intentkommt von, ContentResolverwenn Sie ContentResolver.requestSync()oder verwandte Planungsmethoden aufrufen .)

Das <meta-data>Tag wird unten diskutiert.

2. Stellen Sie Android einen Dienst zur Verfügung, mit dem Sie Ihren SyncAdapter finden

Also die Klasse selbst ... Hier ist ein Beispiel:

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

Ihre Klasse muss eine Serviceoder eine ihrer Unterklassen erweitern, implementieren public IBinder onBind(Intent)und a zurückgeben, SyncAdapterBinderwenn dies aufgerufen wird ... Sie benötigen eine Variable vom Typ AbstractThreadedSyncAdapter. Wie Sie sehen, ist das so ziemlich alles in dieser Klasse. Der einzige Grund dafür ist die Bereitstellung eines Dienstes, der eine Standardschnittstelle für Android bietet, über die Sie Ihre Klasse nach Ihrer eigenen SyncAdapterfragen können.

3. Geben Sie a class SyncAdapteran, um die Synchronisierung tatsächlich durchzuführen.

In mySyncAdapter wird die eigentliche Synchronisierungslogik selbst gespeichert. Es istonPerformSync() Methode wird aufgerufen, wenn die Synchronisierung abgeschlossen ist. Ich denke, Sie haben dies bereits eingerichtet.

4. Stellen Sie eine Bindung zwischen einem Kontotyp und einer Inhaltsbehörde her

Wenn wir noch einmal auf AndroidManifest zurückblicken, ist dieses seltsame <meta-data>Tag in unserem Service das Schlüsselelement, das die Bindung zwischen einer ContentAuthority und einem Konto herstellt. Es verweist extern auf eine andere XML-Datei (nennen Sie es wie Sie möchten, etwas, das für Ihre App relevant ist.) Schauen wir uns sync_myapp.xml an:

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

Okay, was macht das? Es teilt Android mit, dass der von uns definierte Synchronisierungsadapter (die Klasse, die im name-Element des <service>Tags aufgerufen wurde, das das Tag enthält, das <meta-data>auf diese Datei verweist ...) Kontakte über ein Konto im com.google-Stil synchronisiert.

Alle Ihre contentAuthority-Zeichenfolgen müssen mit dem übereinstimmen, was Sie synchronisieren. Dies sollte eine Zeichenfolge sein, die Sie definieren, wenn Sie eine eigene Datenbank erstellen, oder Sie sollten einige vorhandene Gerätezeichenfolgen verwenden, wenn Sie die Synchronisierung durchführen Datentypen (wie Kontakte oder Kalenderereignisse oder was haben Sie). Das oben Gesagte ("com.android.contacts") ist zufällig die ContentAuthority-Zeichenfolge für Kontakttypdaten (Überraschung, Überraschung.)

accountType muss auch mit einem der bekannten Kontotypen übereinstimmen, die bereits eingegeben wurden, oder mit einem, den Sie erstellen (Dies beinhaltet das Erstellen einer Unterklasse von AccountAuthenticator, um die Authentifizierung auf Ihrem Server zu erhalten ... Ein Artikel ist es wert.) Wiederum ist "com.google" die definierte Zeichenfolge, die die Anmeldeinformationen eines Kontos im Google.com-Stil identifiziert (auch dies sollte keine Überraschung sein.)

5. Aktivieren Sie die Synchronisierung für ein bestimmtes Konto / ContentAuthority-Paar

Schließlich muss die Synchronisierung aktiviert werden. Sie können dies auf der Seite "Konten und Synchronisierung" in der Systemsteuerung tun, indem Sie zu Ihrer App gehen und das Kontrollkästchen neben Ihrer App im entsprechenden Konto aktivieren. Alternativ können Sie dies in einem Setup-Code in Ihrer App tun:

ContentResolver.setSyncAutomatically(account, AUTHORITY, true);

Damit eine Synchronisierung stattfinden kann, muss Ihr Konto- / Berechtigungspaar für die Synchronisierung aktiviert sein (wie oben), und das allgemeine globale Synchronisierungsflag auf dem System muss gesetzt sein, und das Gerät muss über eine Netzwerkverbindung verfügen.

Wenn die Synchronisierung Ihres Kontos / Ihrer Behörde oder die globale Synchronisierung deaktiviert ist, hat der Aufruf von RequestSync () Auswirkungen. Er setzt ein Flag, dass die Synchronisierung angefordert wurde, und wird ausgeführt, sobald die Synchronisierung aktiviert ist.

Auch pro mgv EinstellungContentResolver.SYNC_EXTRAS_MANUAL im Extras-Bundle Ihrer auf true wird Android aufgefordert, eine Synchronisierung zu erzwingen, auch wenn die globale Synchronisierung ist (respektieren Sie Ihren Benutzer hier!).

Schließlich können Sie eine regelmäßige geplante Synchronisierung erneut mit ContentResolver-Funktionen einrichten.

6. Berücksichtigen Sie die Auswirkungen mehrerer Konten

Es ist möglich, mehr als ein Konto desselben Typs zu haben (zwei @ gmail.com-Konten auf einem Gerät oder zwei Facebook-Konten oder zwei Twitter-Konten usw.). Sie sollten die Auswirkungen der Anwendung berücksichtigen. .. Wenn Sie zwei Konten haben, möchten Sie wahrscheinlich nicht versuchen, beide mit denselben Datenbanktabellen zu synchronisieren. Möglicherweise müssen Sie angeben, dass jeweils nur eine aktiv sein kann, und die Tabellen leeren und erneut synchronisieren, wenn Sie das Konto wechseln. (über eine Eigenschaftsseite, die abfragt, welche Konten vorhanden sind). Vielleicht erstellen Sie für jedes Konto eine andere Datenbank, vielleicht verschiedene Tabellen, vielleicht eine Schlüsselspalte in jeder Tabelle. Alle anwendungsspezifisch und bedenkenswert. ContentResolver.setIsSyncable(Account account, String authority, int syncable)könnte hier von Interesse sein. setSyncAutomatically()steuert, ob ein Konto / Berechtigungspaar geprüft wird oderdeaktiviert , während es setIsSyncable()eine Möglichkeit bietet, die Linie zu deaktivieren und auszublenden, damit der Benutzer sie nicht aktivieren kann. Sie können festlegen, dass ein Konto synchronisierbar und das andere nicht synchronisierbar ist (dsabled).

7. Beachten Sie ContentResolver.notifyChange ()

Eine knifflige Sache. ContentResolver.notifyChange()ist eine Funktion, mit der ContentProviders Android benachrichtigt, dass die lokale Datenbank geändert wurde. Dies hat zwei Funktionen: Erstens führt dies dazu, dass Cursor, die diesem Inhalts-Uri folgen, aktualisiert werden und im Gegenzug ein ListViewusw. anfordern und ungültig machen und neu zeichnen. Es ist sehr magisch, die Datenbank ändert sich und IhreListView nur automatisch aktualisiert. Genial. Wenn sich die Datenbank ändert, fordert Android auch außerhalb Ihres normalen Zeitplans die Synchronisierung für Sie an, damit diese Änderungen vom Gerät entfernt und so schnell wie möglich mit dem Server synchronisiert werden. Auch super.

Es gibt jedoch einen Randfall. Wenn Sie vom Server ziehen und ein Update in den Server schieben ContentProvider, wird es pflichtbewusst aufgerufen notifyChange()und Android sagt: "Oh, Datenbankänderungen, stellen Sie sie besser auf den Server!" (Doh!) Gut geschrieben ContentProvidershat einige Tests, um festzustellen, ob die Änderungen vom Netzwerk oder vom Benutzer stammen, und setzt syncToNetworkin diesem Fall das boolesche Flag auf false, um diese verschwenderische Doppelsynchronisierung zu verhindern. Wenn Sie Daten in a ContentProvidereinspeisen, müssen Sie herausfinden, wie dies funktioniert. Andernfalls führen Sie immer zwei Synchronisierungen durch, wenn nur eine benötigt wird.

8. Fühle dich glücklich!

Sobald Sie alle diese XML-Metadaten eingerichtet und die Synchronisierung aktiviert haben, weiß Android, wie Sie alles für Sie verbinden, und die Synchronisierung sollte beginnen. An diesem Punkt rasten viele Dinge, die schön sind, einfach ein und es wird sich sehr nach Magie anfühlen. Genießen!

jcwenger
quelle
11
ContentResolver.setSyncAutomatically (Konto, AUTHORITY, true);
JCwenger
1
Kein Problem, ich bin froh, dass ich helfen konnte. Unter dem Gesichtspunkt des Stils müssen Sie jedoch Ihre Namenskonventionen unbedingt bereinigen, wenn "AUTHORITY" und "myaccounttype" die tatsächlichen Zeichenfolgen sind, die Sie verwenden (und nicht nur Beispiele für das Kopieren an die Site). Diese Zeichenfolgen müssen auf dem gesamten Gerät eindeutig sein, und Sie werden in echte Schwierigkeiten geraten, wenn ein anderer Programmierer träge ein Paket mit einer passenden Zeichenfolge für die Autorität erstellt und Sie einen Konflikt erhalten. Prost!
JCwenger
22
Sie können eine Synchronisierung anfordern, obwohl die globalen Synchronisierungseinstellungen deaktiviert sind. ContentResolver.SYNC_EXTRAS_MANUAL
Fügen
2
@kaciula: Ich kenne keine, aber das Gerät wird sich daran erinnern, dass es synchronisiert werden muss, und es startet eine, sobald die globale Synchronisierung aktiviert ist. Sie sollten wirklich nicht versuchen, den Benutzer in diesem Fall zu übertreffen - zumal "Globale Synchronisierung aus" eine der wichtigsten Möglichkeiten ist, um in kritischen Situationen Batterie zu sparen. Wenn Sie wirklich besorgt sind, dass die Daten nicht synchronisiert werden, ziehen Sie ein Popup in Betracht, das dem Benutzer mitteilt, WARUM sich die Daten nicht bewegen, wenn sie eine Weile gesessen haben. Auf diese Weise können Sie Benutzer schulen, die ihre Geräte versehentlich falsch konfiguriert haben, und Power-User daran erinnern, falls sie es vergessen haben.
JCwenger
2
Es könnte sich lohnen, hinzuzufügen, dass, wenn Sie addPeriodicSync () verwenden möchten, es NUR zu funktionieren scheint, wenn Sie AUCHSyncAutomatically () setzen - ich habe dies aus Verzweiflung hinzugefügt und versucht, ETWAS zum Laufen zu bringen. Ich weiß, dass es nicht Teil der ursprünglichen Frage war, aber dies ist eine so vollständige Antwort!
android.weasel
0

Ich habe setIsSyncablenach der AccountManager- setAuthTokenMethode kalibriert . Aber setAuthTokenzurückgegebene Funktion, bevor setIsSyncableerreicht wurde. Nach Bestelländerungen hat alles gut funktioniert!

Andris
quelle