Executar um adaptador de sincronização

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

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

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, após uma em um determinado período do dia. Você também pode executar a sincronização quando houver alterações nos dados armazenados no dispositivo. Evite executar adaptador de sincronização como resultado direto de uma ação do usuário, porque ao fazer isso você não terá a da capacidade de agendamento do framework do adaptador de sincronização. Por exemplo, evite fornecendo 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 são alterados
Execute o adaptador de sincronização em resposta a uma mensagem de um servidor, indicando que a autenticação dados sejam alterados. Esta opção permite atualizar os dados do servidor no dispositivo sem prejudicar o desempenho ou desperdiçar a duração da bateria pesquisando o servidor.
Quando os dados do dispositivo forem modificados
Execute um adaptador de sincronização quando os dados mudarem no dispositivo. Com essa opção, você pode enviar dados modificados do dispositivo para um servidor e é especialmente útil se você precisa garantir que o servidor sempre tem os dados mais recentes do dispositivo. Essa opção é simples implementar se você realmente armazenar dados em seu provedor de conteúdo. Se você estiver usando um stub provedor de conteúdo, pode ser mais difícil detectar alterações nos dados.
Em intervalos regulares
Execute um adaptador de sincronização após a expiração de um intervalo escolhido ou em uma determinada a cada vez todos os dias.
Sob demanda
Execute o adaptador de sincronização em resposta a uma ação do usuário. No entanto, para oferecer experiência, você deve confiar principalmente em uma das opções mais automatizadas. Usando automatizadas, você economiza bateria e recursos de 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 modificados

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

O Mensagens do Google Cloud (GCM, na sigla em inglês) oferece e os componentes do dispositivo e servidor necessários para fazer esse sistema de mensagens funcionar. Uso do GCM para acionar transferências é mais confiável e mais eficiente do que procurar status em servidores. Durante a enquete requer um Service que esteja sempre ativo, o GCM usa um O BroadcastReceiver é ativado quando uma mensagem chega. Durante a enquete em intervalos regulares usa energia da bateria mesmo que não haja atualizações disponíveis, o GCM somente enviará quando necessário.

Observação: se você usar o GCM para acionar o adaptador de sincronização por meio de uma transmissão para todos dispositivos em que o app está instalado, lembre-se de que eles recebem a mensagem em 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, considere adiar o início do adaptador de sincronização que é exclusivo para cada dispositivo.

O snippet de código a seguir mostra como executar requestSync() em resposta a um mensagem recebida 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 modificados

Se o app coleta dados em um provedor de conteúdo, e você quer atualizar o servidor sempre que você atualizar o provedor, poderá configurar seu app para executar o adaptador de sincronização automaticamente. Afazeres isso, você registra um observador para o provedor de conteúdo. Quando os dados no seu provedor de conteúdo mudar, o framework do provedor de conteúdo chama o observador. No observador, chame requestSync() para instruir o framework a executar o adaptador de sincronização.

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

Para criar um observador para o provedor de conteúdo, estenda a classe ContentObserver e implementar as duas formas do onChange(). Em onChange(), ligar requestSync() para iniciar o adaptador de sincronização.

Para registrar o observador, transmita-o como um argumento em uma chamada para registerContentObserver(): Em nessa chamada, você também precisa passar um URI de conteúdo para os dados que deseja observar. O conteúdo provedor de serviços compara esse URI de observação com os URIs de conteúdo transmitidos como argumentos ao Métodos ContentResolver que modificam seu provedor, como: ContentResolver.insert(). Se houver uma correspondência, implementação de ContentObserver.onChange() for chamado.

O snippet de código abaixo 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 ao definir um período de espera entre execuções, ou exibindo-o em determinados horários do dia ou ambos. Como executar o adaptador de sincronização periodicamente permite que você faça uma correspondência aproximada ao intervalo de atualização do seu servidor.

Da mesma forma, você poderá enviar dados a partir do dispositivo quando seu servidor estiver relativamente inativo, usando programação do adaptador de sincronização para ser executado à noite. A maioria dos usuários deixa os dispositivos ligados e conectados à noite, então 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 aciona uma transferência de dados em um horário ligeiramente diferente. Se todos os dispositivos executarem ao mesmo tempo, é provável que você sobrecarregue os dados do servidor e da operadora de celular redes VPC.

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

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

Observe que o addPeriodicSync() não executar o adaptador de sincronização em um determinado horário do dia. Para executar o adaptador de sincronização aproximadamente no mesmo horário todos os dias, use um alarme recorrente como gatilho. Alarmes recorrentes são descritos em mais detalhes na documentação de referência para AlarmManager. Se você usar o método método setInexactRepeating() para definir acionadores de hora do dia com alguma variação, você ainda deve randomizar o horário de início para garantir que o adaptador de sincronização seja executado em diferentes dispositivos de maneira escalonada.

O método addPeriodicSync() não desativar setSyncAutomatically(), Portanto, você pode ter várias execuções de sincronização em um período de tempo relativamente curto. Além disso, apenas alguns sinalizações de controle do adaptador de sincronização são permitidas em uma chamada para addPeriodicSync() as sinalizações não permitidos estão descritos na documentação de referência para 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

Executar o adaptador de sincronização em resposta a uma solicitação do usuário é a estratégia menos recomendada para executar um adaptador de sincronização. A estrutura foi projetada especificamente para conservar a energia da bateria ao executar adaptadores de sincronização de acordo com uma programação. Opções que executam uma sincronização em resposta aos dados as mudanças usam a energia da bateria de forma eficaz, já que ela é usada para fornecer novos dados.

Permitir que os usuários executem uma sincronização sob demanda significa que ela será executada sozinha, o que é o uso ineficiente de recursos de rede e energia. Além disso, fornecer sincronização sob demanda leva os usuários a solicitar uma sincronização, mesmo se não houver evidências de que os dados foram alterados, e executar uma sincronização que não atualiza os dados é um uso ineficaz da energia da bateria. Em geral, o app precisa usar outros sinais para acionar uma sincronização ou programá-los em intervalos regulares, sem a entrada do usuário.

No entanto, se ainda quiser executar o adaptador de sincronização sob demanda, defina as sinalizações do adaptador de sincronização para um adaptador de sincronização manual e, em seguida, 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 definida por setSyncAutomatically().
SYNC_EXTRAS_EXPEDITED
Força o início imediato da sincronização. Se você não o definir, o sistema pode aguardar vários segundos antes de executar a solicitação de sincronização, porque tenta otimizar o uso da bateria pela e programar muitas solicitações em um curto período de tempo.

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

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