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 funciona melhor para você.
O componente do adaptador de sincronização no 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, é preciso 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 sua classe do 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 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 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, definir construtores para a classe 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
AbstractThreadedSyncAdapter
e criando os construtores dele. Use os
construtores para executar tarefas de configuração sempre que o componente do adaptador de sincronização 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 receber uma instância de ContentResolver
. Como uma segunda forma do construtor foi adicionada à versão 3.0 da plataforma Android para oferecer suporte ao 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 instanciação do componente do adaptador de sincronização é abordada com mais detalhes na seção Vincular o adaptador de sincronização ao framework.
O exemplo abaixo 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 possa executar a transferência de dados em segundo plano, sem o envolvimento do app. Quando o framework está pronto para sincronizar os dados do 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 os
seguintes argumentos:
- Conta
-
Um objeto
Account
associado ao evento que acionou o adaptador de sincronização. Se o 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. Seu 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
-
Um
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 funcionalidade básica de umContentResolver
. Se você estiver usando um provedor de conteúdo para armazenar dados do seu app, poderá se conectar ao provedor com esse objeto. Caso contrário, ignore-o. - Resultado da sincronização
-
Um objeto
SyncResult
usado 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 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 presumir 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. Se você quiser fazer o download de dados de um servidor e armazená-los em um provedor de conteúdo, forneça o código que solicita esses dados, faça o download deles e os insira no provedor. Da mesma forma, se você quiser enviar dados a um servidor, será preciso lê-los 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 enquanto 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. Em vez disso, é necessário fornecer seus próprios algoritmos para lidar com essa situação.
- Limpeza
- Sempre encerre as 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. Assim, você não precisa configurar seu processamento em segundo plano.
Além das tarefas relacionadas à sincronização, tente 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 Como transferir 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 precisa fornecer ao framework acesso ao código. Para fazer isso, você precisa criar um Service
vinculado que transmita um objeto binder especial do 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 em onCreate()
, você adia
a criação até que o serviço seja iniciado, o que acontece quando o framework tenta executar a
transferência de dados pela primeira vez. Você precisa instanciar o componente de maneira segura para as linhas de execução, caso o framework do adaptador de sincronização enfileire várias execuções em resposta a gatilhos ou em programação.
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 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 chamando addAccountExplicitly()
.
O melhor lugar para chamar o método é no 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 = "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 ao framework metadados que descrevam o componente e forneçam outras sinalizações. 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 app, controla uma parte da interface do usuário do sistema relacionada aos adaptadores de sincronização e declara 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 de syncadapter.xml
.
Esse arquivo XML contém um único elemento XML <sync-adapter>
que tem os 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 é 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 que você especifica no atributoandroid:authorities
do elemento<provider>
que declara seu 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 fornecido ao criar 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 o rótulo associados ao tipo de conta ficam visíveis na seção Contas do app Configurações do sistema. Portanto, torne o adaptador de sincronização invisível, a menos que você tenha um tipo de conta ou domínio facilmente associado ao seu app. Se você tornar o tipo de conta invisível, ainda será possível 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 fazer upload de dados para a nuvem. Defina como
false
se o app só fizer o download de dados. -
android:allowParallelSyncs
- Permite que várias instâncias do componente 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 especificado. Se você quiser controlar programaticamente quando o adaptador de sincronização pode ser executado, defina essa sinalização como
false
e, em seguida, chamerequestSync()
para executá-lo. 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, você precisa solicitar permissões relacionadas ao uso do componente e declarar o Service
vinculado que foi adicionado.
Como o componente do adaptador de sincronização executa um código que transfere dados entre a rede e o dispositivo, é necessário 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. Assim, você pode controlá-lo programaticamente a partir de outros componentes no app. Você também precisa solicitar uma permissão especial que permita que o app use o componente do autenticador criado 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 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 o app controle as configurações do adaptador de sincronização. Você precisa dessa permissão para
definir a execução periódica do adaptador de sincronização usando
addPeriodicSync()
. Essa permissão não é necessária para chamarrequestSync()
. Para saber mais sobre como executar o adaptador de sincronização, consulte Como 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>
O elemento
<intent-filter>
configura um filtro que é acionado pela ação da intent
android.content.SyncAdapter
, enviado 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="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 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 para seu adaptador de sincronização. A próxima lição mostra como instruir o framework do adaptador de sincronização a executá-lo em resposta a um evento ou em uma programação regular.