Perché ContentResolver.requestSync non attiva una sincronizzazione?


112

Sto tentando di implementare il pattern Content-Provider-Sync Adapter come discusso in Google IO - diapositiva 26. Il mio provider di contenuti funziona e la mia sincronizzazione funziona quando lo avvio dall'applicazione Dev Tools Sync Tester, tuttavia quando chiamo ContentResolver. requestSync (account, authority, bundle) dal mio ContentProvider, la mia sincronizzazione non viene mai attivata.

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

Modifica: aggiunto snippet di manifest Il mio file manifest xml contiene:

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

--Modificare

Il mio syncadapter.xml associato al mio servizio di sincronizzazione contiene:

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

Non sono sicuro di quale altro codice sarebbe utile. L'account passato a requestSync è di "myaccounttype" e l'AUTORITÀ passata alla chiamata corrisponde al mio xml dell'adattatore syc.

ContentResolver.requestSync è il modo corretto per richiedere una sincronizzazione? Sembra che lo strumento di verifica della sincronizzazione si colleghi direttamente al servizio e chiami start sync, ma sembra che vanifichi lo scopo dell'integrazione con l'architettura di sincronizzazione.

Se questo è il modo corretto per richiedere una sincronizzazione, perché il tester di sincronizzazione dovrebbe funzionare, ma non la mia chiamata a ContentResolver.requestSync? C'è qualcosa che devo passare nel pacchetto?

Sto testando nell'emulatore su dispositivi che eseguono 2.1 e 2.2.


3
Il mio problema era che il mio punto di interruzione nell'adattatore di sincronizzazione non veniva raggiunto ... Poi mi sono reso conto che stavo cercando di eseguire il debug di un servizio ... Spero che questo aiuti altri come me.
dangalg

2
I punti di interruzione nel servizio adattatore di sincronizzazione NON verranno attivati. Questo perché il servizio dell'adattatore di sincronizzazione viene eseguito in un processo separato. Questo è ciò a cui stava suggerendo @danglang. Vedi anche questa domanda: stackoverflow.com/questions/8559458/…
Konstantin Schubert

1
Nel mio caso la rimozione android:process=":sync"dal servizio di sincronizzazione ha lasciato che il debugger colpisse i punti critici. Il servizio di sincronizzazione stesso funzionava prima, perché potevo vedere i messaggi di registro dal onPerformSyncmetodo per conto di un altro processo.
Sergey

Un'altra causa è che il WiFi è disattivato, quindi se stai tentando di sincronizzarti con dati fittizi, controlla la tua connessione.
Allan Veloso

Risposte:


280

La chiamata requestSync()funzionerà solo su una coppia {Account, ContentAuthority} nota al sistema. La tua app deve eseguire una serie di passaggi per dire ad Android che sei in grado di sincronizzare un tipo specifico di contenuto utilizzando un tipo specifico di account. Lo fa in AndroidManifest.

1. Avvisare Android che il pacchetto dell'applicazione fornisce la sincronizzazione

Prima di tutto, in AndroidManifest.xml, devi dichiarare di avere un servizio di sincronizzazione:

<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'attributo name del <service>tag è il nome della tua classe per connettere la sincronizzazione ... parlerò di questo tra un secondo.

L'impostazione di exported true lo rende visibile ad altri componenti (necessario quindi è ContentResolverpossibile chiamarlo).

Il filtro intento consente di rilevare un intento che richiede la sincronizzazione. (Questo Intentderiva dalla ContentResolverchiamata ContentResolver.requestSync()o dai metodi di pianificazione correlati.)

Il <meta-data>tag verrà discusso di seguito.

2. Fornire ad Android un servizio utilizzato per trovare il SyncAdapter

Quindi la classe stessa ... Ecco un esempio:

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

La tua classe deve estendere Serviceo una delle sue sottoclassi, deve implementare public IBinder onBind(Intent)e deve restituire un SyncAdapterBinderquando viene chiamata ... Hai bisogno di una variabile di tipo AbstractThreadedSyncAdapter. Quindi, come puoi vedere, è praticamente tutto in quella classe. L'unico motivo per cui è lì è fornire un servizio, che offre un'interfaccia standard per Android per interrogare la tua classe su cosa sia la tua SyncAdapterstessa.

3. Fornire un class SyncAdapterper eseguire effettivamente la sincronizzazione.

mySyncAdapter è il luogo in cui viene archiviata la logica di sincronizzazione reale. Il suo onPerformSync()metodo viene chiamato quando è il momento di sincronizzare. Immagino che tu abbia già questo a posto.

4. Stabilire un legame tra un tipo di account e un'autorità per i contenuti

Ripensando ad AndroidManifest, quello strano <meta-data>tag nel nostro servizio è l'elemento chiave che stabilisce il legame tra un ContentAuthority e un account. Fa riferimento esternamente a un altro file xml (chiamalo come preferisci, qualcosa di rilevante per la tua app.) Diamo un'occhiata a 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, quindi cosa fa questo? Indica ad Android che l'adattatore di sincronizzazione che abbiamo definito (la classe che è stata chiamata nell'elemento name del <service>tag che include il <meta-data>tag che fa riferimento a questo file ...) sincronizzerà i contatti utilizzando un account in stile com.google.

Tutti i tuoi contenuti Le stringhe dell'autorità devono corrispondere e corrispondere a ciò che stai sincronizzando: questa dovrebbe essere una stringa che definisci, se stai creando il tuo database, o dovresti usare alcune stringhe del dispositivo esistenti se stai sincronizzando noto tipi di dati (come contatti, eventi di calendario o altro). Il precedente ("com.android.contacts") sembra essere la stringa ContentAuthority per i dati del tipo di contatti (sorpresa, sorpresa.)

accountType deve anche corrispondere a uno di quei tipi di account conosciuti che sono già inseriti, o deve corrispondere a quello che stai creando (ciò comporta la creazione di una sottoclasse di AccountAuthenticator per ottenere l'autenticazione sul tuo server ... Vale un articolo, a sua volta.) Ancora una volta, "com.google" è la stringa definita che identifica ... le credenziali dell'account in stile google.com (di nuovo, questa non dovrebbe essere una sorpresa).

5. Abilita la sincronizzazione su una determinata coppia Account / ContentAuthority

Infine, la sincronizzazione deve essere abilitata. Puoi farlo nella pagina Account e sincronizzazione nel pannello di controllo accedendo alla tua app e impostando la casella di controllo accanto alla tua app all'interno dell'account corrispondente. In alternativa, puoi farlo in un codice di configurazione nella tua app:

ContentResolver.setSyncAutomatically(account, AUTHORITY, true);

Affinché la sincronizzazione avvenga, la tua coppia account / autorità deve essere abilitata per la sincronizzazione (come sopra) e deve essere impostato il flag di sincronizzazione globale globale sul sistema e il dispositivo deve avere la connettività di rete.

Se la sincronizzazione dell'account / autorità o la sincronizzazione globale sono disabilitate, la chiamata a RequestSync () ha un effetto: imposta un contrassegno che la sincronizzazione è stata richiesta e verrà eseguita non appena la sincronizzazione sarà abilitata.

Inoltre, per mgv , l'impostazione ContentResolver.SYNC_EXTRAS_MANUALsu true nel pacchetto extra di requestSync chiederà ad Android di forzare una sincronizzazione anche se la sincronizzazione globale è disattivata (sii rispettoso del tuo utente qui!)

Infine, puoi impostare una sincronizzazione pianificata periodica, sempre con le funzioni ContentResolver.

6. Considera le implicazioni di più account

È possibile avere più di un account dello stesso tipo (due account @ gmail.com configurati su un dispositivo o due account Facebook, o due account Twitter, ecc ...) Dovresti considerare le implicazioni dell'applicazione di farlo. .. Se hai due account, probabilmente non vorrai provare a sincronizzarli entrambi nelle stesse tabelle di database. Forse è necessario specificare che solo uno può essere attivo alla volta e svuotare le tabelle e risincronizzarle se si cambia account. (tramite una pagina delle proprietà che interroga gli account presenti). Forse crei un database diverso per ogni account, forse tabelle diverse, forse una colonna chiave in ogni tabella. Tutte specifiche per applicazioni e meritevoli di riflessione. ContentResolver.setIsSyncable(Account account, String authority, int syncable)potrebbe essere di interesse qui. setSyncAutomatically()controlla se una coppia account / autorità è controllata odeselezionato , mentre setIsSyncable()fornisce un modo per deselezionare e disattivare la linea in modo che l'utente non possa accenderlo. È possibile impostare un account sincronizzabile e l'altro non sincronizzabile (disattivato).

7. Sii consapevole di ContentResolver.notifyChange ()

Una cosa complicata. ContentResolver.notifyChange()è una funzione utilizzata da ContentProviders per notificare ad Android che il database locale è stato modificato. Questo ha due funzioni, in primo luogo, farà sì che i cursori che seguono quell'URI di contenuto si aggiornino e, a loro volta, richiedano e invalidano e ridisegnano un ListView, ecc ... È molto magico, il database cambia e il tuo si ListViewaggiorna automaticamente. Eccezionale. Inoltre, quando il database cambia, Android richiederà la sincronizzazione per te, anche al di fuori della tua normale pianificazione, in modo che tali modifiche vengano rimosse dal dispositivo e sincronizzate con il server il più rapidamente possibile. Anche fantastico.

C'è però un caso limite. Se esegui il pull dal server e ContentProviderinserisci un aggiornamento in , verrà chiamato doverosamente notifyChange()e Android dirà, "Oh, modifiche al database, meglio metterle sul server!" (Doh!) Ben scritto ContentProvidersavrà alcuni test per vedere se le modifiche provengono dalla rete o dall'utente, e imposterà il syncToNetworkflag booleano false in tal caso, per evitare questa doppia sincronizzazione dispendiosa. Se stai inserendo i dati in un ContentProvider, ti conviene capire come farlo funzionare - Altrimenti finirai per eseguire sempre due sincronizzazioni quando ne serve solo una.

8. Sentiti felice!

Una volta che hai tutti questi metadati xml a posto e la sincronizzazione abilitata, Android saprà come connettere tutto per te e la sincronizzazione dovrebbe iniziare a funzionare. A questo punto, molte cose belle andranno a posto e sembreranno magiche. Godere!


11
ContentResolver.setSyncAutomatically (account, AUTHORITY, true);
jcwenger

1
Nessun problema, sono contento di aver potuto aiutare. Anche in questo caso dal punto di vista dello stile, però, se "AUTHORITY" e "myaccounttype" sono le stringhe effettive che stai utilizzando (e non solo esempi per il copy-past sul sito), devi assolutamente ripulire le tue convenzioni di denominazione. Quelle stringhe devono essere univoche su tutto il dispositivo e ti imbatterai in guai seri se qualche altro programmatore crea pigramente un pacchetto con una stringa corrispondente per autorità e si ottiene un conflitto. Saluti!
jcwenger

22
Puoi richiedere una sincronizzazione anche se le impostazioni di sincronizzazione globale sono disattivate. Basta aggiungere un ContentResolver.SYNC_EXTRAS_MANUALset a true per il pacchetto extra e
forzerai

2
@kaciula: non ne conosco nessuno, ma il dispositivo si ricorderà che deve essere sincronizzato e ne inizierà uno non appena la sincronizzazione globale sarà attivata. Non dovresti davvero provare a battere l'utente su questo - Soprattutto perché "Sincronizzazione globale disattivata" è uno dei modi chiave per risparmiare batteria in situazioni critiche. Se sei davvero preoccupato che i dati non vengano sincronizzati, prendi in considerazione un popup che dice all'utente PERCHÉ i dati non si muovono, se è rimasto fermo per un po '. In questo modo puoi istruire gli utenti che hanno accidentalmente configurato male i loro dispositivi e ricordarlo agli utenti esperti nel caso se ne fossero dimenticati.
jcwenger

2
Potrebbe valere la pena aggiungere che se vuoi usare addPeriodicSync (), sembra funzionare SOLO se imposti ANCHE SETSyncAutomatically () - l'ho aggiunto per disperazione, cercando di far funzionare QUALCOSA. So che non faceva parte della domanda originale, ma questa è una risposta così completa!
android.weasel

0

Stavo chiamando setIsSyncabledopo il setAuthTokenmetodo AccountManager . Ma la setAuthTokenfunzione restituita prima è setIsSyncablestata raggiunta. Dopo la modifica dell'ordine, tutto ha funzionato bene!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.