Criar 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ê.

O componente adaptador de sincronização no app encapsula o código das tarefas que são transferidas dados entre o dispositivo e um servidor. Com base na programação e nos acionadores fornecidos no seu app, o framework do adaptador de sincronização executará o código no componente do adaptador de sincronização. Para adicionar um do adaptador de sincronização ao seu app, será preciso adicionar as seguintes partes:

Classe do adaptador de sincronização.
Classe que encapsula seu código de transferência de dados em uma interface compatível com o adaptador de sincronização de análise de dados em nuvem.
Service vinculado.
Componente que permite que o framework do adaptador de sincronização execute o código no adaptador de sincronização .
Arquivo de metadados XML do adaptador de sincronização.
Um arquivo contendo informações sobre o adaptador de sincronização. O framework lê esse arquivo para saiba como carregar e programar sua transferência de dados.
Declarações no manifesto do app.
XML que declara o serviço vinculado e aponta para 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 lição, 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, definindo construtores para a classe e implementar o método em que você define a transferência de dados tarefas.

Estender a classe base do adaptador de sincronização

Para criar o componente do adaptador de sincronização, comece estendendo AbstractThreadedSyncAdapter e criar os construtores dele. Use o construtores para executar tarefas de configuração sempre que o componente adaptador de sincronização é criado do zero, assim como você usa Activity.onCreate() para configurar um atividades. Por exemplo, se o app usa um provedor de conteúdo para armazenar dados, use os construtores para receber uma instância de ContentResolver. Como uma segunda forma do O construtor foi adicionado na plataforma Android versão 3.0 para oferecer suporte ao 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 o adaptador de sincronização. componentes que são instâncias singleton. A criação de instâncias do componente adaptador de sincronização é abordada com mais detalhes na seção Vincule o adaptador de sincronização ao framework.

O exemplo abaixo mostra como implementar AbstractThreadedSyncAdapter e os construtores dela:

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, encapsula seu código de transferência de dados, para que o framework do adaptador de sincronização possa executar a transferência de dados em segundo plano, sem o envolvimento do seu app. Quando o framework estiver pronto para sincronizar os dados do seu aplicativo, ele invoca a implementação do método onPerformSync():

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

Conta
Um objeto Account associado ao evento que acionou. o adaptador de sincronização. Caso seu servidor não use contas, não é necessário usar o método informações neste objeto.
Extras
Um Bundle contendo flags enviadas pelo evento que acionou a sincronização. por um adaptador.
Autoridade
A autoridade de um provedor de conteúdo no sistema. Seu 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
Um ContentProviderClient do provedor de conteúdo indicado pelo de autoridade. Um ContentProviderClient é um arquivo público para um provedor de conteúdo. Ele tem as mesmas funcionalidades básicas de um ContentResolver: Se você estiver usando um provedor de conteúdo para armazenar dados para seu app, é possível se conectar ao provedor com este objeto. Caso contrário, ignore reimplantá-lo.
Resultado da sincronização
Um objeto SyncResult usado para enviar informações para a sincronização do adaptador de rede.

O snippet a seguir mostra a estrutura geral 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 onPerformSync() é específico para requisitos de sincronização de dados do app e protocolos de conexão de servidor, tarefas gerais que sua implementação deve realizar:

Conectar-se a um servidor
Embora você possa presumir que a rede estará disponível quando sua transferência de dados for iniciada, a o framework do adaptador de sincronização não se conecta automaticamente a um servidor.
Fazer o download e upload de dados
Os adaptadores de sincronização não automatizam nenhuma tarefa de transferência de dados. Se você quiser fazer o download dados de um servidor e armazená-los em um provedor de conteúdo, é necessário fornecer o código solicita os dados, faz o download deles e os insere no provedor. Da mesma forma, se você quiser enviar dados a um servidor, você precisa lê-los de um arquivo, banco de dados ou provedor e a solicitação de upload necessária. Você também precisa lidar com erros de rede que ocorrem 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 lida automaticamente com conflitos entre dados no servidor e dados 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. Em vez disso, você precisa fornecer algoritmos próprios para ao lidar com essa situação.
Limpeza
Sempre feche as conexões com um servidor e limpe os arquivos temporários e os caches ao final do sua transferência de dados.

Observação:o framework do adaptador de sincronização é executado onPerformSync() em uma linha de execução em segundo plano, para que você não precise configurar seu próprio processamento em segundo plano.

Além de suas tarefas relacionadas à sincronização, você deve tentar combinar suas tarefas regulares tarefas relacionadas à rede e adicioná-las onPerformSync(): Ao concentrar todas as tarefas de rede nesse método, você economiza a energia da bateria necessárias para iniciar e interromper as interfaces de rede. Para saber mais sobre como tornar o acesso à rede eficiente, consulte o curso de treinamento Transferência de dados sem consumo de bateria, que descreve diversas informações sobre o acesso à rede tarefas que podem ser incluídas no código de transferência de dados.

Vincular o adaptador de sincronização ao framework

Agora você tem seu código de transferência de dados encapsulado em um componente do adaptador de sincronização, mas para dar ao framework acesso ao código. Para fazer isso, você precisa criar um vinculado Service, que transmite um objeto de vinculação especial Android do adaptador de sincronização. ao framework. Com esse objeto binder, o framework pode invocar o onPerformSync() e transmitir dados para ele.

Instanciar o componente adaptador de sincronização como um singleton no onCreate() do serviço. Ao instanciar o componente em onCreate(), você adia criando-o até que o serviço seja iniciado, o que acontece quando o framework tenta executar pela primeira vez transferência de dados. Você precisa instanciar o componente de maneira segura para a linha de execução, caso a sincronização o framework do adaptador coloca várias execuções em fila em resposta a gatilhos ou programação.

Por exemplo, o snippet a seguir mostra como criar uma classe que implementa o vinculado ao Service, instancia o componente do adaptador de sincronização e recebe a Objeto de vinculação do Android:

Kotlin

package com.example.android.syncadapter
/**
 * Define a Service that returns an [android.os.IBinder] for the
 * sync adapter class, allowing the sync adapter framework to call
 * onPerformSync().
 */
class SyncService : Service() {
    /*
     * Instantiate the sync adapter object.
     */
    override fun onCreate() {
        /*
         * Create the sync adapter as a singleton.
         * Set the sync adapter as syncable
         * Disallow parallel syncs
         */
        synchronized(sSyncAdapterLock) {
            sSyncAdapter = sSyncAdapter ?: SyncAdapter(applicationContext, true)
        }
    }

    /**
     * Return an object that allows the system to invoke
     * the sync adapter.
     *
     */
    override fun onBind(intent: Intent): IBinder {
        /*
         * Get the object that allows external processes
         * to call onPerformSync(). The object is created
         * in the base class code when the SyncAdapter
         * constructors call super()
         *
         * We should never be in a position where this is called before
         * onCreate() so the exception should never be thrown
         */
        return sSyncAdapter?.syncAdapterBinder ?: throw IllegalStateException()
    }

    companion object {
        // Storage for an instance of the sync adapter
        private var sSyncAdapter: SyncAdapter? = null
        // Object to use as a thread-safe lock
        private val sSyncAdapterLock = Any()
    }
}

Java

package com.example.android.syncadapter;
/**
 * Define a Service that returns an <code><a href="/reference/android/os/IBinder.html">IBinder</a></code> for the
 * sync adapter class, allowing the sync adapter framework to call
 * onPerformSync().
 */
public class SyncService extends Service {
    // Storage for an instance of the sync adapter
    private static SyncAdapter sSyncAdapter = null;
    // Object to use as a thread-safe lock
    private static final Object sSyncAdapterLock = new Object();
    /*
     * Instantiate the sync adapter object.
     */
    @Override
    public void onCreate() {
        /*
         * Create the sync adapter as a singleton.
         * Set the sync adapter as syncable
         * Disallow parallel syncs
         */
        synchronized (sSyncAdapterLock) {
            if (sSyncAdapter == null) {
                sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
            }
        }
    }
    /**
     * Return an object that allows the system to invoke
     * the sync adapter.
     *
     */
    @Override
    public IBinder onBind(Intent intent) {
        /*
         * Get the object that allows external processes
         * to call onPerformSync(). The object is created
         * in the base class code when the SyncAdapter
         * constructors call super()
         */
        return sSyncAdapter.getSyncAdapterBinder();
    }
}

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 Adicione o arquivo de metadados do Authenticator. Agora você precisa configurar esse tipo de conta no sistema Android. Para configurar o tipo de conta, adicione uma conta de marcador que use o tipo de conta chame addAccountExplicitly().

O melhor lugar para chamar o método é no onCreate() do método atividade de abertura. 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 = "placeholderaccount"
...
class MainActivity : FragmentActivity() {

    // Instance fields
    private lateinit var mAccount: Account
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       ...
        // Create the placeholder account
        mAccount = createSyncAccount()
       ...
    }
    ...
    /**
     * Create a new placeholder account for the sync adapter
     */
    private fun createSyncAccount(): Account {
        val accountManager = getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
        return Account(ACCOUNT, ACCOUNT_TYPE).also { newAccount ->
            /*
             * Add the account and account type, no password or user data
             * If successful, return the Account object, otherwise report an error.
             */
            if (accountManager.addAccountExplicitly(newAccount, null, null)) {
                /*
                 * If you don't set android:syncable="true" in
                 * in your <provider> element in the manifest,
                 * then call context.setIsSyncable(account, AUTHORITY, 1)
                 * here.
                 */
            } else {
                /*
                 * The account exists or some other error occurred. Log this, report it,
                 * or handle it internally.
                 */
            }
        }
    }
    ...
}

Java

public class MainActivity extends FragmentActivity {
    ...
    ...
    // Constants
    // The authority for the sync adapter's content provider
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // An account type, in the form of a domain name
    public static final String ACCOUNT_TYPE = "example.com";
    // The account name
    public static final String ACCOUNT = "placeholderaccount";
    // Instance fields
    Account mAccount;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Create the placeholder account
        mAccount = CreateSyncAccount(this);
        ...
    }
    ...
    /**
     * Create a new placeholder account for the sync adapter
     *
     * @param context The application context
     */
    public static Account CreateSyncAccount(Context context) {
        // Create the account type and default account
        Account newAccount = new Account(
                ACCOUNT, ACCOUNT_TYPE);
        // Get an instance of the Android account manager
        AccountManager accountManager =
                (AccountManager) context.getSystemService(
                        ACCOUNT_SERVICE);
        /*
         * Add the account and account type, no password or user data
         * If successful, return the Account object, otherwise report an error.
         */
        if (accountManager.addAccountExplicitly(newAccount, null, null)) {
            /*
             * If you don't set android:syncable="true" in
             * in your <provider> element in the manifest,
             * then call context.setIsSyncable(account, AUTHORITY, 1)
             * here.
             */
        } else {
            /*
             * The account exists or some other error occurred. Log this, report it,
             * or handle it internally.
             */
        }
    }
    ...
}

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

Para conectar o componente do adaptador de sincronização ao framework, é necessário fornecer o framework com metadados que descrevem o componente e fornecem sinalizações adicionais. Os metadados especificam o tipo de conta que você criou para o adaptador de sincronização, declara uma autoridade do provedor de conteúdo associada ao seu aplicativo, controla uma parte da interface de usuário do sistema relacionada a adaptadores de sincronização, e declara outras flags relacionadas à sincronização. Declare esses metadados em um arquivo XML especial armazenado em no diretório /res/xml/ no projeto do app. Você pode dar qualquer nome ao arquivo, embora seja geralmente chamado de syncadapter.xml.

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

android:contentAuthority
A autoridade de URI para seu provedor de conteúdo. Se você criou um provedor de conteúdo stub para seu app na lição anterior, Como criar um provedor de conteúdo stub, use o valor especificado para o atributo android:authorities no elemento <provider> adicionado ao manifesto do app. Esse atributo é descritos em 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, deve ser o mesmo da autoridade de URI de conteúdo usada para esses dados. Esse valor também é uma das autoridades especificadas na android:authorities atributo 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 como o valor do tipo de conta fornecido quando você criou o arquivo de metadados do autenticador, conforme descritos na seção Adicionar o arquivo de metadados do autenticador. É também o valor especificado para o valor constante ACCOUNT_TYPE no snippet de código da seção Adicione a conta exigida pela Estrutura.
Atributos de configuração
android:userVisible
Define a visibilidade do tipo de conta do adaptador de sincronização. Por padrão, o o ícone e o rótulo associados ao tipo de conta ficam visíveis no Contas do app Configurações do sistema. Por isso, faça a sincronização. adaptador invisível, a menos que você tenha um tipo de conta ou domínio que seja facilmente associado com seu app. Se você tornar seu tipo de conta invisível, ainda poderá permitir que os usuários controlar o adaptador de sincronização com uma interface do usuário em uma das atividades do app.
android:supportsUploading
Permite fazer upload de dados para a nuvem. Defina como false se apenas seu app de downloads.
android:allowParallelSyncs
Permite que várias instâncias do adaptador de sincronização sejam executadas ao mesmo tempo. Use esta opção se o app for compatível com várias contas de usuário e você quiser permitir várias transferir 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 em qualquer o horário especificado. Se você quiser controlar programaticamente quando a sincronização adaptador pode ser executado, defina essa flag como false e chame requestSync() para executar adaptador de sincronização. Para saber mais sobre como executar um adaptador de sincronização, consulte a lição Executar um adaptador de sincronização

O exemplo a seguir mostra o XML de um adaptador de sincronização que usa uma única conta de marcador de posição e só faz 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, é necessário solicitar permissões relacionadas ao uso do componente, e é necessário declarar a propriedade Service vinculada que você adicionou.

Como o componente adaptador de sincronização executa um código que transfere dados entre a rede e a dispositivo, será necessário solicitar permissão para acessar a Internet. Além disso, seu app precisa para solicitar permissão de leitura e gravação das configurações do adaptador de sincronização, para controlar a sincronização de forma programática com outros componentes do app. Você também precisa solicitar uma permissão especial que autoriza o app a usar o componente autenticador que você criou na lição 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 upload de dados do dispositivo para um servidor. Não é necessário adicionar essa permissão novamente caso você tenha sido antes de tê-lo solicitado.
android.permission.READ_SYNC_SETTINGS
Permite que o app leia as configurações atuais do adaptador de sincronização. Por exemplo, você precisa que permissão para chamar getIsSyncable().
android.permission.WRITE_SYNC_SETTINGS
Permite que o app controle as configurações do adaptador de sincronização. Você precisa dessa permissão para o adaptador de sincronização periódico definido é executado usando addPeriodicSync(). Essa permissão não é necessária para chamar requestSync() Para saber mais sobre use o adaptador de sincronização em 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>

Por fim, 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="false"
                android:process=":sync">
            <intent-filter>
                <action android:name="android.content.SyncAdapter"/>
            </intent-filter>
            <meta-data android:name="android.content.SyncAdapter"
                    android:resource="@xml/syncadapter" />
        </service>

A <intent-filter> define um filtro que é acionado pela ação da intent android.content.SyncAdapter, enviada pelo sistema para executar o adaptador de sincronização. Quando o filtro for acionado, o sistema iniciará o serviço vinculado que você criou, que neste exemplo é SyncService: O atributo android:exported="false" permite que apenas seu app e o sistema acessem o Service. O atributo android:process=":sync" instrui o sistema a executar a Service em um processo global compartilhado chamado sync Se você tiver vários adaptadores de sincronização no app, eles podem compartilhar esse processo, o que reduz a sobrecarga.

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

Agora você tem todos os componentes para seu adaptador de sincronização. A próxima aula mostra como instruirá o framework do adaptador de sincronização a executá-lo, seja em resposta a um evento ou em dentro de um cronograma regular.