Executar um adaptador de sincronização

Observação: recomendamos o WorkManager como a solução para a maioria dos casos de uso para processamento em segundo plano. Consulte o guia de processamento em segundo plano para saber qual solução é mais adequada para você.

Nas lições anteriores desta aula, você aprendeu a criar um componente de adaptador de sincronização que encapsula o código de transferência de dados e como adicionar outros componentes que permitem conectar o adaptador de sincronização ao sistema. Agora, você tem tudo o que precisa para instalar um app que inclua um adaptador de sincronização, mas nenhum dos códigos que você viu executa o adaptador de sincronização.

Tente executar o adaptador de sincronização com base em uma programação ou como resultado indireto de algum evento. Por exemplo, talvez você queira que o adaptador de sincronização seja executado regularmente, depois de um determinado período ou em um horário específico do dia. Talvez você também queira executar o adaptador de sincronização quando houver alterações nos dados armazenados no dispositivo. Evite executar o adaptador de sincronização como resultado direto de uma ação do usuário, porque, ao fazer isso, você não consegue todos os benefícios da capacidade de agendamento do framework do adaptador de sincronização. Por exemplo, evite fornecer um botão de atualização na interface do usuário.

Você tem as seguintes opções para executar o adaptador de sincronização:

Quando os dados do servidor forem modificados
Execute o adaptador de sincronização em resposta a uma mensagem de um servidor, indicando que os dados baseados no servidor foram modificados. Essa opção permite que você atualize os dados do servidor no dispositivo sem diminuir o desempenho nem desperdiçar a vida da bateria pesquisando o servidor.
Quando os dados do dispositivo forem modificados
Execute um adaptador de sincronização quando os dados forem modificados no dispositivo. Essa opção permite enviar dados modificados do dispositivo para um servidor e é especialmente útil se você precisar garantir que o servidor sempre terá os dados mais recentes do dispositivo. Essa opção é simples de implementar se você realmente armazena dados no provedor de conteúdo. Se você está usando um stub de provedor de conteúdo, a detecção de alterações nos dados pode ser mais difícil.
Em intervalos regulares
Execute um adaptador de sincronização após a expiração de um intervalo escolhido por você ou em um horário específico todos os dias.
Sob demanda
Execute o adaptador de sincronização em resposta a uma ação do usuário. No entanto, para proporcionar a melhor experiência do usuário, confie principalmente em uma das opções mais automatizadas. Usando opções automatizadas, você economiza bateria e recursos da rede.

O restante desta lição descreve cada uma das opções com mais detalhes.

Executar o adaptador de sincronização quando os dados do servidor são alterados

Se o app transferir dados de um servidor, e os dados dele forem modificados com frequência, você poderá usar um adaptador de sincronização para fazer downloads em resposta a essa mudança. Para executar o adaptador de sincronização, configure o servidor para enviar uma mensagem especial a um BroadcastReceiver no seu app. Em resposta a essa mensagem, chame ContentResolver.requestSync() para sinalizar o framework do adaptador de sincronização e executar o adaptador de sincronização.

O Google Cloud Messaging (GCM) fornece os componentes do servidor e do dispositivo de que você precisa para fazer com que o sistema de mensagens funcione. O uso do GCM para acionar transferências é mais confiável e mais eficiente do que a pesquisa do status em servidores. Embora a pesquisa exija um Service sempre ativo, o GCM usa um BroadcastReceiver que é ativado quando uma mensagem chega. A pesquisa em intervalos regulares consome bateria mesmo que nenhuma atualização esteja disponível, mas o GCM só enviará mensagens quando necessário.

Observação: se você usar o GCM para acionar o adaptador de sincronização por meio de uma transmissão a todos os dispositivos em que o app estiver instalado, lembre-se de que eles receberão a mensagem mais ou menos ao mesmo tempo. Essa situação pode fazer com que várias instâncias do adaptador de sincronização sejam executadas ao mesmo tempo, causando sobrecarga no servidor e na rede. Para evitar essa situação em uma transmissão para todos os dispositivos, avalie a possibilidade de adiar o início do adaptador de sincronização por um período exclusivo para cada dispositivo.

O snippet de código a seguir mostra como executar requestSync() em resposta ao recebimento de uma mensagem do GCM.

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

Executar o adaptador de sincronização quando os dados do provedor de conteúdo são alterados

Se o app coletar dados em um provedor de conteúdo e você desejar atualizar o servidor sempre que atualizar o provedor, configure o app para executar o adaptador de sincronização automaticamente. Para fazer isso, registre um observador para o provedor de conteúdo. Quando os dados no provedor de conteúdo são modificados, o framework chama o observador. No observador, chame requestSync() para pedir ao framework que execute o adaptador de sincronização.

Observação: se estiver usando um provedor de conteúdo stub, você não terá nenhum dado no provedor e onChange() nunca será chamado. Nesse caso, você precisa fornecer um mecanismo próprio para detectar mudanças nos dados do dispositivo. Esse mecanismo também é responsável por chamar requestSync() quando os dados mudam.

Se quiser criar um observador para o provedor de conteúdo, estenda a classe ContentObserver e implemente as duas formas do método onChange() dela. Em onChange(), chame requestSync() para iniciar o adaptador de sincronização.

Se quiser registrar o observador, transmita-o como um argumento em uma chamada para registerContentObserver(). Nessa chamada, você também precisa transmitir um URI de conteúdo para os dados que quer observar. O framework do provedor de conteúdo compara esse URI de observação a URIs de conteúdo transmitidos como argumentos para os métodos ContentResolver que modificam seu provedor, como ContentResolver.insert(). Se houver uma correspondência, a implementação de ContentObserver.onChange() será chamada.

O snippet de código a seguir mostra como definir um ContentObserver que chama requestSync() quando uma tabela muda:

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

Executar o adaptador de sincronização periodicamente

Você pode executar o adaptador de sincronização periodicamente definindo um período para aguardar entre execuções ou executá-lo em determinados momentos do dia ou ambos. A execução periódica do adaptador de sincronização permite que você faça a correspondência aproximada ao intervalo de atualização do servidor.

Da mesma forma, você pode fazer upload de dados do dispositivo quando o servidor estiver relativamente inativo, programando o adaptador de sincronização para ser executado à noite. Como a maioria dos usuários mantém os dispositivos ligados e conectados à noite, esse horário geralmente está disponível. Além disso, o dispositivo não está executando outras tarefas ao mesmo tempo que o adaptador de sincronização. No entanto, se você adotar essa abordagem, precisará garantir que cada dispositivo acione uma transferência de dados em um horário um pouco diferente. Se todos os dispositivos executarem o adaptador de sincronização ao mesmo tempo, é provável que você sobrecarregue as redes de dados do servidor e do provedor de dados móveis.

Em geral, as execuções periódicas fazem sentido quando os usuários não precisam de atualizações instantâneas, mas esperam receber atualizações regulares. Execuções periódicas também farão sentido se você quiser equilibrar a disponibilidade de dados atualizados com a eficiência de execuções menores do adaptador de sincronização que não utilizam os recursos do dispositivo em excesso.

Para executar o adaptador de sincronização em intervalos regulares, chame addPeriodicSync(). Essa ação programa o adaptador de sincronização para ser executado após um determinado período. Como o framework precisa considerar outras execuções do adaptador de sincronização e tenta maximizar a eficiência da bateria, o tempo decorrido pode variar em alguns segundos. Além disso, o framework não executará o adaptador de sincronização se a rede não estiver disponível.

Observe que addPeriodicSync() não executa o adaptador de sincronização em um horário do dia específico. Para executar o adaptador de sincronização aproximadamente no mesmo horário todos os dias, use um alarme de repetição como gatilho. Alarmes de repetição são descritos mais detalhadamente na documentação de referência para AlarmManager. Se o método setInexactRepeating() for usado para definir gatilhos em um horário do dia com alguma variação, ainda será necessário estabelecer um horário de início aleatório para garantir que o adaptador de sincronização seja executado em diferentes dispositivos de maneira escalonada.

Como o método addPeriodicSync() não desativa setSyncAutomatically(), você pode ter várias execuções de sincronização em um período relativamente curto. Além disso, apenas algumas sinalizações de controle do adaptador de sincronização são permitidas em uma chamada para addPeriodicSync(). As sinalizações não permitidas são descritas na documentação de referência de addPeriodicSync().

O snippet de código a seguir mostra como programar execuções periódicas do adaptador de sincronização.

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

Executar o adaptador de sincronização sob demanda

A execução do adaptador de sincronização em resposta a uma solicitação do usuário é a estratégia menos recomendável. O framework foi projetado especificamente para conservar a energia da bateria ao executar adaptadores de sincronização de acordo com uma programação. As opções que executam uma sincronização em resposta a alterações em dados usam a energia da bateria de forma eficaz, porque é usada para fornecer novos dados.

Comparativamente, permitir que os usuários executem uma sincronização sob demanda significa que ela é executada por si só, o que significa um uso ineficaz de recursos de rede e energia. Além disso, o fornecimento de sincronização sob demanda leva os usuários a solicitar uma sincronização, mesmo que não haja evidências de que os dados tenham sido alterados, e a execução de uma sincronização que não atualize dados é um uso ineficaz da bateria. No geral, seu app precisa usar outros sinais para acionar uma sincronização ou programá-los em intervalos regulares, sem uma entrada do usuário.

Se mesmo assim você ainda quiser executar o adaptador de sincronização sob demanda, defina as sinalizações para uma execução manual e depois chame ContentResolver.requestSync().

Gerencie transferências sob demanda com as seguintes sinalizações:

SYNC_EXTRAS_MANUAL
Força uma sincronização manual. O framework do adaptador de sincronização ignora as configurações já existentes, como a sinalização estabelecida por setSyncAutomatically().
SYNC_EXTRAS_EXPEDITED
Força o início imediato da sincronização. Se essa opção não é definida, o sistema aguarda alguns segundos antes de executar a solicitação de sincronização, porque ele tenta otimizar o uso da bateria programando várias solicitações em um curto período.

O snippet de código a seguir mostra como chamar requestSync() em resposta a um clique de botão.

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