¿Por qué ContentResolver.requestSync no activa una sincronización?

112

Estoy tratando de implementar el patrón Content-Provider-Sync Adapter como se discutió en Google IO - diapositiva 26. Mi proveedor de contenido está funcionando y mi sincronización funciona cuando lo activo desde la aplicación Dev Tools Sync Tester, sin embargo, cuando llamo a ContentResolver. requestSync (cuenta, autoridad, paquete) de mi ContentProvider, mi sincronización nunca se activa.

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

Editar: fragmento de manifiesto agregado Mi archivo XML de manifiesto contiene:

<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

Mi syncadapter.xml asociado con mi servicio de sincronización contiene:

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

No estoy seguro de qué otro código sería útil. La cuenta pasada a requestSync es de "myaccounttype" y la AUTORIDAD pasada a la llamada coincide con mi adaptador syc xml.

¿ContentResolver.requestSync es la forma correcta de solicitar una sincronización? Parece que la herramienta de prueba de sincronización se vincula directamente con el servicio y las llamadas comienzan a sincronizarse, pero parece que anula el propósito de la integración con la arquitectura de sincronización.

Si esa es la forma correcta de solicitar una sincronización, ¿por qué funcionaría el probador de sincronización, pero no mi llamada a ContentResolver.requestSync? ¿Hay algo que deba pasar en el paquete?

Estoy probando en el emulador en dispositivos que ejecutan 2.1 y 2.2.

Ben
fuente
3
Mi problema era que no se alcanzaba mi punto de interrupción en el adaptador de sincronización ... Entonces me di cuenta de que estaba intentando depurar un servicio ... Espero que esto ayude a otros como yo.
dangalg
2
Los puntos de interrupción en el servicio del adaptador de sincronización NO se activarán. Eso es porque el servicio del adaptador de sincronización se ejecuta en un proceso separado. Esto es lo que @danglang estaba insinuando. Consulte también esta pregunta: stackoverflow.com/questions/8559458/…
Konstantin Schubert
1
En mi caso, eliminar android:process=":sync"del servicio de sincronización permitió que el depurador golpeara los puntos pico. El servicio de sincronización en sí estaba funcionando antes de eso, porque podía ver los mensajes de registro del onPerformSyncmétodo en nombre de otro proceso.
Sergey
Otra causa es el WiFi desactivado, por lo que si está intentando sincronizar con datos simulados, verifique su conexión.
Allan Veloso

Respuestas:

280

Las llamadas requestSync()solo funcionarán en un par {Account, ContentAuthority} que sea conocido por el sistema. Tu aplicación debe seguir una serie de pasos para indicarle a Android que eres capaz de sincronizar un tipo específico de contenido usando un tipo específico de cuenta. Hace esto en el AndroidManifest.

1. Notifique a Android que el paquete de su aplicación proporciona sincronización

En primer lugar, en AndroidManifest.xml, debe declarar que tiene un Servicio de sincronización:

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

El atributo de nombre de la <service>etiqueta es el nombre de su clase para conectar la sincronización ... Hablaré de eso en un segundo.

Establecer exported true lo hace visible para otros componentes (necesario para ContentResolverpoder llamarlo).

El filtro de intención le permite detectar una intención que solicita sincronización. (Esto Intentproviene de ContentResolvercuando llama ContentResolver.requestSync()o de métodos de programación relacionados).

La <meta-data>etiqueta se discutirá a continuación.

2. Proporcione a Android un servicio utilizado para encontrar su SyncAdapter

Entonces, la clase en sí ... Aquí hay un ejemplo:

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

Su clase debe extender Serviceo una de sus subclases, debe implementar public IBinder onBind(Intent)y debe devolver un SyncAdapterBindercuando se llama ... Necesita una variable de tipo AbstractThreadedSyncAdapter. Como puede ver, eso es prácticamente todo en esa clase. La única razón por la que está ahí es para proporcionar un Servicio, que ofrece una interfaz estándar para Android para consultar a su clase sobre lo que es usted SyncAdaptermismo.

3. Proporcione una class SyncAdapterpara realizar la sincronización.

mySyncAdapter es donde se almacena la lógica de sincronización real. Se onPerformSync()llama a su método cuando llega el momento de sincronizar. Supongo que ya tiene esto en su lugar.

4. Establecer un vínculo entre un tipo de cuenta y una autoridad de contenido.

Mirando hacia atrás de nuevo en AndroidManifest, esa extraña <meta-data>etiqueta en nuestro servicio es la pieza clave que establece el vínculo entre ContentAuthority y una cuenta. Hace referencia externamente a otro archivo xml (llámelo como quiera, algo relevante para su aplicación). Veamos 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" /> 

Bien, entonces, ¿qué hace esto? Le dice a Android que el adaptador de sincronización que hemos definido (la clase que se llamó en el elemento de nombre de la <service>etiqueta que incluye la <meta-data>etiqueta que hace referencia a este archivo ...) sincronizará los contactos usando una cuenta de estilo com.google.

Todas las cadenas de contenido de la autoridad deben coincidir y coincidir con lo que está sincronizando: esta debe ser una cadena que defina, si está creando su propia base de datos, o debe usar algunas cadenas de dispositivos existentes si está sincronizando. tipos de datos (como contactos o eventos de calendario o lo que sea). Lo anterior ("com.android.contacts") resulta ser la cadena ContentAuthority para los datos del tipo de contactos (sorpresa, sorpresa).

accountType también tiene que coincidir con uno de esos tipos de cuenta conocidos que ya están ingresados, o tiene que coincidir con uno que está creando (esto implica crear una subclase de AccountAuthenticator para obtener la autenticación en su servidor ... Vale la pena un artículo en sí). Nuevamente, "com.google" es la cadena definida que identifica ... las credenciales de la cuenta de estilo google.com (nuevamente, esto no debería ser una sorpresa).

5. Habilite la sincronización en un par de cuenta / autoridad de contenido determinado

Finalmente, la sincronización debe estar habilitada. Puede hacer esto en la página Cuentas y sincronización en el panel de control yendo a su aplicación y configurando la casilla de verificación junto a su aplicación dentro de la cuenta correspondiente. Alternativamente, puede hacerlo en algún código de configuración en su aplicación:

ContentResolver.setSyncAutomatically(account, AUTHORITY, true);

Para que se produzca la sincronización, su par de cuenta / autoridad debe estar habilitado para sincronizar (como arriba) y la marca de sincronización global general en el sistema debe estar configurada, y el dispositivo debe tener conectividad de red.

Si la sincronización de su cuenta / autoridad o la sincronización global están deshabilitadas, llamar a RequestSync () tiene un efecto: establece una marca de que se ha solicitado la sincronización y se realizará tan pronto como se habilite la sincronización.

Además, por mgv , si se establece ContentResolver.SYNC_EXTRAS_MANUALen verdadero en el paquete de extras de su solicitud , Sync le pedirá a Android que fuerce una sincronización incluso si la sincronización global está desactivada (¡sea respetuoso con su usuario aquí!)

Finalmente, puede configurar una sincronización programada periódica, nuevamente con las funciones de ContentResolver.

6. Considere las implicaciones de múltiples cuentas

Es posible tener más de una cuenta del mismo tipo (dos cuentas @ gmail.com configuradas en un dispositivo o dos cuentas de Facebook, o dos cuentas de Twitter, etc.). Debe considerar las implicaciones de aplicación de hacerlo. .. Si tiene dos cuentas, probablemente no quiera intentar sincronizar ambas en las mismas tablas de la base de datos. Tal vez necesite especificar que solo uno puede estar activo a la vez, y vaciar las tablas y resincronizar si cambia de cuenta. (a través de una página de propiedades que consulta qué cuentas están presentes). Tal vez cree una base de datos diferente para cada cuenta, tal vez tablas diferentes, tal vez una columna clave en cada tabla. Toda aplicación específica y digna de reflexión. ContentResolver.setIsSyncable(Account account, String authority, int syncable)podría ser de interés aquí. setSyncAutomatically()controla si un par de cuenta / autoridad está verificado odesmarcado , mientras que setIsSyncable()proporciona una forma de desmarcar y atenuar la línea para que el usuario no pueda activarla. Puede configurar una cuenta Syncable y la otra no Syncable (dsabled).

7. Tenga en cuenta ContentResolver.notifyChange ()

Una cosa complicada. ContentResolver.notifyChange()es una función utilizada por ContentProviders para notificar a Android que se ha cambiado la base de datos local. Esto tiene dos funciones, primero, hará que los cursores que siguen ese contenido uri se actualicen y, a su vez, vuelvan a consultar, invaliden y vuelvan a dibujar ListView, etc. Es muy mágico, la base de datos cambia y tu ListViewsolo se actualiza automáticamente. Increíble. Además, cuando la base de datos cambie, Android solicitará la sincronización por usted, incluso fuera de su horario normal, para que esos cambios se eliminen del dispositivo y se sincronicen con el servidor lo más rápido posible. También impresionante.

Sin embargo, hay un caso de borde. Si extrae del servidor e inserta una actualización en el ContentProvider, automáticamente llamará notifyChange()y Android dirá: "¡Oh, cambios en la base de datos, mejor colóquelos en el servidor!" (¡Doh!) Bien escrito ContentProviderstendrá algunas pruebas para ver si los cambios provienen de la red o del usuario, y establecerá la syncToNetworkbandera booleana como falsa si es así, para evitar esta doble sincronización innecesaria. Si está introduciendo datos en un ContentProvider, le conviene averiguar cómo hacer que esto funcione; de ​​lo contrario, siempre terminará realizando dos sincronizaciones cuando solo se necesita una.

8. ¡Siéntete feliz!

Una vez que tenga todos estos metadatos XML en su lugar y la sincronización habilitada, Android sabrá cómo conectar todo por usted, y la sincronización debería comenzar a funcionar. En este punto, muchas cosas que son agradables simplemente encajarán en su lugar y se sentirán como magia. ¡Disfrutar!

jcwenger
fuente
11
ContentResolver.setSyncAutomatically (cuenta, AUTORIDAD, verdadero);
jcwenger
1
No hay problema, me alegro de poder ayudar. Sin embargo, desde el punto de vista del estilo, si "AUTHORITY" y "myaccounttype" son las cadenas reales que está usando (y no solo muestras para copiar y pegar en el sitio), definitivamente necesita limpiar sus convenciones de nomenclatura. Esas cadenas tienen que ser únicas en todo el dispositivo, y se encontrará con un verdadero problema si algún otro programador crea un paquete con una cadena coincidente para la autoridad y se produce un conflicto. ¡Salud!
jcwenger
22
Puede solicitar una sincronización aunque la configuración de sincronización global esté desactivada. Simplemente agregue un ContentResolver.SYNC_EXTRAS_MANUALconjunto a fiel al paquete de extras y forzará la sincronización :)
mgv
2
@kaciula: No conozco ninguno, pero el dispositivo RECORDARÁ que necesita sincronizarse y lo iniciará tan pronto como se active la sincronización global. Realmente no deberías intentar vencer al usuario en este caso, especialmente porque la "sincronización global desactivada" es una de las formas clave de ahorrar batería en situaciones críticas. Si está realmente preocupado porque los datos no se sincronizan, considere una ventana emergente que le diga al usuario POR QUÉ los datos no se están moviendo, si ha estado inactivo por un tiempo. De esa manera, puede educar a los usuarios que hayan configurado mal accidentalmente sus dispositivos y recordarles a los usuarios avanzados en caso de que lo hayan olvidado.
jcwenger
2
Podría valer la pena agregar que si desea usar addPeriodicSync (), SOLO parece funcionar si TAMBIÉN estableceSyncAutomatically (); agregué eso por desesperación, tratando de que ALGO funcione. Sé que no era parte de la pregunta original, ¡pero esta es una respuesta tan completa!
android.weasel
0

Estaba llamando setIsSyncabledespués del setAuthTokenmétodo AccountManager . Pero se alcanzó la setAuthTokenfunción devuelta antes setIsSyncable. Después de los cambios de orden, ¡todo funcionó bien!

Andris
fuente