The Android Developer Challenge is back! Submit your idea before December 2.

Criar um adaptador de sincronização

Observação: recomendamos WorkManager como a solução 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 é mais adequada para você.

O componente adaptador de sincronização no seu app encapsula o código das tarefas que transferem dados entre o dispositivo e um servidor. Com base na programação e nos gatilhos fornecidos no app, o framework do adaptador de sincronização executa o código no componente do adaptador de sincronização. Para adicionar um componente do adaptador de sincronização ao app, você precisa adicionar o seguinte:

Classe do adaptador de sincronização.
Uma classe que une seu código de transferência de dados em uma interface compatível com o framework do adaptador de sincronização.
Service vinculado.
Um componente que permite que o framework do adaptador de sincronização execute o código na sua classe de adaptador de sincronização.
Arquivo de metadados XML do adaptador de sincronização.
Um arquivo que contém informações sobre o adaptador de sincronização. O framework lê esse arquivo para descobrir como carregar e programar a transferência de dados.
Declarações no manifesto do app.
XML que declara o serviço vinculado e aponta para os metadados específicos do adaptador de sincronização.

Esta lição mostra como definir esses elementos.

Criar uma classe de adaptador de sincronização

Nesta parte da aula, você aprenderá a criar a classe do adaptador de sincronização que encapsula o código de transferência de dados. A criação da classe inclui estender a classe base do adaptador de sincronização, definir construtores para ela e implementar o método em que você define as tarefas de transferência de dados.

Estender a classe base do adaptador de sincronização

Para criar o componente adaptador de sincronização, comece por estender AbstractThreadedSyncAdapter e criar seus construtores. Use os construtores para executar tarefas de configuração cada vez que o componente adaptador de sincronização correspondente for criado a partir do zero, da mesma forma que você usa Activity.onCreate() para configurar uma atividade. Por exemplo, se seu app usa um provedor de conteúdo para armazenar dados, use os construtores para ter uma instância ContentResolver. Como uma segunda forma do construtor foi adicionada à plataforma Android versão 3.0 para compatibilizar com o argumento parallelSyncs, você precisa criar duas formas do construtor para manter a compatibilidade.

Observação: o framework do adaptador de sincronização foi projetado para funcionar com componentes do adaptador de sincronização que são instâncias únicas. A criação de instâncias do componente adaptador de sincronização é descrita em mais detalhes na seção Vincular o adaptador de sincronização ao framework.

O exemplo a seguir mostra como implementar AbstractThreadedSyncAdapter e seus construtores.

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

Adicionar o código de transferência de dados

O componente do adaptador de sincronização não faz a transferência de dados automaticamente. Em vez disso, ele encapsula seu código de transferência de dados, para que o framework do adaptador de sincronização execute a transferência de dados em segundo plano, sem o envolvimento do app. Quando o framework está pronto para sincronizar dados do app, ele invoca a implementação do método onPerformSync().

Para facilitar a transferência de dados a partir do código do app principal para o componente adaptador de sincronização, o framework do adaptador sincronização chama onPerformSync() com os seguintes argumentos:

Conta
Um objeto Account associado com o evento que acionou o adaptador de sincronização. Se seu servidor não usa contas, você não precisa usar as informações nesse objeto.
Extras
Um Bundle contendo sinalizações enviadas pelo evento que acionou o adaptador de sincronização.
Autoridade
A autoridade de um provedor de conteúdo no sistema. O app precisa ter acesso a esse provedor. Normalmente, a autoridade corresponde a um provedor de conteúdo no seu próprio app.
Cliente do provedor de conteúdo
O ContentProviderClient do provedor de conteúdo apontado pelo argumento de autoridade. O ContentProviderClient é uma interface pública leve para um provedor de conteúdo. Ele tem a mesma funcionalidade básica de um ContentResolver. Se você estiver usando um provedor de conteúdo para armazenar dados para seu app, poderá se conectar ao provedor com esse objeto. Caso contrário, pode ignorá-lo.
Resultado da sincronização
Um objeto SyncResult que você usa para enviar informações para o framework do adaptador de sincronização.

O snippet a seguir mostra a estrutura geral de 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.
         */
    }
    

Embora a implementação real de onPerformSync() seja específica para requisitos de sincronização de dados do app e protocolos de conexão do servidor, sua implementação precisa executar algumas tarefas gerais:

Conectar-se a um servidor
Embora você possa supor que a rede esteja disponível quando a transferência de dados for iniciada, o framework do adaptador de sincronização não se conecta automaticamente a um servidor.
Fazer o download e upload de dados
Um adaptador de sincronização não automatiza tarefas de transferência de dados. Para fazer o download de dados de um servidor e armazená-los em um provedor de conteúdo, forneça o código que solicita os dados, faça o download e insira-os no provedor. Da mesma forma, se você quiser enviar dados a um servidor, é necessário lê-los a partir de um arquivo, banco de dados ou provedor e enviar a solicitação de upload necessária. Você também precisa gerenciar erros de rede que ocorrem quando a transferência de dados está em execução.
Gerenciar conflitos de dados ou determinar a atualidade dos dados
Um adaptador de sincronização não gerencia automaticamente conflitos entre dados no servidor e no dispositivo. Além disso, ele não detecta automaticamente se os dados no servidor são mais recentes do que os dados no dispositivo ou vice-versa. Para isso, você precisa fornecer algoritmos próprios para gerenciar essa situação.
Limpeza
Sempre feche conexões com um servidor e limpe arquivos temporários e armazene-os no final da transferência de dados.

Observação: o framework do adaptador de sincronização executa onPerformSync() em uma linha de execução em segundo plano, para que você não tenha que configurar seu processamento em segundo plano.

Além das tarefas relacionadas com a sincronização, você precisa tentar combinar as tarefas regulares relacionadas à rede e adicioná-las a onPerformSync(). Ao concentrar todas as tarefas de rede nesse método, você economiza a energia da bateria necessária para iniciar e interromper as interfaces de rede. Para saber mais sobre como tornar o acesso à rede mais eficiente, consulte a aula de treinamento Transferência de dados sem consumo de bateria, que descreve várias tarefas de acesso à rede que você pode incluir no código de transferência de dados.

Vincular o adaptador de sincronização ao framework

Agora você tem o código de transferência de dados encapsulado em um componente de adaptador de sincronização, mas é necessário que o acesso ao código seja fornecido ao framework. Para fazer isso, você precisa criar um Service vinculado que transmita um objeto especial Android vinculado do componente adaptador de sincronização ao framework. Com esse objeto vinculado, o framework pode invocar o método onPerformSync() e transmitir os dados para ele.

Instanciar o componente adaptador de sincronização como um singleton no método onCreate() do serviço. Ao instanciar o componente em onCreate(), você adia a criação até que o serviço seja iniciado, o que acontece na primeira tentativa do framework de executar a transferência de dados. Você precisa instanciar o componente de uma maneira segura na linha de execução, caso o framework do adaptador de sincronização enfileire várias execuções em resposta a gatilhos ou programação.

Por exemplo, o snippet a seguir mostra como criar uma classe que implementa o Service vinculado, instancia o componente adaptador de sincronização e recebe o objeto Android vinculado.

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

Observação: para ver um exemplo mais detalhado de um serviço vinculado a um adaptador de sincronização, consulte o app de exemplo.

Adicionar a conta exigida pelo framework

O framework do adaptador de sincronização exige que cada adaptador de sincronização tenha um tipo de conta. Você declarou o valor do tipo de conta na seção Adicionar o arquivo de metadados do autenticador. Agora você precisa configurar esse tipo de conta no sistema Android. Para configurar o tipo de conta, adicione uma conta fictícia que use o tipo de conta, chamando addAccountExplicitly().

O melhor lugar para chamar está no método onCreate() da atividade de abertura do seu app. O snippet de código a seguir mostra como fazer isso.

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 = "dummyaccount"
    ...
    class MainActivity : FragmentActivity() {

        // Instance fields
        private lateinit var mAccount: Account
        ...
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
           ...
            // Create the dummy account
            mAccount = createSyncAccount()
           ...
        }
        ...
        /**
         * Create a new dummy 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 = "dummyaccount";
        // Instance fields
        Account mAccount;
        ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ...
            // Create the dummy account
            mAccount = CreateSyncAccount(this);
            ...
        }
        ...
        /**
         * Create a new dummy 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.
                 */
            }
        }
        ...
    }
    

Adicionar o arquivo de metadados do adaptador de sincronização

Para conectar o componente do adaptador de sincronização ao framework, você precisa fornecer a ele metadados que descrevam o componente e forneçam sinalizações adicionais. Os metadados especificam o tipo de conta que você criou para o adaptador de sincronização, declaram uma autoridade do provedor de conteúdo associada ao app, controlam uma parte da interface do usuário do sistema relacionada a adaptadores de sincronização e declaram outras sinalizações relacionadas à sincronização. Declare esses metadados em um arquivo XML especial armazenado no diretório /res/xml/ no projeto do app. Você pode dar qualquer nome ao arquivo, embora ele seja normalmente chamado syncadapter.xml.

Esse arquivo XML contém um único elemento XML <sync-adapter> que tem os seguintes atributos:

android:contentAuthority
A autoridade do URI para seu provedor de conteúdo. Se você criou um stub de provedor de conteúdo para o app na lição anterior sobre como Criar um stub de provedor de conteúdo, utilize o valor especificado para o atributo android:authorities no elemento <provider> que você adicionou ao manifesto do seu app. Esse atributo é descrito com mais detalhes na seção Declarar o provedor no manifesto.
Se você transferir dados de um provedor de conteúdo para um servidor com o adaptador de sincronização, esse valor precisará ser o mesmo da autoridade de URI de conteúdo usada para esses dados. Esse valor é também uma das autoridades especificadas no atributo android:authorities do elemento <provider> que declara o provedor no manifesto do app.
android:accountType
O tipo de conta exigido pelo framework do adaptador de sincronização. O valor precisa ser o mesmo que o valor do tipo de conta que você forneceu quando criou o arquivo de metadados do autenticador, conforme descrito na seção Adicionar o arquivo de metadados do autenticador. É também o valor especificado para a constante ACCOUNT_TYPE no snippet de código da seção Adicionar a conta exigida pelo framework.
Atributos de configuração
android:userVisible
Define a visibilidade do tipo de conta do adaptador de sincronização. Por padrão, o ícone da conta e rótulo associado com o tipo de conta estão visíveis na seção Contas do app Config. do sistema, de modo que você precisa tornar o adaptador de sincronização invisível, a menos que tenha um tipo de conta ou domínio que seja facilmente associado ao app. Mesmo que você torne seu tipo de conta invisível, poderá permitir que os usuários controlem o adaptador de sincronização com uma interface do usuário em uma das atividades do app.
android:supportsUploading
Permite que você faça upload de dados para a nuvem. Defina esta opção como false se o app fizer apenas downloads de dados.
android:allowParallelSyncs
Permite que várias instâncias do adaptador de sincronização sejam executadas ao mesmo tempo. Use se o app for compatível com várias contas de usuário e você quiser permitir que vários usuários transfiram dados em paralelo. Essa sinalização não terá efeito se você nunca executar várias transferências de dados.
android:isAlwaysSyncable
Indica ao framework do adaptador de sincronização que ele pode ser executado a qualquer momento. Se você quiser controlar programaticamente quando o adaptador de sincronização pode ser executado, defina essa sinalização como false e depois chame requestSync() para executá-lo. Para saber mais sobre o funcionamento de um adaptador de sincronização, consulte a lição Como executar um adaptador de sincronização

O exemplo a seguir mostra o XML de um adaptador de sincronização que usa uma única conta fictícia e faz apenas downloads.

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

Declarar o adaptador de sincronização no manifesto

Depois de adicionar o componente do adaptador de sincronização ao app, você precisa solicitar permissões relacionadas com o uso do componente e declarar o Service vinculado que adicionou.

Como o componente do adaptador de sincronização executa o código que transfere dados entre a rede e o dispositivo, você precisa solicitar permissão para acessar a Internet. Além disso, seu app precisa solicitar permissão para ler e gravar as configurações do adaptador de sincronização para que você possa controlar o adaptador de sincronização de outros componentes no app. Você também precisa solicitar uma permissão especial que permite que seu app use o componente do autenticador que você criou na lição sobre como Criar um stub de autenticação.

Para solicitar essas permissões, adicione o seguinte ao manifesto do app como elementos filhos de <manifest>:

android.permission.INTERNET
Permite que o código do adaptador de sincronização acesse a Internet para fazer o download ou o upload de dados do dispositivo para um servidor. Não é necessário adicionar essa permissão novamente se você a solicitou anteriormente.
android.permission.READ_SYNC_SETTINGS
Permite que o app leia as configurações atuais do adaptador de sincronização. Por exemplo, você precisa dessa permissão para chamar getIsSyncable().
android.permission.WRITE_SYNC_SETTINGS
Permite que seu app controle as configurações do adaptador de sincronização. Você precisa dessa permissão para definir que o adaptador de sincronização periódica seja executado usando addPeriodicSync(). Essa permissão não é necessária para chamar requestSync(). Para saber mais sobre o funcionamento do adaptador de sincronização, consulte Executar um adaptador de sincronização.

O snippet a seguir mostra como adicionar as permissões.

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

Finalmente, para declarar o Service vinculado que o framework usa para interagir com o adaptador de sincronização, adicione o seguinte XML ao manifesto do app como um elemento filho de <application>:

            <service
                    android:name="com.example.android.datasync.SyncService"
                    android:exported="true"
                    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>
    

O elemento <intent-filter> configura um filtro que é acionado pela ação de intent android.content.SyncAdapter enviada pelo sistema para executar o adaptador de sincronização. Quando o filtro é acionado, o sistema inicia o serviço vinculado que você criou, que neste exemplo é SyncService. O atributo android:exported="true" permite que outros processos, que não sejam seu app (incluindo o sistema), acessem o Service. O atributo android:process=":sync" informa ao sistema para executar o Service em um processo compartilhado global chamado sync. Se você tiver vários adaptadores de sincronização no app, eles poderão compartilhar esse processo, o que reduz a sobrecarga.

O elemento <meta-data> fornece o nome do arquivo XML de metadados do adaptador de sincronização criado anteriormente. O atributo android:name indica que esses metadados são para o framework do adaptador de sincronização. O elemento android:resource especifica o nome do arquivo de metadados.

Agora você tem todos os componentes do adaptador de sincronização. A próxima lição mostra como informar o framework do adaptador de sincronização para executar o adaptador de sincronização, seja em resposta a um evento ou em uma programação regular.