Nota: consigliamo di utilizzare WorkManager come soluzione consigliata per la maggior parte dei casi d'uso di elaborazione in background. Fai riferimento alla guida all'elaborazione in background per scoprire la soluzione più adatta alle tue esigenze.
Il componente dell'adattatore di sincronizzazione nella tua app incapsula il codice per le attività che trasferiscono i dati tra il dispositivo e un server. In base alla pianificazione e agli attivatori specificati nell'app, il framework dell'adattatore di sincronizzazione esegue il codice nel componente dell'adattatore di sincronizzazione. Per aggiungere un componente dell'adattatore di sincronizzazione alla tua app, devi aggiungere quanto segue:
- Classe adattatore di sincronizzazione.
- Una classe che inserisce il codice Data Transfer in un'interfaccia compatibile con il framework dell'adattatore di sincronizzazione.
-
Associato
Service
. - Un componente che consente al framework dell'adattatore di sincronizzazione di eseguire il codice nella classe dell'adattatore di sincronizzazione.
- File XML dei metadati dell'adattatore di sincronizzazione.
- Un file contenente informazioni sull'adattatore di sincronizzazione. Il framework legge questo file per scoprire come caricare e pianificare il trasferimento dei dati.
- Dichiarazioni nel file manifest dell'app.
- XML che dichiara il servizio associato e punta a sincronizzare i metadati specifici dell'adattatore.
Questa lezione spiega come definire questi elementi.
Crea una classe dell'adattatore di sincronizzazione
In questa parte della lezione imparerai a creare la classe dell'adattatore di sincronizzazione che incapsula il codice Data Transfer. La creazione della classe include l'estensione della classe di base dell'adattatore di sincronizzazione, la definizione dei costruttori per la classe e l'implementazione del metodo in cui definisci le attività di trasferimento dei dati.
Estendi la classe dell'adattatore di sincronizzazione di base
Per creare il componente dell'adattatore di sincronizzazione, inizia estendendo AbstractThreadedSyncAdapter
e scrivendo i relativi costruttori. Utilizza i costruttori per eseguire attività di configurazione ogni volta che viene creato il componente dell'adattatore di sincronizzazione da zero, proprio come usi Activity.onCreate()
per configurare un'attività. Ad esempio, se la tua app utilizza un fornitore di contenuti per archiviare i dati, utilizza i costruttori per ottenere un'istanza ContentResolver
. Poiché nella piattaforma Android versione 3.0 è stata aggiunta una seconda forma del costruttore per supportare l'argomento parallelSyncs
, devi crearne due per mantenere la compatibilità.
Nota: il framework dell'adattatore di sincronizzazione è progettato per funzionare con i componenti degli adattatori di sincronizzazione che sono istanze singleton. La creazione di un'istanza del componente dell'adattatore di sincronizzazione è trattata in maggiore dettaglio nella sezione Associazione dell'adattatore di sincronizzazione al framework.
L'esempio seguente mostra come implementare AbstractThreadedSyncAdapter
e i relativi costruttori:
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(); ... }
Aggiungi il codice Data Transfer
Il componente dell'adattatore di sincronizzazione non esegue automaticamente il trasferimento di dati. Incapsula invece il codice Data Transfer in modo che il framework dell'adattatore di sincronizzazione possa eseguire il trasferimento di dati in background, senza il coinvolgimento dell'app. Quando il framework è pronto per sincronizzare i dati dell'applicazione, richiama la tua implementazione del metodo onPerformSync()
.
Per facilitare il trasferimento dei dati dal codice principale dell'app al componente dell'adattatore di sincronizzazione, il framework dell'adattatore di sincronizzazione chiama
onPerformSync()
con i
seguenti argomenti:
- Account
-
Un oggetto
Account
associato all'evento che ha attivato l'adattatore di sincronizzazione. Se il server non utilizza account, non è necessario utilizzare le informazioni in questo oggetto. - Extra
-
Un elemento
Bundle
contenente i flag inviati dall'evento che ha attivato l'adattatore di sincronizzazione. - Autorità
- L'autorità di un fornitore di contenuti nell'ambito del sistema. La tua app deve avere accesso a questo provider. In genere, l'autorità corrisponde a un fornitore di contenuti nella tua app.
- Client del fornitore di contenuti
-
Un
ContentProviderClient
per il fornitore di contenuti a cui punta l'argomento dell'autorità.ContentProviderClient
è un'interfaccia pubblica leggera per un provider di contenuti. Ha le stesse funzionalità di base diContentResolver
. Se utilizzi un fornitore di contenuti per archiviare i dati per la tua app, puoi connetterti al provider con questo oggetto. In caso contrario, puoi ignorarlo. - Risultato della sincronizzazione
-
Un oggetto
SyncResult
che utilizzi per inviare informazioni al framework dell'adattatore di sincronizzazione.
Il seguente snippet mostra la struttura generale di
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. */ }
Sebbene l'attuale implementazione di onPerformSync()
sia specifica per i requisiti di sincronizzazione dei dati e i protocolli di connessione del server dell'app, esistono alcune attività generali che l'implementazione deve eseguire:
- Connessione a un server
- Anche se si può presumere che la rete sia disponibile all'avvio del trasferimento di dati, il framework dell'adattatore di sincronizzazione non si connette automaticamente a un server.
- Download e caricamento dei dati
- Un adattatore di sincronizzazione non automatizza alcuna attività di trasferimento di dati. Se vuoi scaricare i dati da un server e archiviarli in un fornitore di contenuti, devi fornire il codice che richiede i dati, li scarica e li inserisce nel provider. Analogamente, se vuoi inviare dati a un server, devi leggerli da un file, un database o un provider e inviare la richiesta di caricamento necessaria. Devi anche gestire gli errori di rete che si verificano durante il trasferimento dei dati.
- Gestione dei conflitti di dati o determinazione del livello di attualità dei dati
- Un adattatore di sincronizzazione non gestisce automaticamente i conflitti tra i dati sul server e quelli sul dispositivo. Inoltre, non rileva automaticamente se i dati sul server sono più recenti rispetto a quelli sul dispositivo e viceversa. Devi invece fornire i tuoi algoritmi per gestire questa situazione.
- Eseguire la pulizia.
- Chiudi sempre le connessioni a un server ed pulisci le cache e i file temporanei al termine del trasferimento dei dati.
Nota:il framework dell'adattatore di sincronizzazione esegue onPerformSync()
su un thread in background, quindi non è necessario configurare un'elaborazione in background personalizzata.
Oltre alle attività relative alla sincronizzazione, dovresti provare a combinare le normali attività di rete e aggiungerle a onPerformSync()
.
Raggruppando tutte le attività di rete in questo metodo, puoi risparmiare la carica della batteria necessaria per avviare e arrestare le interfacce di rete. Per ulteriori informazioni su come rendere più efficiente l'accesso alla rete, consulta il corso di formazione Trasferimento di dati senza svuotare la batteria, che descrive diverse attività di accesso alla rete che puoi includere nel codice di trasferimento dei dati.
Associa l'adattatore di sincronizzazione al framework
Il codice Data Transfer ora è incapsulato in un componente dell'adattatore di sincronizzazione, ma devi fornire al framework l'accesso al tuo codice. A questo scopo, devi creare un elemento Service
associato che trasmette un oggetto binder Android speciale dal componente dell'adattatore di sincronizzazione al framework. Con questo oggetto binder, il framework può richiamare il metodo onPerformSync()
e trasferirvi i dati.
Crea l'istanza del componente adattatore di sincronizzazione come singleton nel
metodo onCreate()
del servizio. Creando un'istanza del componente in onCreate()
, posticipa la creazione fino all'avvio del servizio, ovvero quando il framework tenta per la prima volta di eseguire il trasferimento di dati. Devi creare un'istanza del componente in modo sicuro per i thread, nel caso in cui il framework dell'adattatore di sincronizzazione accoda più esecuzioni dell'adattatore di sincronizzazione in risposta ai trigger o alla pianificazione.
Ad esempio, lo snippet seguente mostra come creare una classe che implementi l'elemento Service
associato, crei un'istanza del componente dell'adattatore di sincronizzazione e recupera l'oggetto binder di 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(); } }
Nota: per un esempio più dettagliato di servizio associato per un adattatore di sincronizzazione, vedi l'app di esempio.
Aggiungi l'account richiesto dal framework
Il framework dell'adattatore di sincronizzazione richiede che ogni adattatore di sincronizzazione abbia un tipo di account. Hai dichiarato
il valore del tipo di account nella sezione
Aggiungere il file di metadati di Authenticator. Ora devi configurare questo tipo di account nel sistema Android. Per configurare il tipo di account, aggiungi un account segnaposto che utilizzi il tipo di account
chiamando addAccountExplicitly()
.
Il modo migliore per chiamare il metodo è utilizzare il metodo onCreate()
dell'attività di apertura dell'app. Il seguente snippet di codice mostra come fare:
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. */ } } ... }
Aggiungi il file di metadati dell'adattatore di sincronizzazione
Per collegare il componente dell'adattatore di sincronizzazione al framework, devi fornire nel framework i metadati che descrivono il componente e forniscono flag aggiuntivi. I metadati specificano il tipo di account creato per l'adattatore di sincronizzazione, dichiarano un'autorità del fornitore di contenuti associata alla tua app, controllano una parte dell'interfaccia utente del sistema relativa agli adattatori di sincronizzazione e dichiarano altri flag relativi alla sincronizzazione. Dichiara questi metadati in un file XML speciale archiviato nella directory /res/xml/
del progetto dell'app. Puoi assegnare qualsiasi nome al file, anche se di solito si chiama syncadapter.xml
.
Questo file XML contiene un singolo elemento XML <sync-adapter>
con
i seguenti attributi:
android:contentAuthority
-
L'autorità URI del tuo fornitore di contenuti. Se hai creato un fornitore di contenuti stub per
la tua app nella lezione precedente Creazione di un fornitore di contenuti stub, utilizza il valore specificato per
l'attributo
android:authorities
nell'elemento<provider>
che hai aggiunto al file manifest dell'app. Questo attributo è descritto più dettagliatamente nella sezione Dichiarare il provider nel manifest.
Se trasferisci i dati da un fornitore di contenuti a un server con l'adattatore di sincronizzazione, questo valore deve corrispondere all'autorità URI del contenuto che stai utilizzando per i dati. Questo valore è anche una delle autorità specificate nell'attributoandroid:authorities
dell'elemento<provider>
che dichiara il tuo provider nel file manifest dell'app. android:accountType
-
Il tipo di account richiesto dal framework dell'adattatore di sincronizzazione. Il valore deve essere uguale al valore del tipo di account fornito al momento della creazione del file di metadati dell'autenticatore, come descritto nella sezione Aggiungere il file di metadati di Authenticator. Corrisponde anche al valore specificato per la
costante
ACCOUNT_TYPE
nello snippet di codice riportato nella sezione Aggiungere l'account richiesto dal framework. - Attributi impostazioni
-
-
android:userVisible
- Imposta la visibilità del tipo di account dell'adattatore di sincronizzazione. Per impostazione predefinita, l'icona dell'account e l'etichetta associati al tipo di account sono visibili nella sezione Account dell'app Impostazioni del sistema, pertanto dovresti rendere invisibile l'adattatore di sincronizzazione, a meno che tu non abbia un tipo di account o un dominio facilmente associato all'app. Se rendi invisibile il tuo tipo di account, puoi comunque consentire agli utenti di controllare l'adattatore di sincronizzazione con un'interfaccia utente in una delle attività dell'app.
-
android:supportsUploading
-
Consente di caricare i dati nel cloud. Impostalo su
false
se la tua app scarica solo dati. -
android:allowParallelSyncs
- Consente di eseguire contemporaneamente più istanze del componente dell'adattatore di sincronizzazione. Utilizza questa opzione se la tua app supporta più account utente e vuoi consentire a più utenti di trasferire i dati in parallelo. Questo flag non ha effetto se non esegui mai più trasferimenti di dati.
-
android:isAlwaysSyncable
-
Indica al framework dell'adattatore di sincronizzazione che può eseguire l'adattatore di sincronizzazione in qualsiasi momento da te specificato. Se vuoi controllare in modo programmatico quando può essere eseguito l'adattatore di sincronizzazione, imposta questo flag su
false
, quindi chiamarequestSync()
per eseguire l'adattatore di sincronizzazione. Per scoprire di più sull'esecuzione di un adattatore di sincronizzazione, consulta la lezione Esecuzione di un adattatore di sincronizzazione
-
L'esempio seguente mostra il codice XML per un adattatore di sincronizzazione che utilizza un singolo account segnaposto ed esegue solo i download.
<?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"/>
Dichiara l'adattatore di sincronizzazione nel file manifest
Dopo aver aggiunto il componente dell'adattatore di sincronizzazione all'app, devi richiedere le autorizzazioni correlate all'utilizzo del componente e dichiarare l'elemento Service
associato che hai aggiunto.
Poiché il componente dell'adattatore di sincronizzazione esegue il codice che trasferisce i dati tra la rete e il dispositivo, devi richiedere l'autorizzazione per accedere a Internet. Inoltre, l'app deve richiedere l'autorizzazione per leggere e scrivere le impostazioni dell'adattatore di sincronizzazione, in modo da poter controllare l'adattatore di sincronizzazione in modo programmatico da altri componenti dell'app. Devi anche richiedere un'autorizzazione speciale che consenta all'app di utilizzare il componente di autenticazione creato nella lezione Creazione di un comando Stub Authenticator.
Per richiedere queste autorizzazioni, aggiungi quanto segue al file manifest dell'app come elementi secondari di <manifest>
:
-
android.permission.INTERNET
- Consente al codice dell'adattatore di sincronizzazione di accedere a internet in modo da poter scaricare o caricare dati dal dispositivo su un server. Non è necessario aggiungere di nuovo questa autorizzazione se la avevi richiesta in precedenza.
-
android.permission.READ_SYNC_SETTINGS
-
Consente all'app di leggere le impostazioni correnti dell'adattatore di sincronizzazione. Ad esempio, devi avere questa autorizzazione per chiamare
getIsSyncable()
. -
android.permission.WRITE_SYNC_SETTINGS
-
Consente all'app di controllare le impostazioni dell'adattatore di sincronizzazione. Devi disporre di questa autorizzazione per impostare esecuzioni periodiche di adattatori di sincronizzazione utilizzando
addPeriodicSync()
. Questa autorizzazione non è necessaria per chiamarerequestSync()
. Per ulteriori informazioni sull'esecuzione dell'adattatore di sincronizzazione, consulta la sezione Esecuzione di un adattatore di sincronizzazione.
Lo snippet seguente mostra come aggiungere le autorizzazioni:
<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>
Infine, per dichiarare l'elemento Service
associato che il framework utilizza per
interagire con l'adattatore di sincronizzazione, aggiungi il seguente XML al file manifest dell'app come elemento secondario
di <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'elemento
<intent-filter>
configura un filtro che viene attivato dall'azione intent
android.content.SyncAdapter
, inviato dal sistema per eseguire l'adattatore di sincronizzazione. Quando viene attivato il filtro, il sistema avvia il servizio associato che hai creato, che in questo esempio è SyncService
. L'attributo android:exported="false"
consente solo alla tua app e al sistema di accedere a Service
. L'attributo android:process=":sync"
indica al sistema di eseguire Service
in un processo condiviso globale denominato sync
. Se nell'app sono presenti più adattatori di sincronizzazione, questi possono condividere questo processo, riducendo così l'overhead.
L'elemento
<meta-data>
fornisce il nome del file XML dei metadati dell'adattatore di sincronizzazione che hai creato in precedenza.
L'attributo android:name
indica che questi metadati sono per il framework dell'adattatore di sincronizzazione. L'elemento
android:resource
specifica il nome del file di metadati.
Ora disponi di tutti i componenti dell'adattatore di sincronizzazione. La lezione successiva illustra come indicare al framework dell'adattatore di sincronizzazione di eseguire l'adattatore di sincronizzazione in risposta a un evento o a cadenza regolare.