Observação: recomendamos o 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 o 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 classe do 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.
- Um 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 do adaptador de sincronização, comece estendendo o AbstractThreadedSyncAdapter
e criando os construtores dele. Use os construtores para executar tarefas de configuração sempre que o componente do adaptador de sincronização correspondente for criado do zero, da mesma forma que você usa Activity.onCreate()
para configurar uma atividade. Por exemplo, se o app usa um provedor de conteúdo para armazenar dados, use os construtores para ter uma instância de ContentResolver
. Já que uma segunda forma do construtor foi adicionada na versão 3.0 da Plataforma Android para oferecer compatibilidade com o argumento parallelSyncs
, é necessário 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 singleton. A criação de instâncias para o componente do 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 os construtores dele:
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 o 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 os dados do app, 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 sincronização chama onPerformSync()
com os seguintes argumentos:
- Conta
-
Um objeto
Account
associado ao 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 app.
- Cliente do provedor de conteúdo
-
O
ContentProviderClient
do provedor de conteúdo apontado pelo argumento de autoridade. OContentProviderClient
é uma interface pública leve para um provedor de conteúdo. Ele tem a mesma função básica de umContentResolver
. 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 de onPerformSync()
seja específica para os 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 estará 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 deles e insira-os no provedor. Da mesma forma, para enviar dados para um servidor, é necessário ler os dados de um arquivo, banco de dados ou provedor e enviar a solicitação de upload necessária. Também é preciso lidar com erros de rede que ocorrem durante a transferência de dados.
- 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 armazenados em cache ao 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 precise configurar o processamento em segundo plano.
Além das tarefas relacionadas à sincronização, tente combinar as tarefas normais relacionadas à rede e adicioná-las ao 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 o 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 do adaptador de sincronização, mas é necessário que o acesso ao código seja fornecido ao framework. Para fazer isso, é preciso criar um Service
vinculado que transmita um objeto binder especial Android do componente do adaptador de sincronização para o framework. Com esse objeto binder, o framework pode invocar o método onPerformSync()
e transmitir dados para ele.
Instancie o componente do adaptador de sincronização como um singleton no método onCreate()
do serviço. Ao instanciar o componente no onCreate()
, a criação é adiada 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ções.
Por exemplo, o snippet a seguir mostra como criar uma classe que implementa o Service
vinculado, instancia o componente do adaptador de sincronização e recebe o objeto binder 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 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 chamando addAccountExplicitly()
.
O melhor lugar para chamar o método é o método onCreate()
da atividade de abertura do 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. É possível dar qualquer nome ao arquivo, embora ele geralmente seja chamado de syncadapter.xml
.
Este arquivo XML contém um único elemento XML <sync-adapter>
, que tem os seguintes atributos:
android:contentAuthority
-
A autoridade de URI para o provedor de conteúdo. Se você criou um provedor de conteúdo stub para o app na lição anterior sobre 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 é descrito 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, esse valor precisará ser o mesmo da autoridade de URI de conteúdo usada para esses dados. Esse valor também é uma das autoridades que você especifica no atributoandroid: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 igual ao valor do tipo de conta fornecido ao criar o arquivo de metadados do autenticador, conforme descrito na seção Adicionar o arquivo de metadados do autenticador. Esse também é o valor especificado para o
ACCOUNT_TYPE
constante 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 o rótulo associados ao tipo de conta ficam visíveis na seção Contas do app Config. do sistema. Dessa forma, é preciso deixar o adaptador de sincronização invisível, a menos que tenha um tipo de conta ou domínio fácil de ser associado ao app. Se o tipo de conta ficar invisível, ainda será possível permitir que os usuários controlem o adaptador de sincronização por meio de 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 o download 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 o adaptador pode ser executado a qualquer momento definido por você. Para controlar programaticamente quando o adaptador de sincronização pode ser executado, defina essa sinalização como
false
e chamerequestSync()
para executar o adaptador de sincronização. Para saber mais sobre o funcionamento de 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 fictícia 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 com o uso do componente e declarar o Service
vinculado que você 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, o app precisa solicitar permissão para ler e gravar as configurações do adaptador de sincronização para que você possa controlar programaticamente o adaptador de sincronização a partir de outros componentes no app. Também é necessário solicitar uma permissão especial que autorize o app a usar o componente de autenticação criado na lição sobre como Criar um autenticador de stubs.
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 se você a solicitou antes.
-
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 o app controle as configurações do adaptador de sincronização. Essa permissão é necessária para definir as execuções periódicas do adaptador de sincronização usando
addPeriodicSync()
. Essa permissão não é necessária para chamarrequestSync()
. Para saber mais sobre a execução 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>
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="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 da 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 criado, que nesse exemplo é SyncService
. O atributo android:exported="true"
permite que outros processos além do app (incluindo o sistema) acessem o Service
. O atributo android:process=":sync"
diz ao sistema para executar o Service
em um processo global compartilhado 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 para 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 para seu adaptador de sincronização. A próxima lição mostra como informar o framework do adaptador de sincronização para executar o adaptador, seja em resposta a um evento ou em uma programação regular.