Esegui un adattatore di sincronizzazione

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.

Nelle lezioni precedenti di questo corso hai imparato a creare un componente dell'adattatore di sincronizzazione che incapsula il codice Data Transfer e ad aggiungere i componenti aggiuntivi che ti consentono di collegare l'adattatore di sincronizzazione al sistema. Ora hai tutto il necessario per installare un'app che include un adattatore di sincronizzazione, ma nessuno del codice visualizzato esegue effettivamente l'adattatore di sincronizzazione.

Dovresti provare a eseguire l'adattatore di sincronizzazione in base a una programmazione o come risultato indiretto di un evento. Ad esempio, è possibile che l'adattatore di sincronizzazione venga eseguito regolarmente, dopo un determinato periodo di tempo o a una particolare ora del giorno. Puoi utilizzare l'adattatore di sincronizzazione anche in caso di modifiche ai dati memorizzati sul dispositivo. Dovresti evitare di eseguire l'adattatore di sincronizzazione come risultato diretto di un'azione dell'utente, poiché così facendo non potrai sfruttare appieno la capacità di pianificazione del framework dell'adattatore di sincronizzazione. Ad esempio, evita di fornire un pulsante di aggiornamento nell'interfaccia utente,

Per eseguire l'adattatore di sincronizzazione sono disponibili le seguenti opzioni:

Quando cambiano i dati del server
Esegui l'adattatore di sincronizzazione in risposta a un messaggio da un server, che indica che i dati basati su server sono stati modificati. Questa opzione consente di aggiornare i dati dal server al dispositivo senza compromettere le prestazioni o sprecare la durata della batteria eseguendo il polling del server.
Quando cambiano i dati del dispositivo
Esegui un adattatore di sincronizzazione quando i dati cambiano sul dispositivo. Questa opzione consente di inviare i dati modificati dal dispositivo a un server ed è particolarmente utile se devi assicurarti che il server abbia sempre i dati del dispositivo più recenti. Questa opzione è semplice da implementare se memorizzi effettivamente i dati nel tuo fornitore di contenuti. Se utilizzi un fornitore di contenuti stub, il rilevamento delle modifiche ai dati può essere più difficile.
A intervalli regolari
Esegui un adattatore di sincronizzazione dopo la scadenza di un intervallo scelto oppure eseguilo ogni giorno a una determinata ora.
On demand
Esegui l'adattatore di sincronizzazione in risposta a un'azione dell'utente. Tuttavia, per offrire la migliore esperienza utente possibile, ti consigliamo di affidarti principalmente a una delle opzioni più automatizzate. Utilizzando le opzioni automatizzate, risparmi batteria e risorse di rete.

Il resto della lezione descrive ciascuna opzione in modo più dettagliato.

Esegui l'adattatore di sincronizzazione quando i dati del server cambiano

Se l'app trasferisce dati da un server e i dati del server cambiano spesso, puoi utilizzare un adattatore di sincronizzazione per eseguire i download in risposta alle modifiche ai dati. Per eseguire l'adattatore di sincronizzazione, fai in modo che il server invii un messaggio speciale a BroadcastReceiver nell'app. In risposta a questo messaggio, chiama ContentResolver.requestSync() per indicare al framework dell'adattatore di sincronizzazione di eseguire l'adattatore di sincronizzazione.

Google Cloud Messaging (GCM) fornisce i componenti server e dispositivo necessari per il funzionamento di questo sistema di messaggistica. L'utilizzo di GCM per attivare i trasferimenti è più affidabile ed efficiente rispetto al polling dei server per lo stato. Mentre il polling richiede un Service sempre attivo, GCM utilizza un BroadcastReceiver che viene attivato all'arrivo di un messaggio. Il polling a intervalli regolari consuma batteria anche se non sono disponibili aggiornamenti, ma GCM invia messaggi solo quando necessario.

Nota. Se utilizzi GCM per attivare l'adattatore di sincronizzazione tramite una trasmissione a tutti i dispositivi su cui è installata l'app, ricorda che ricevono il messaggio più o meno contemporaneamente. Questa situazione può causare l'esecuzione contemporanea di più istanze dell'adattatore di sincronizzazione, causando un sovraccarico del server e della rete. Per evitare che questo accada per una trasmissione su tutti i dispositivi, ti consigliamo di posticipare l'avvio dell'adattatore di sincronizzazione per un periodo che sia univoco per ogni dispositivo.

Il seguente snippet di codice mostra come eseguire requestSync() in risposta a un messaggio GCM in arrivo:

Kotlin

...
// Constants
// Content provider authority
const val AUTHORITY = "com.example.android.datasync.provider"
// Account type
const val ACCOUNT_TYPE = "com.example.android.datasync"
// Account
const val ACCOUNT = "default_account"
// Incoming Intent key for extended data
const val KEY_SYNC_REQUEST = "com.example.android.datasync.KEY_SYNC_REQUEST"
...
class GcmBroadcastReceiver : BroadcastReceiver() {
    ...
    override fun onReceive(context: Context, intent: Intent) {
        // Get a GCM object instance
        val gcm: GoogleCloudMessaging = GoogleCloudMessaging.getInstance(context)
        // Get the type of GCM message
        val messageType: String? = gcm.getMessageType(intent)
        /*
         * Test the message type and examine the message contents.
         * Since GCM is a general-purpose messaging system, you
         * may receive normal messages that don't require a sync
         * adapter run.
         * The following code tests for a a boolean flag indicating
         * that the message is requesting a transfer from the device.
         */
        if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE == messageType
            && intent.getBooleanExtra(KEY_SYNC_REQUEST, false)) {
            /*
             * Signal the framework to run your sync adapter. Assume that
             * app initialization has already created the account.
             */
            ContentResolver.requestSync(mAccount, AUTHORITY, null)
            ...
        }
        ...
    }
    ...
}

Java

public class GcmBroadcastReceiver extends BroadcastReceiver {
    ...
    // Constants
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // Account type
    public static final String ACCOUNT_TYPE = "com.example.android.datasync";
    // Account
    public static final String ACCOUNT = "default_account";
    // Incoming Intent key for extended data
    public static final String KEY_SYNC_REQUEST =
            "com.example.android.datasync.KEY_SYNC_REQUEST";
    ...
    @Override
    public void onReceive(Context context, Intent intent) {
        // Get a GCM object instance
        GoogleCloudMessaging gcm =
                GoogleCloudMessaging.getInstance(context);
        // Get the type of GCM message
        String messageType = gcm.getMessageType(intent);
        /*
         * Test the message type and examine the message contents.
         * Since GCM is a general-purpose messaging system, you
         * may receive normal messages that don't require a sync
         * adapter run.
         * The following code tests for a a boolean flag indicating
         * that the message is requesting a transfer from the device.
         */
        if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)
            &&
            intent.getBooleanExtra(KEY_SYNC_REQUEST)) {
            /*
             * Signal the framework to run your sync adapter. Assume that
             * app initialization has already created the account.
             */
            ContentResolver.requestSync(mAccount, AUTHORITY, null);
            ...
        }
        ...
    }
    ...
}

Eseguire l'adattatore di sincronizzazione quando cambiano i dati del fornitore di contenuti

Se la tua app raccoglie dati presso un fornitore di contenuti e vuoi aggiornare il server ogni volta che aggiorni il provider, puoi configurare l'app in modo che esegua automaticamente l'adattatore di sincronizzazione. A questo scopo, devi registrare un osservatore per il fornitore di contenuti. Quando i dati del fornitore di contenuti cambiano, il framework del fornitore di contenuti chiama l'osservatore. Nell'osservatore, chiama requestSync() per indicare al framework di eseguire l'adattatore di sincronizzazione.

Nota: se utilizzi un fornitore di contenuti stub, non sono presenti dati nel fornitore di contenuti e onChange() non viene mai chiamato. In questo caso, devi fornire un meccanismo personale per rilevare le modifiche ai dati del dispositivo. Questo meccanismo è anche responsabile della chiamata a requestSync() quando i dati cambiano.

Per creare un osservatore per il tuo fornitore di contenuti, estendi la classe ContentObserver e implementa entrambe le forme del suo metodo onChange(). In onChange(), chiama requestSync() per avviare l'adattatore di sincronizzazione.

Per registrare l'osservatore, passalo come argomento in una chiamata a registerContentObserver(). In questa chiamata, devi anche passare un URI di contenuti per i dati che vuoi guardare. Il framework del fornitore di contenuti confronta l'URI di questo smartwatch con gli URI dei contenuti trasmessi come argomenti ai metodi ContentResolver che modificano il provider, come ContentResolver.insert(). Se viene rilevata una corrispondenza, viene richiamata l'implementazione di ContentObserver.onChange().

Il seguente snippet di codice mostra come definire un ContentObserver che chiama requestSync() quando una tabella viene modificata:

Kotlin

// Constants
// Content provider scheme
const val SCHEME = "content://"
// Content provider authority
const val AUTHORITY = "com.example.android.datasync.provider"
// Path for the content provider table
const val TABLE_PATH = "data_table"
...
class MainActivity : FragmentActivity() {
    ...
    // A content URI for the content provider's data table
    private lateinit var uri: Uri
    // A content resolver for accessing the provider
    private lateinit var mResolver: ContentResolver
    ...
    inner class TableObserver(...) : ContentObserver(...) {
        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         * This method signature is provided for compatibility with
         * older platforms.
         */
        override fun onChange(selfChange: Boolean) {
            /*
             * Invoke the method signature available as of
             * Android platform version 4.1, with a null URI.
             */
            onChange(selfChange, null)
        }

        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         */
        override fun onChange(selfChange: Boolean, changeUri: Uri?) {
            /*
             * Ask the framework to run your sync adapter.
             * To maintain backward compatibility, assume that
             * changeUri is null.
             */
            ContentResolver.requestSync(account, AUTHORITY, null)
        }
        ...
    }
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Get the content resolver object for your app
        mResolver = contentResolver
        // Construct a URI that points to the content provider data table
        uri = Uri.Builder()
                .scheme(SCHEME)
                .authority(AUTHORITY)
                .path(TABLE_PATH)
                .build()
        /*
         * Create a content observer object.
         * Its code does not mutate the provider, so set
         * selfChange to "false"
         */
        val observer = TableObserver(false)
        /*
         * Register the observer for the data table. The table's path
         * and any of its subpaths trigger the observer.
         */
        mResolver.registerContentObserver(uri, true, observer)
        ...
    }
    ...
}

Java

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // Content provider scheme
    public static final String SCHEME = "content://";
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // Path for the content provider table
    public static final String TABLE_PATH = "data_table";
    // Account
    public static final String ACCOUNT = "default_account";
    // Global variables
    // A content URI for the content provider's data table
    Uri uri;
    // A content resolver for accessing the provider
    ContentResolver mResolver;
    ...
    public class TableObserver extends ContentObserver {
        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         * This method signature is provided for compatibility with
         * older platforms.
         */
        @Override
        public void onChange(boolean selfChange) {
            /*
             * Invoke the method signature available as of
             * Android platform version 4.1, with a null URI.
             */
            onChange(selfChange, null);
        }
        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         */
        @Override
        public void onChange(boolean selfChange, Uri changeUri) {
            /*
             * Ask the framework to run your sync adapter.
             * To maintain backward compatibility, assume that
             * changeUri is null.
             */
            ContentResolver.requestSync(mAccount, AUTHORITY, null);
        }
        ...
    }
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Get the content resolver object for your app
        mResolver = getContentResolver();
        // Construct a URI that points to the content provider data table
        uri = new Uri.Builder()
                  .scheme(SCHEME)
                  .authority(AUTHORITY)
                  .path(TABLE_PATH)
                  .build();
        /*
         * Create a content observer object.
         * Its code does not mutate the provider, so set
         * selfChange to "false"
         */
        TableObserver observer = new TableObserver(false);
        /*
         * Register the observer for the data table. The table's path
         * and any of its subpaths trigger the observer.
         */
        mResolver.registerContentObserver(uri, true, observer);
        ...
    }
    ...
}

Esegui periodicamente l'adattatore di sincronizzazione

Puoi eseguire l'adattatore di sincronizzazione periodicamente impostando un periodo di tempo di attesa tra un'esecuzione e l'altra, eseguendolo in determinati momenti della giornata oppure eseguendo entrambe le operazioni. Se esegui periodicamente l'adattatore di sincronizzazione, puoi ottenere approssimativamente l'intervallo di aggiornamento del server.

Analogamente, puoi caricare dati dal dispositivo quando il server è relativamente inattivo, pianificando l'esecuzione dell'adattatore di sincronizzazione durante la notte. La maggior parte degli utenti lascia l'alimentazione accesa e collegata alla corrente di notte, quindi solitamente questo orario è disponibile. Inoltre, il dispositivo non esegue altre attività contemporaneamente all'adattatore di sincronizzazione. Se scegli questo approccio, tuttavia, devi assicurarti che ogni dispositivo attivi un trasferimento di dati in un momento leggermente diverso. Se l'adattatore di sincronizzazione viene eseguito su tutti i dispositivi contemporaneamente, il server e le reti di dati del provider di telefonia mobile potrebbero sovraccaricare.

In generale, le esecuzioni periodiche sono utili se gli utenti non hanno bisogno di aggiornamenti istantanei, ma si aspettano di ricevere aggiornamenti regolari. Le esecuzioni periodiche hanno senso anche se vuoi bilanciare la disponibilità di dati aggiornati con l'efficienza delle esecuzioni di adattatori di sincronizzazione più piccole che non consumano troppe risorse del dispositivo.

Per attivare l'adattatore di sincronizzazione a intervalli regolari, chiama addPeriodicSync(). Questa operazione pianifica l'esecuzione dell'adattatore di sincronizzazione dopo un determinato periodo di tempo. Poiché il framework dell'adattatore di sincronizzazione deve tenere conto di altre esecuzioni dell'adattatore di sincronizzazione e cercare di massimizzare l'efficienza della batteria, il tempo trascorso potrebbe variare di alcuni secondi. Inoltre, il framework non eseguirà l'adattatore di sincronizzazione se la rete non è disponibile.

Tieni presente che addPeriodicSync() non esegue l'adattatore di sincronizzazione in una determinata ora del giorno. Per attivare l'adattatore di sincronizzazione all'incirca alla stessa ora ogni giorno, utilizza una sveglia ricorrente come attivatore. Gli allarmi ricorrenti sono descritti in maggiore dettaglio nella documentazione di riferimento per AlarmManager. Se utilizzi il metodo setInexactRepeating() per impostare gli attivatori dell'ora del giorno che presentano variazioni, devi comunque creare un'ora di inizio casuale per garantire che l'adattatore di sincronizzazione venga eseguito da dispositivi diversi.

Il metodo addPeriodicSync() non disabilita setSyncAutomatically(), quindi potresti ricevere più esecuzioni di sincronizzazione in un periodo di tempo relativamente breve. Inoltre, in una chiamata a addPeriodicSync() sono consentiti solo alcuni flag di controllo dell'adattatore di sincronizzazione; quelli non consentiti sono descritti nella documentazione di riferimento per addPeriodicSync().

Il seguente snippet di codice mostra come pianificare esecuzioni periodiche dell'adattatore di sincronizzazione:

Kotlin

// Content provider authority
const val AUTHORITY = "com.example.android.datasync.provider"
// Account
const val ACCOUNT = "default_account"
// Sync interval constants
const val SECONDS_PER_MINUTE = 60L
const val SYNC_INTERVAL_IN_MINUTES = 60L
const val SYNC_INTERVAL = SYNC_INTERVAL_IN_MINUTES * SECONDS_PER_MINUTE
...
class MainActivity : FragmentActivity() {
    ...
    // A content resolver for accessing the provider
    private lateinit var mResolver: ContentResolver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Get the content resolver for your app
        mResolver = contentResolver
        /*
         * Turn on periodic syncing
         */
        ContentResolver.addPeriodicSync(
                mAccount,
                AUTHORITY,
                Bundle.EMPTY,
                SYNC_INTERVAL)
        ...
    }
    ...
}

Java

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // Account
    public static final String ACCOUNT = "default_account";
    // Sync interval constants
    public static final long SECONDS_PER_MINUTE = 60L;
    public static final long SYNC_INTERVAL_IN_MINUTES = 60L;
    public static final long SYNC_INTERVAL =
            SYNC_INTERVAL_IN_MINUTES *
            SECONDS_PER_MINUTE;
    // Global variables
    // A content resolver for accessing the provider
    ContentResolver mResolver;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Get the content resolver for your app
        mResolver = getContentResolver();
        /*
         * Turn on periodic syncing
         */
        ContentResolver.addPeriodicSync(
                mAccount,
                AUTHORITY,
                Bundle.EMPTY,
                SYNC_INTERVAL);
        ...
    }
    ...
}

Esegui l'adattatore di sincronizzazione on demand

L'esecuzione dell'adattatore di sincronizzazione in risposta alla richiesta di un utente è la strategia meno preferibile per l'esecuzione di un adattatore di sincronizzazione. Il framework è progettato specificamente per risparmiare batteria quando esegue adattatori di sincronizzazione in base a una pianificazione. Le opzioni che eseguono una sincronizzazione in risposta alle modifiche dei dati consumano la batteria in modo efficace, poiché viene impiegata per fornire nuovi dati.

In confronto, consentire agli utenti di eseguire una sincronizzazione on demand significa che la sincronizzazione viene eseguita da sola, il che comporta un uso inefficiente della rete e delle risorse di alimentazione. Inoltre, fornire la sincronizzazione on demand porta gli utenti a richiedere una sincronizzazione anche se non è evidente che i dati siano stati modificati. Inoltre, eseguire una sincronizzazione che non aggiorna i dati è un uso inefficace dell'alimentazione a batteria. In generale, la tua app deve utilizzare altri indicatori per attivare una sincronizzazione o pianificarla a intervalli regolari, senza input utente.

Tuttavia, se vuoi comunque eseguire l'adattatore di sincronizzazione on demand, imposta i flag dell'adattatore di sincronizzazione per l'esecuzione manuale dell'adattatore di sincronizzazione, quindi chiama ContentResolver.requestSync().

Esegui trasferimenti on demand con i seguenti flag:

SYNC_EXTRAS_MANUAL
Forza una sincronizzazione manuale. Il framework dell'adattatore di sincronizzazione ignora le impostazioni esistenti, ad esempio il flag impostato da setSyncAutomatically().
SYNC_EXTRAS_EXPEDITED
Forza l'avvio immediato della sincronizzazione. Se non imposti questa opzione, il sistema potrebbe attendere diversi secondi prima di eseguire la richiesta di sincronizzazione, perché tenta di ottimizzare l'utilizzo della batteria pianificando molte richieste in un breve periodo di tempo.

Il seguente snippet di codice mostra come chiamare requestSync() in risposta a un clic su un pulsante:

Kotlin

// Constants
// Content provider authority
val AUTHORITY = "com.example.android.datasync.provider"
// Account type
val ACCOUNT_TYPE = "com.example.android.datasync"
// Account
val ACCOUNT = "default_account"
...
class MainActivity : FragmentActivity() {
    ...
    // Instance fields
    private lateinit var mAccount: Account
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        /*
         * Create the placeholder account. The code for CreateSyncAccount
         * is listed in the lesson Creating a Sync Adapter
         */

        mAccount = createSyncAccount()
        ...
    }

    /**
     * Respond to a button click by calling requestSync(). This is an
     * asynchronous operation.
     *
     * This method is attached to the refresh button in the layout
     * XML file
     *
     * @param v The View associated with the method call,
     * in this case a Button
     */
    fun onRefreshButtonClick(v: View) {
        // Pass the settings flags by inserting them in a bundle
        val settingsBundle = Bundle().apply {
            putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true)
            putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true)
        }
        /*
         * Request the sync for the default account, authority, and
         * manual sync settings
         */
        ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle)
    }

Java

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // Content provider authority
    public static final String AUTHORITY =
            "com.example.android.datasync.provider";
    // Account type
    public static final String ACCOUNT_TYPE = "com.example.android.datasync";
    // Account
    public static final String ACCOUNT = "default_account";
    // Instance fields
    Account mAccount;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        /*
         * Create the placeholder account. The code for CreateSyncAccount
         * is listed in the lesson Creating a Sync Adapter
         */

        mAccount = CreateSyncAccount(this);
        ...
    }
    /**
     * Respond to a button click by calling requestSync(). This is an
     * asynchronous operation.
     *
     * This method is attached to the refresh button in the layout
     * XML file
     *
     * @param v The View associated with the method call,
     * in this case a Button
     */
    public void onRefreshButtonClick(View v) {
        // Pass the settings flags by inserting them in a bundle
        Bundle settingsBundle = new Bundle();
        settingsBundle.putBoolean(
                ContentResolver.SYNC_EXTRAS_MANUAL, true);
        settingsBundle.putBoolean(
                ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
        /*
         * Request the sync for the default account, authority, and
         * manual sync settings
         */
        ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle);
    }