Ya está disponible la segunda Vista previa para desarrolladores de Android 11; pruébala y comparte tus comentarios.

Cómo crear un adaptador de sincronización

Nota: Recomendamos usar WorkManager como la solución más apropiada para la mayoría de los casos prácticos de procesamiento en segundo plano. Consulta la guía de procesamiento en segundo plano para conocer la solución que funcionará mejor en tu caso.

El componente del adaptador de sincronización de tu app encapsula el código para las tareas que transfieren datos entre el dispositivo y un servidor. Según la programación y los activadores que proporcionas en tu app, el marco de trabajo del adaptador de sincronización ejecuta el código en el componente del adaptador de sincronización. Para agregar un componente de adaptador de sincronización a tu app, debes agregar los siguientes elementos:

Clase de adaptador de sincronización
Una clase que une tu código de transferencia de datos en una interfaz compatible con el marco de trabajo del adaptador de sincronización.
Service vinculado
Un componente que permite que el marco de trabajo del adaptador de sincronización ejecute el código en la clase de tu adaptador de sincronización.
Archivo de metadatos XML del adaptador de sincronización
Un archivo que incluye información sobre tu adaptador de sincronización. El marco de trabajo lee este archivo para determinar cómo se debe cargar y programar la transferencia de datos.
Declaraciones en el manifiesto de la app
Un archivo XML en el que se declaran el servicio vinculado y puntos para sincronizar metadatos específicos del adaptador.

En esta lección, se muestra cómo definir estos elementos.

Cómo crear una clase de adaptador de sincronización

En esta parte de la lección, aprenderás a crear la clase de adaptador de sincronización que encapsula el código de transferencia de datos. La creación de la clase incluye extender la clase base del adaptador de sincronización, definir constructores para la clase e implementar el método en el que se definen las tareas de transferencia de datos.

Cómo extender la clase de adaptador de sincronización de base

Para crear el componente del adaptador de sincronización, extiende AbstractThreadedSyncAdapter y escribe sus constructores. Usa los constructores para ejecutar tareas de configuración cada vez que crees desde cero tu componente adaptador de sincronización, de la misma manera que usas Activity.onCreate() para configurar una actividad. Por ejemplo, si tu app usa un proveedor de contenido para almacenar datos, usa los constructores para obtener una instancia de ContentResolver. Debido a que se agregó una segunda forma del constructor en la versión 3.0 de la plataforma de Android para admitir el argumento parallelSyncs, debes crear dos formas del constructor para mantener la compatibilidad.

Nota: El marco de trabajo del adaptador de sincronización está diseñado para funcionar con componentes del adaptador de sincronización que son instancias de singleton. Se analizará con mayor detalles la creación de instancias del componente del adaptador de sincronización en la sección Cómo vincular el adaptador de sincronización con el marco de trabajo.

En el siguiente ejemplo, se muestra cómo implementar AbstractThreadedSyncAdapter y sus constructores:

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

Cómo agregar el código de transferencia de datos

El componente del adaptador de sincronización no realiza la transferencia de datos automáticamente. En cambio, encapsula tu código de transferencia de datos, de modo que el marco de trabajo del adaptador de sincronización pueda ejecutar la transferencia de datos en segundo plano, sin la participación de tu app. Cuando el marco de trabajo está listo para sincronizar los datos de tu app, invoca tu implementación del método onPerformSync().

Para facilitar la transferencia de datos desde el código principal de tu app al componente del adaptador de sincronización, el marco de trabajo del adaptador de sincronización llama a onPerformSync() con los siguientes argumentos:

Cuenta
Un objeto Account asociado con el evento que activó el adaptador de sincronización. Si tu servidor no usa cuentas, no necesitas usar la información de este objeto.
Extras
Un Bundle que contiene marcas enviadas por el evento que activó el adaptador de sincronización.
Autoridad
La autoridad de un proveedor de contenido en el sistema. Tu app debe tener acceso a este proveedor. Por lo general, la autoridad corresponde a un proveedor de contenido de tu propia app.
Cliente proveedor de contenido
Un ContentProviderClient para el proveedor de contenido al que hace referencia el argumento de autoridad. Un ContentProviderClient es una interfaz pública básica para un proveedor de contenido. Tiene la misma funcionalidad básica que un ContentResolver. Si usas un proveedor de contenido para almacenar datos de tu app, puedes conectarte con el proveedor que tenga este objeto. De lo contrario, puedes ignorarlo.
Resultado de sincronización
Un objeto SyncResult que se usa para enviar información al marco de trabajo del adaptador de sincronización.

En el siguiente fragmento, se muestra la estructura general 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.
         */
    }
    

Si bien la implementación real de onPerformSync() es específica para los requisitos de sincronización de datos de tu app y los protocolos de conexión del servidor, hay algunas tareas generales que debe realizar tu implementación:

Conexión con un servidor
Aunque se puede asumir que la red está disponible cuando se inicia la transferencia de datos, el marco de trabajo del adaptador de sincronización no se conecta automáticamente a un servidor.
Descarga y carga de datos
Un adaptador de sincronización no automatiza ninguna tarea de transferencia de datos. Si quieres descargar datos de un servidor y almacenarlos en un proveedor de contenido, debes proporcionar el código que solicita los datos, los descarga y los inserta en el proveedor. Del mismo modo, si quieres enviar datos a un servidor, debes leerlos desde un archivo, una base de datos o un proveedor, y enviar la solicitud de carga necesaria. También debes solucionar los errores de red que se producen mientras se ejecuta la transferencia de datos.
Control de los conflictos de datos o determinación de la actualidad los datos
Un adaptador de sincronización no controla automáticamente los conflictos entre los datos del servidor y los datos del dispositivo. Además, no detecta automáticamente si los datos del servidor son más recientes que los datos del dispositivo, o viceversa. En cambio, debes proporcionar tus propios algoritmos para controlar esta situación.
Limpieza
Al final de cada transferencia de datos, siempre debes cerrar las conexiones con un servidor y limpiar los archivos temporales y las memorias caché.

Nota: El marco de trabajo del adaptador de sincronización se ejecuta en onPerformSync() en un subproceso en segundo plano, por lo que no debes configurar tu propio procesamiento en segundo plano.

Además de las tareas relacionadas con la sincronización, debes intentar combinar tus tareas habituales relacionadas con la red y agregarlas a onPerformSync(). Si concentras todas las tareas de red en este método, conservas la energía de la batería necesaria para iniciar y detener las interfaces de red. Para obtener más información sobre cómo hacer que el acceso a la red sea más eficiente, consulta la clase de capacitación Cómo transferir datos sin consumir la batería, en la que se describen varias tareas de acceso a la red que puedes incluir en tu código de transferencia de datos.

Cómo vincular el adaptador de sincronización con el marco de trabajo

Ahora tienes tu código de transferencia de datos encapsulado en un componente de adaptador de sincronización, pero debes proporcionar al marco de trabajo acceso a tu código. Para ello, debes crear un Service vinculado que transfiera un objeto vinculante especial de Android del componente del adaptador de sincronización al marco de trabajo. Con este objeto vinculante, el marco de trabajo puede invocar el método onPerformSync() y transferirle datos.

Crea una instancia de tu componente de adaptador de sincronización como un singleton en el método onCreate() del servicio. Cuando creas una instancia del componente en onCreate(), pospones la creación hasta que se inicia el servicio, lo que sucede cuando el marco de trabajo intenta ejecutar la transferencia de datos por primera vez. Debes crear una instancia del componente de manera segura para subprocesos, en caso de que el marco de trabajo del adaptador de sincronización ponga en cola múltiples ejecuciones de tu adaptador de sincronización como respuesta a activadores o programación.

Por ejemplo, en el siguiente fragmento, se muestra cómo crear una clase que implementa el Service vinculado, crea una instancia de tu componente de adaptador de sincronización y obtiene el objeto vinculante de 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();
        }
    }
    

Nota: Si quieres ver un ejemplo más detallado de un servicio vinculado para un adaptador de sincronización, consulta la app de ejemplo.

Cómo agregar la cuenta obligatoria para el marco de trabajo

El marco de trabajo del adaptador de sincronización requiere que cada adaptador de sincronización tenga un tipo de cuenta. Ya declaraste el valor del tipo de cuenta en la sección Cómo agregar el archivo de metadatos del Autenticador. Ahora debes configurar este tipo de cuenta en el sistema Android. Para configurar el tipo de cuenta, agrega una cuenta ficticia que use el tipo de cuenta llamando a addAccountExplicitly().

El mejor lugar para llamar al método es en el método onCreate() de la actividad de apertura de tu app. En el siguiente fragmento de código, se muestra cómo hacerlo:

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.
                 */
            }
        }
        ...
    }
    

Cómo agregar el archivo de metadatos del adaptador de sincronización

Para conectar el componente del adaptador de sincronización con el marco de trabajo, debes proporcionar metadatos que describan el componente y ofrezcan marcas adicionales. Los metadatos especifican el tipo de cuenta que creaste para tu adaptador de sincronización, declaran una autoridad de proveedor de contenido asociada con tu app, controlan una parte de la interfaz de usuario del sistema relacionada con los adaptadores de sincronización y declaran otras marcas relacionadas con la sincronización. Declara estos metadatos en un archivo XML especial almacenado en el directorio /res/xml/ del proyecto de tu app. Puedes darle cualquier nombre al archivo, aunque por lo general se llama syncadapter.xml.

Este archivo XML contiene un solo elemento XML <sync-adapter> que tiene los siguientes atributos:

android:contentAuthority
La autoridad del URI para tu proveedor de contenido. Si creaste un proveedor de contenido de stub para tu app en la lección anterior, Cómo crear un proveedor de contenido de stub, usa el valor que especificaste para el atributo android:authorities en el elemento <provider> que agregaste al manifiesto de la app. Este atributo se describe con más detalle en la sección Cómo declarar al proveedor en el manifiesto.
Si transfieres datos de un proveedor de contenido a un servidor con tu adaptador de sincronización, este valor debe ser igual a la autoridad del URI de contenido que usas para esos datos. Este valor también es una de las autoridades que especificas en el atributo android:authorities del elemento <provider> que declara tu proveedor en el manifiesto de tu app.
android:accountType
El tipo de cuenta requerido por el marco de trabajo del adaptador de sincronización. El valor debe ser el mismo que el valor del tipo de cuenta que proporcionaste cuando creaste el archivo de metadatos del autenticador, como se describe en la sección Cómo agregar el archivo de metadatos del Autenticador. También es el valor que especificaste para la constante ACCOUNT_TYPE en el fragmento de código de la sección Cómo agregar la cuenta obligatoria para el marco de trabajo.
Atributos de configuración
android:userVisible
Establece la visibilidad del tipo de cuenta del adaptador de sincronización. De forma predeterminada, el ícono y la etiqueta asociados al tipo de cuenta aparecen en la sección Accounts de la app de configuración del sistema, por lo que debes inhabilitar tu adaptador de sincronización, a menos que tengas un dominio o tipo de cuenta fácilmente asociados. Si haces invisible el tipo de cuenta, puedes permitir que los usuarios controlen tu adaptador de sincronización con una interfaz de usuario en una de las actividades de tu app.
android:supportsUploading
Te permite subir datos a la nube. Establece este atributo como false si tu app solo descarga datos.
android:allowParallelSyncs
Permite que se ejecuten al mismo tiempo varias instancias de tu componente de adaptador de sincronización. Úsalo si tu app admite varias cuentas de usuario y deseas permitir que varios usuarios transfieran datos en paralelo. Esta marca no funciona si nunca ejecutas múltiples transferencias de datos.
android:isAlwaysSyncable
Indica al marco de trabajo del adaptador de sincronización que puede ejecutar tu adaptador de sincronización en cualquier momento que hayas especificado. Si quieres controlar mediante programación cuándo se puede ejecutar tu adaptador de sincronización, establece el valor este indicador como false y, luego, llama a requestSync() para ejecutar el adaptador de sincronización. Para obtener más información sobre cómo ejecutar un adaptador de sincronización, consulta la lección Cómo ejecutar un adaptador de sincronización

En el siguiente ejemplo, se muestra el XML de un adaptador de sincronización que usa una sola cuenta ficticia y solo realiza descargas.

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

Cómo declarar el adaptador de sincronización en el manifiesto

Una vez que agregaste el componente del adaptador de sincronización a tu app, debes solicitar permisos relacionados con el uso del componente y declarar el Service vinculado que agregaste.

Debido a que el componente del adaptador de sincronización ejecuta un código que transfiere datos entre la red y el dispositivo, debes solicitar permiso para acceder a Internet. Además, tu aplicación debe solicitar permiso para leer y escribir la configuración del adaptador de sincronización, de modo que puedas controlar el adaptador de sincronización mediante programación desde otros componentes de tu app. También debes solicitar un permiso especial que permita a tu app usar el componente de autenticación que creaste en la lección Cómo crear un autenticador de stub.

Para solicitar estos permisos, agrega lo siguiente al manifiesto de tu app como elementos secundarios de <manifest>:

android.permission.INTERNET
Permite que el código del adaptador de sincronización acceda a Internet para poder descargar o subir datos entre el dispositivo y un servidor. No necesitas volver a agregar este permiso si ya lo solicitaste.
android.permission.READ_SYNC_SETTINGS
Permite a tu app leer la configuración actual del adaptador de sincronización. Por ejemplo, necesitas este permiso para llamar a getIsSyncable().
android.permission.WRITE_SYNC_SETTINGS
Permite a tu app controlar la configuración del adaptador de sincronización. Necesitas este permiso para configurar ejecuciones periódicas del adaptador de sincronización usando addPeriodicSync(). Este permiso no es obligatorio para llamar a requestSync(). Para obtener más información sobre cómo ejecutar el adaptador de sincronización, consulta Cómo ejecutar un adaptador de sincronización.

En el siguiente fragmento, se muestra cómo agregar los permisos:

    <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 último, para declarar el Service vinculado que el marco de trabajo usa para interactuar con tu adaptador de sincronización, agrega el siguiente XML al manifiesto de tu app como elemento secundario 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>
    

El elemento <intent-filter> establece un filtro que se activa por la acción del intent android.content.SyncAdapter, que envía el sistema para ejecutar el adaptador de sincronización. Cuando se activa el filtro, el sistema inicia el servicio vinculado que creaste, que en este ejemplo es SyncService. El atributo android:exported="true" permite que otros procesos, además de tu app e incluido el sistema, accedan al Service. El atributo android:process=":sync" le indica al sistema que debe ejecutar el Service en un proceso compartido global llamado sync. Si tienes múltiples adaptadores de sincronización en tu app, estos pueden compartir este proceso, lo que reduce la sobrecarga.

El elemento <meta-data> proporciona el nombre del archivo XML de metadatos del adaptador de sincronización que creaste anteriormente. El atributo android:name indica que estos metadatos son para el marco del adaptador de sincronización. El elemento android:resource especifica el nombre del archivo de metadatos.

Ahora tienes todos los componentes para tu adaptador de sincronización. En la siguiente lección, se muestra cómo indicarle al marco de trabajo del adaptador de sincronización que ejecute el adaptador de sincronización, ya sea en respuesta a un evento o en un programa habitual.