Por que ContentResolver.requestSync não dispara uma sincronização?

112

Estou tentando implementar o padrão Content-Provider-Sync Adapter, conforme discutido no Google IO - slide 26. Meu provedor de conteúdo está funcionando e minha sincronização funciona quando eu a aciono no aplicativo Dev Tools Sync Tester, no entanto, quando chamo ContentResolver. requestSync (conta, autoridade, pacote) de meu ContentProvider, minha sincronização nunca é acionada.

ContentResolver.requestSync(
        account, 
        AUTHORITY, 
        new Bundle());

Editar - snippet de manifesto adicionado Meu manifesto xml contém:

<service
    android:name=".sync.SyncService"
    android:exported="true">
    <intent-filter>
        <action
            android:name="android.content.SyncAdapter" />
    </intent-filter>
    <meta-data android:name="android.content.SyncAdapter"
    android:resource="@xml/syncadapter" />
</service>

--Editar

Meu syncadapter.xml associado ao meu serviço de sincronização contém:

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"  
    android:contentAuthority="AUTHORITY"
    android:accountType="myaccounttype"
    android:supportsUploading="true"
/>

Não tenho certeza de que outro código seria útil. A conta passada para requestSync é "myaccounttype" e a AUTHORITY passada para a chamada corresponde ao meu xml do adaptador syc.

ContentResolver.requestSync é a maneira correta de solicitar uma sincronização? Parece que a ferramenta do testador de sincronização se liga diretamente ao serviço e chama o início da sincronização, mas parece que isso anula o propósito de integração com a arquitetura de sincronização.

Se essa for a maneira correta de solicitar uma sincronização, por que o testador de sincronização funcionaria, mas não minha chamada para ContentResolver.requestSync? Há algo que preciso passar no pacote?

Estou testando no emulador em dispositivos rodando 2.1 e 2.2.

Ben
fonte
3
Meu problema era que meu ponto de interrupção no adaptador de sincronização não estava sendo alcançado ... Então percebi que estava tentando depurar um serviço ... Espero que isso ajude outras pessoas como eu.
dangalg
2
Os pontos de interrupção no serviço do adaptador de sincronização FALHARÃO no acionamento. Isso ocorre porque o serviço do adaptador de sincronização é executado em um processo separado. Isso é o que @danglang estava sugerindo. Veja também esta pergunta: stackoverflow.com/questions/8559458/…
Konstantin Schubert
1
No meu caso, a remoção android:process=":sync"do serviço de sincronização permite que o depurador acerte os pontos críticos. O serviço de sincronização em si estava funcionando antes disso, porque eu podia ver as mensagens de log do onPerformSyncmétodo em nome de outro processo.
Sergey,
Outra causa é o WiFi desligado, então se você estiver tentando sincronizar com dados simulados, verifique sua conexão.
Allan Veloso

Respostas:

280

A chamada requestSync()só funcionará em um par {Account, ContentAuthority} conhecido pelo sistema. Seu aplicativo precisa passar por uma série de etapas para informar ao Android que você é capaz de sincronizar um tipo específico de conteúdo usando um tipo específico de conta. Ele faz isso no AndroidManifest.

1. Notifique o Android de que o pacote do seu aplicativo fornece sincronização

Em primeiro lugar, no AndroidManifest.xml, você deve declarar que possui um serviço de sincronização:

<service android:name=".sync.mySyncService" android:exported="true">
   <intent-filter>
      <action android:name="android.content.SyncAdapter" /> 
    </intent-filter>
    <meta-data 
        android:name="android.content.SyncAdapter" 
        android:resource="@xml/sync_myapp" /> 
</service>

O atributo name da <service>tag é o nome da sua classe para conectar a sincronização ... Falarei sobre isso em um segundo.

Definir exportado como verdadeiro o torna visível para outros componentes (necessário para ContentResolverpoder chamá-lo).

O filtro de intent permite capturar uma sincronização de solicitação de intent. (Isso Intentvem ContentResolverquando você liga ContentResolver.requestSync()ou métodos de agendamento relacionados).

A <meta-data>tag será discutida abaixo.

2. Fornece ao Android um serviço usado para encontrar seu SyncAdapter

Portanto, a própria classe ... Aqui está um exemplo:

public class mySyncService extends Service {

    private static mySyncAdapter mSyncAdapter = null;

    public SyncService() {
        super();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (mSyncAdapter == null) {
            mSyncAdapter = new mySyncAdapter(getApplicationContext(), true);
        }
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return mSyncAdapter.getSyncAdapterBinder();
    }
}

Sua classe deve estender Serviceou uma de suas subclasses, deve implementar public IBinder onBind(Intent)e deve retornar um SyncAdapterBinderquando for chamado ... Você precisa de uma variável do tipo AbstractThreadedSyncAdapter. Como você pode ver, isso é praticamente tudo nessa classe. A única razão pela qual está lá é para fornecer um serviço, que oferece uma interface padrão para Android para consultar sua classe sobre o que você SyncAdapteré.

3. Forneça um class SyncAdapterpara realmente executar a sincronização.

mySyncAdapter é onde a própria lógica de sincronização real é armazenada. Seu onPerformSync()método é chamado quando é hora de sincronizar. Eu acho que você já tem isso no lugar.

4. Estabeleça uma ligação entre um tipo de conta e uma autoridade de conteúdo

Olhando novamente para AndroidManifest, aquela <meta-data>tag estranha em nosso serviço é a peça-chave que estabelece a ligação entre um ContentAuthority e uma conta. Ele faz referência externa a outro arquivo xml (chame-o do que quiser, algo relevante para o seu aplicativo). Vejamos sync_myapp.xml:

<?xml version="1.0" encoding="utf-8" ?> 
<sync-adapter 
    xmlns:android="http://schemas.android.com/apk/res/android"   
    android:contentAuthority="com.android.contacts"
    android:accountType="com.google" 
    android:userVisible="true" /> 

Ok, então o que isso faz? Diz ao Android que o adaptador de sincronização que definimos (a classe que foi chamada no elemento de nome da <service>tag que inclui a <meta-data>tag que faz referência a este arquivo ...) sincronizará os contatos usando uma conta do estilo com.google.

Todas as suas strings contentAuthority devem corresponder e combinar com o que você está sincronizando - Deve ser uma string que você define, se estiver criando seu próprio banco de dados, ou deve usar algumas strings de dispositivo existentes se estiver sincronizando conhecido tipos de dados (como contatos ou eventos de calendário ou o que for.) O acima ("com.android.contacts") passa a ser a string ContentAuthority para dados de tipo de contato (surpresa, surpresa.)

accountType também deve corresponder a um daqueles tipos de conta conhecidos que já foram inseridos, ou deve corresponder a um que você está criando (isso envolve a criação de uma subclasse de AccountAuthenticator para obter autenticação em seu servidor ... Vale um artigo, por si só.) Novamente, "com.google" é a string definida que identifica ... credenciais da conta no estilo google.com (novamente, isso não deve ser uma surpresa).

5. Ative a sincronização em um determinado par de conta / autoridade de conteúdo

Finalmente, a sincronização deve ser ativada. Você pode fazer isso na página Contas e sincronização no painel de controle acessando seu aplicativo e marcando a caixa de seleção ao lado de seu aplicativo na conta correspondente. Como alternativa, você pode fazer isso em algum código de configuração em seu aplicativo:

ContentResolver.setSyncAutomatically(account, AUTHORITY, true);

Para que a sincronização ocorra, seu par conta / autoridade deve estar habilitado para sincronizar (como acima) e o sinalizador de sincronização global geral no sistema deve ser definido, e o dispositivo deve ter conectividade de rede.

Se sua sincronização de conta / autoridade ou a sincronização global estiverem desabilitadas, chamar RequestSync () tem um efeito - define um sinalizador de que a sincronização foi solicitada e será executada assim que a sincronização for habilitada.

Além disso, por mgv , definir ContentResolver.SYNC_EXTRAS_MANUALcomo verdadeiro no pacote de extras do seu requestSync pedirá ao Android para forçar uma sincronização, mesmo se a sincronização global estiver desligada (seja respeitoso com seu usuário aqui!)

Finalmente, você pode configurar uma sincronização agendada periódica, novamente com funções ContentResolver.

6. Considere as implicações de várias contas

É possível ter mais de uma conta do mesmo tipo (duas contas @ gmail.com configuradas em um dispositivo ou duas contas do Facebook, ou duas contas do Twitter, etc ...). Você deve considerar as implicações do aplicativo ao fazer isso. .. Se você tem duas contas, provavelmente não deseja sincronizar as duas nas mesmas tabelas do banco de dados. Talvez você precise especificar que apenas um pode estar ativo por vez, esvaziar as tabelas e sincronizar novamente se você trocar de conta. (por meio de uma página de propriedade que consulta quais contas estão presentes). Talvez você crie um banco de dados diferente para cada conta, talvez tabelas diferentes, talvez uma coluna-chave em cada tabela. Todo aplicativo específico e digno de alguma reflexão. ContentResolver.setIsSyncable(Account account, String authority, int syncable)pode ser de interesse aqui. setSyncAutomatically()controla se um par de conta / autoridade é verificado oudesmarcado , enquanto setIsSyncable()fornece uma maneira de desmarcar e esmaecer a linha para que o usuário não possa ativá-la. Você pode definir uma conta sincronizável e a outra não sincronizável (desativada).

7. Esteja ciente de ContentResolver.notifyChange ()

Uma coisa complicada. ContentResolver.notifyChange()é uma função usada por ContentProviders para notificar o Android de que o banco de dados local foi alterado. Isso tem duas funções, primeiro, fará com que os cursores seguindo esse conteúdo uri sejam atualizados e, por sua vez, consulte novamente e invalide e redesenhe um ListView, etc ... É muito mágico, o banco de dados muda e você ListViewapenas atualiza automaticamente. Impressionante. Além disso, quando o banco de dados muda, o Android irá solicitar a sincronização para você, mesmo fora de sua programação normal, para que essas alterações sejam retiradas do dispositivo e sincronizadas com o servidor o mais rápido possível. Também incrível.

Porém, há um caso extremo. Se você puxar do servidor e enviar uma atualização para o ContentProvider, ele será devidamente chamado notifyChange()e o Android dirá: "Oh, alterações no banco de dados, melhor colocá-las no servidor!" (Doh!) Bem escrito ContentProvidersfará alguns testes para ver se as mudanças vieram da rede ou do usuário, e definirá o syncToNetworksinalizador booleano como falso em caso afirmativo, para evitar esse desperdício de sincronização dupla. Se você estiver alimentando dados em um ContentProvider, cabe a você descobrir como fazer isso funcionar - caso contrário, você acabará sempre executando duas sincronizações quando apenas uma for necessária.

8. Sinta-se feliz!

Depois de ter todos esses metadados xml no lugar e a sincronização ativada, o Android saberá como conectar tudo para você e a sincronização deve começar a funcionar. Nesse ponto, muitas coisas legais simplesmente se encaixarão no lugar e parecerão mágica. Aproveitar!

Jcwenger
fonte
11
ContentResolver.setSyncAutomatically (conta, AUTHORITY, true);
jcwenger de
1
Sem problemas, fico feliz em poder ajudar. Novamente, do ponto de vista de estilo, se "AUTHORITY" e "myaccounttype" são as strings reais que você está usando (e não apenas amostras para copiar no site), você definitivamente precisa limpar suas convenções de nomenclatura. Essas strings devem ser únicas em todo o dispositivo, e você terá problemas reais se algum outro programador preguiçosamente fizer um pacote com uma string correspondente para autoridade e você obter um conflito. Felicidades!
jcwenger de
22
Você pode solicitar uma sincronização mesmo que as configurações globais de sincronização estejam desativadas. Basta adicionar um ContentResolver.SYNC_EXTRAS_MANUALconjunto para verdadeiro ao pacote de extras e você forçará a sincronização :)
mgv
2
@kaciula: Não sei de nenhum, mas o dispositivo VAI se lembrar que precisa ser sincronizado e será iniciado assim que a sincronização global for ativada. Você realmente não deve tentar enganar o usuário nisso - Especialmente porque "Sincronização global desativada" é uma das principais maneiras de economizar bateria em situações críticas. Se você estiver realmente preocupado com os dados não sendo sincronizados, considere um pop-up que informa ao usuário POR QUE os dados não estão se movendo, se estiver parado por um tempo. Dessa forma, você pode educar os usuários que acidentalmente configuraram incorretamente seus dispositivos e lembrar os usuários avançados caso eles tenham esquecido.
jcwenger de
2
Pode valer a pena acrescentar que se você quiser usar addPeriodicSync (), SÓ parece funcionar se você TAMBÉM setSyncAutomatically () - eu adicionei isso desesperado, tentando fazer ALGUMA COISA funcionar. Eu sei que não fazia parte da pergunta original, mas esta é uma resposta completa!
android.weasel
0

Eu estava ligando setIsSyncabledepois do setAuthTokenmétodo AccountManager . Mas a setAuthTokenfunção retornada antes setIsSyncablefoi alcançada. Depois que o pedido mudou, tudo funcionou bem!

Andris
fonte