Google se compromete a impulsar la igualdad racial para las comunidades afrodescendientes. Obtén información al respecto.

Cómo ejecutar un adaptador de sincronización

Nota: Recomendamos 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.

En las lecciones anteriores de esta clase, aprendiste a crear un componente de adaptador de sincronización que encapsula el código de transferencia de datos y a agregar los componentes adicionales que te permiten conectar el adaptador de sincronización al sistema. Tienes todo lo que necesitas para instalar una app que incluye un adaptador de sincronización, pero ninguno de los códigos que conociste ejecuta el adaptador de sincronización.

Debes intentar ejecutar tu adaptador de sincronización según un programa o como resultado indirecto de algún evento. Por ejemplo, tal vez quieras que tu adaptador de sincronización se ejecute según un programa habitual, ya sea después de un cierto período o en un momento determinado del día. También es posible que desees ejecutar tu adaptador de sincronización cuando haya cambios en los datos almacenados en el dispositivo. Debes evitar ejecutar el adaptador de sincronización como resultado directo de una acción del usuario, ya que, cuando lo haces, no se obtiene el beneficio completo de la capacidad de programación del marco de trabajo del adaptador de sincronización. Por ejemplo, debes evitar incluir un botón de actualización en la interfaz de usuario.

Tienes las siguientes opciones para ejecutar tu adaptador de sincronización:

Cuando cambian los datos del servidor
Ejecuta el adaptador de sincronización en respuesta a un mensaje de un servidor que indique cambios en los datos basados en el servidor. Esta opción te permite actualizar los datos del servidor en el dispositivo sin degradar el rendimiento ni reducir excesivamente la duración de la batería cuando se sondea el servidor.
Cuando cambian los datos del dispositivo
Ejecuta un adaptador de sincronización cuando cambien los datos en el dispositivo. Esta opción te permite enviar datos modificados del dispositivo a un servidor y es muy útil si necesitas asegurarte de que el servidor siempre cuente con los datos más recientes del dispositivo. Esta opción es fácil de implementar si almacenas datos en tu proveedor de contenido. Si estás utilizando un proveedor de contenido de stub, la detección de los cambios en los datos puede ser más difícil.
A intervalos regulares
Ejecuta un adaptador de sincronización después de que caduque un intervalo que elijas, o ejecútalo a una hora determinada todos los días.
A pedido
Ejecuta el adaptador de sincronización en respuesta a una acción del usuario. Sin embargo, para proporcionar la mejor experiencia del usuario, debes confiar principalmente en una de las opciones más automatizadas. Si usas las opciones automáticas, ahorras batería y recursos de red.

En el resto de esta lección, se describe con más detalles cada una de las opciones.

Cómo ejecutar el adaptador de sincronización cuando cambian los datos del servidor

Si tu app transfiere datos de un servidor y estos cambian con frecuencia, puedes usar un adaptador de sincronización para realizar descargas en respuesta a los cambios en los datos. Para ejecutar el adaptador de sincronización, haz que el servidor envíe un mensaje especial a un BroadcastReceiver en tu app. En respuesta a este mensaje, llama a ContentResolver.requestSync() para solicitar al framework del adaptador de sincronización que lo ejecute.

Google Cloud Messaging (GCM) proporciona los componentes del servidor y del dispositivo que necesitas para que funcione este sistema de mensajería. Usar GCM para activar transferencias es más confiable y más eficiente que sondear los servidores para conocer el estado. Si bien el sondeo requiere un Service que esté siempre activo, GCM usa un BroadcastReceiver que se activa cuando llega un mensaje. Aunque el sondeo a intervalos regulares consume batería incluso si no hay actualizaciones disponibles, GCM solo envía mensajes cuando es necesario.

Nota: Si usas GCM para activar tu adaptador de sincronización a través de una transmisión a todos los dispositivos donde está instalada tu app, recuerda que reciben tu mensaje aproximadamente al mismo tiempo. Esta situación puede hacer que varias instancias del adaptador de sincronización se ejecuten al mismo tiempo, lo que provoca sobrecarga del servidor y de la red. Para evitar esta situación cuando realizas una transmisión a todos los dispositivos, procura posponer el inicio del adaptador de sincronización a un período único para cada dispositivo.

En el siguiente fragmento de código, se muestra cómo ejecutar requestSync() en respuesta a un mensaje entrante de GCM:

Kotlin

    ...
    // Constants
    // Content provider authority
    const val AUTHORITY = "com.example.android.datasync.provider"
    // Account type
    const val ACCOUNT_TYPE = "com.example.android.datasync"
    // Account
    const val ACCOUNT = "default_account"
    // Incoming Intent key for extended data
    const val KEY_SYNC_REQUEST = "com.example.android.datasync.KEY_SYNC_REQUEST"
    ...
    class GcmBroadcastReceiver : BroadcastReceiver() {
        ...
        override fun onReceive(context: Context, intent: Intent) {
            // Get a GCM object instance
            val gcm: GoogleCloudMessaging = GoogleCloudMessaging.getInstance(context)
            // Get the type of GCM message
            val messageType: String? = gcm.getMessageType(intent)
            /*
             * Test the message type and examine the message contents.
             * Since GCM is a general-purpose messaging system, you
             * may receive normal messages that don't require a sync
             * adapter run.
             * The following code tests for a a boolean flag indicating
             * that the message is requesting a transfer from the device.
             */
            if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE == messageType
                && intent.getBooleanExtra(KEY_SYNC_REQUEST, false)) {
                /*
                 * Signal the framework to run your sync adapter. Assume that
                 * app initialization has already created the account.
                 */
                ContentResolver.requestSync(mAccount, AUTHORITY, null)
                ...
            }
            ...
        }
        ...
    }
    

Java

    public class GcmBroadcastReceiver extends BroadcastReceiver {
        ...
        // Constants
        // Content provider authority
        public static final String AUTHORITY = "com.example.android.datasync.provider";
        // Account type
        public static final String ACCOUNT_TYPE = "com.example.android.datasync";
        // Account
        public static final String ACCOUNT = "default_account";
        // Incoming Intent key for extended data
        public static final String KEY_SYNC_REQUEST =
                "com.example.android.datasync.KEY_SYNC_REQUEST";
        ...
        @Override
        public void onReceive(Context context, Intent intent) {
            // Get a GCM object instance
            GoogleCloudMessaging gcm =
                    GoogleCloudMessaging.getInstance(context);
            // Get the type of GCM message
            String messageType = gcm.getMessageType(intent);
            /*
             * Test the message type and examine the message contents.
             * Since GCM is a general-purpose messaging system, you
             * may receive normal messages that don't require a sync
             * adapter run.
             * The following code tests for a a boolean flag indicating
             * that the message is requesting a transfer from the device.
             */
            if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)
                &&
                intent.getBooleanExtra(KEY_SYNC_REQUEST)) {
                /*
                 * Signal the framework to run your sync adapter. Assume that
                 * app initialization has already created the account.
                 */
                ContentResolver.requestSync(mAccount, AUTHORITY, null);
                ...
            }
            ...
        }
        ...
    }
    

Cómo ejecutar el adaptador de sincronización cuando cambian los datos del proveedor de contenido

Si tu app recopila datos en un proveedor de contenido y deseas actualizar el servidor cada vez que actualizas el proveedor, puedes configurarla para que ejecute automáticamente tu adaptador de sincronización. Para ello, registra un observador para el proveedor de contenido. Cuando cambien los datos de tu proveedor de contenido, su framework llamará al observador. En el observador, llama a requestSync() para indicarle al framework que ejecute tu adaptador de sincronización.

Nota: Si usas un proveedor de contenido de stub, no tendrás datos en el proveedor de contenido y nunca se llamará a onChange(). En este caso, deberás proporcionar tu propio mecanismo para detectar cambios en los datos del dispositivo. Este mecanismo también es responsable de llamar al método requestSync() cuando cambian los datos.

A fin de crear un observador para tu proveedor de contenido, extiende la clase ContentObserver e implementa ambas formas del método onChange(). En onChange(), llama a requestSync() para iniciar el adaptador de sincronización.

Para registrar el observador, pásalo como argumento en una llamada a registerContentObserver(). En esa llamada, también debes pasar un URI de contenido para los datos que quieras observar. El framework del proveedor de contenido compara este URI de observación con URI de contenido pasados como argumentos a métodos ContentResolver que modifican tu proveedor, como ContentResolver.insert(). Si hay una coincidencia, se llamará a tu implementación de ContentObserver.onChange().

En el siguiente fragmento de código, se muestra cómo definir un ContentObserver que llame a requestSync() cuando cambie una tabla:

Kotlin

    // Constants
    // Content provider scheme
    const val SCHEME = "content://"
    // Content provider authority
    const val AUTHORITY = "com.example.android.datasync.provider"
    // Path for the content provider table
    const val TABLE_PATH = "data_table"
    ...
    class MainActivity : FragmentActivity() {
        ...
        // A content URI for the content provider's data table
        private lateinit var uri: Uri
        // A content resolver for accessing the provider
        private lateinit var mResolver: ContentResolver
        ...
        inner class TableObserver(...) : ContentObserver(...) {
            /*
             * Define a method that's called when data in the
             * observed content provider changes.
             * This method signature is provided for compatibility with
             * older platforms.
             */
            override fun onChange(selfChange: Boolean) {
                /*
                 * Invoke the method signature available as of
                 * Android platform version 4.1, with a null URI.
                 */
                onChange(selfChange, null)
            }

            /*
             * Define a method that's called when data in the
             * observed content provider changes.
             */
            override fun onChange(selfChange: Boolean, changeUri: Uri?) {
                /*
                 * Ask the framework to run your sync adapter.
                 * To maintain backward compatibility, assume that
                 * changeUri is null.
                 */
                ContentResolver.requestSync(account, AUTHORITY, null)
            }
            ...
        }
        ...
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            ...
            // Get the content resolver object for your app
            mResolver = contentResolver
            // Construct a URI that points to the content provider data table
            uri = Uri.Builder()
                    .scheme(SCHEME)
                    .authority(AUTHORITY)
                    .path(TABLE_PATH)
                    .build()
            /*
             * Create a content observer object.
             * Its code does not mutate the provider, so set
             * selfChange to "false"
             */
            val observer = TableObserver(false)
            /*
             * Register the observer for the data table. The table's path
             * and any of its subpaths trigger the observer.
             */
            mResolver.registerContentObserver(uri, true, observer)
            ...
        }
        ...
    }
    

Java

    public class MainActivity extends FragmentActivity {
        ...
        // Constants
        // Content provider scheme
        public static final String SCHEME = "content://";
        // Content provider authority
        public static final String AUTHORITY = "com.example.android.datasync.provider";
        // Path for the content provider table
        public static final String TABLE_PATH = "data_table";
        // Account
        public static final String ACCOUNT = "default_account";
        // Global variables
        // A content URI for the content provider's data table
        Uri uri;
        // A content resolver for accessing the provider
        ContentResolver mResolver;
        ...
        public class TableObserver extends ContentObserver {
            /*
             * Define a method that's called when data in the
             * observed content provider changes.
             * This method signature is provided for compatibility with
             * older platforms.
             */
            @Override
            public void onChange(boolean selfChange) {
                /*
                 * Invoke the method signature available as of
                 * Android platform version 4.1, with a null URI.
                 */
                onChange(selfChange, null);
            }
            /*
             * Define a method that's called when data in the
             * observed content provider changes.
             */
            @Override
            public void onChange(boolean selfChange, Uri changeUri) {
                /*
                 * Ask the framework to run your sync adapter.
                 * To maintain backward compatibility, assume that
                 * changeUri is null.
                 */
                ContentResolver.requestSync(mAccount, AUTHORITY, null);
            }
            ...
        }
        ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ...
            // Get the content resolver object for your app
            mResolver = getContentResolver();
            // Construct a URI that points to the content provider data table
            uri = new Uri.Builder()
                      .scheme(SCHEME)
                      .authority(AUTHORITY)
                      .path(TABLE_PATH)
                      .build();
            /*
             * Create a content observer object.
             * Its code does not mutate the provider, so set
             * selfChange to "false"
             */
            TableObserver observer = new TableObserver(false);
            /*
             * Register the observer for the data table. The table's path
             * and any of its subpaths trigger the observer.
             */
            mResolver.registerContentObserver(uri, true, observer);
            ...
        }
        ...
    }
    

Cómo ejecutar el adaptador de sincronización de manera periódica

Si quieres ejecutar tu adaptador de sincronización de manera periódica, puedes establecer un período de espera entre ejecuciones, ejecutarlo en ciertos momentos del día, o ambos. Si ejecutas el adaptador de sincronización de forma periódica, puedes hacerlo coincidir aproximadamente con el intervalo de actualización de tu servidor.

De manera similar, si quieres subir datos desde el dispositivo cuando el servidor está relativamente inactivo, puedes programar el adaptador de sincronización para que se ejecute por la noche. La mayoría de los usuarios dejan su dispositivo encendido y enchufado por la noche, por lo que suele estar disponible. Además, el dispositivo no ejecuta otras tareas al mismo tiempo que tu adaptador de sincronización. Sin embargo, si adoptas este enfoque, debes asegurarte de que los dispositivos no activen la transferencia de datos al mismo tiempo. Si esto ocurre, es probable que se sobrecarguen las redes de datos de tu servidor y del proveedor de datos móviles.

En general, las ejecuciones periódicas son convenientes si los usuarios no necesitan actualizaciones instantáneas, pero esperan tener actualizaciones periódicas. Las ejecuciones periódicas también son útiles si deseas equilibrar la disponibilidad de datos actualizados con la eficiencia de ejecuciones más pequeñas del adaptador de sincronización que no consuman demasiados recursos del dispositivo.

Para ejecutar el adaptador de sincronización a intervalos regulares, llama a addPeriodicSync(). Esto programa tu adaptador de sincronización para que se ejecute después de que haya transcurrido determinada cantidad de tiempo. Debido a que el marco de trabajo del adaptador de sincronización debe considerar otras ejecuciones del adaptador e intenta maximizar la eficiencia de la batería, el tiempo transcurrido puede variar en unos segundos. Además, el framework no ejecutará tu adaptador de sincronización si no está disponible la red.

Ten en cuenta que addPeriodicSync() no ejecuta el adaptador de sincronización en un momento específico del día. Para ejecutar el adaptador de sincronización aproximadamente a la misma hora todos los días, usa una alarma recurrente como activador. Las alarmas recurrentes se describen con más detalle en la documentación de referencia de AlarmManager. Si usas el método setInexactRepeating() para establecer activadores de hora determinada que tengan alguna variación, deberías aleatorizar la hora de inicio a fin de asegurarte de que las ejecuciones del adaptador de sincronización sean escalonadas.

El método addPeriodicSync() no inhabilita setSyncAutomatically(), por lo que puedes obtener varias ejecuciones de sincronización en un período relativamente corto de tiempo. Además, solo se permiten unas pocas marcas de control del adaptador de sincronización en una llamada a addPeriodicSync(); las marcas que no están permitidas se describen en la documentación de referencia de addPeriodicSync().

En el siguiente fragmento de código, se muestra cómo programar ejecuciones periódicas del adaptador de sincronización:

Kotlin

    // Content provider authority
    const val AUTHORITY = "com.example.android.datasync.provider"
    // Account
    const val ACCOUNT = "default_account"
    // Sync interval constants
    const val SECONDS_PER_MINUTE = 60L
    const val SYNC_INTERVAL_IN_MINUTES = 60L
    const val SYNC_INTERVAL = SYNC_INTERVAL_IN_MINUTES * SECONDS_PER_MINUTE
    ...
    class MainActivity : FragmentActivity() {
        ...
        // A content resolver for accessing the provider
        private lateinit var mResolver: ContentResolver

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            ...
            // Get the content resolver for your app
            mResolver = contentResolver
            /*
             * Turn on periodic syncing
             */
            ContentResolver.addPeriodicSync(
                    mAccount,
                    AUTHORITY,
                    Bundle.EMPTY,
                    SYNC_INTERVAL)
            ...
        }
        ...
    }
    

Java

    public class MainActivity extends FragmentActivity {
        ...
        // Constants
        // Content provider authority
        public static final String AUTHORITY = "com.example.android.datasync.provider";
        // Account
        public static final String ACCOUNT = "default_account";
        // Sync interval constants
        public static final long SECONDS_PER_MINUTE = 60L;
        public static final long SYNC_INTERVAL_IN_MINUTES = 60L;
        public static final long SYNC_INTERVAL =
                SYNC_INTERVAL_IN_MINUTES *
                SECONDS_PER_MINUTE;
        // Global variables
        // A content resolver for accessing the provider
        ContentResolver mResolver;
        ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ...
            // Get the content resolver for your app
            mResolver = getContentResolver();
            /*
             * Turn on periodic syncing
             */
            ContentResolver.addPeriodicSync(
                    mAccount,
                    AUTHORITY,
                    Bundle.EMPTY,
                    SYNC_INTERVAL);
            ...
        }
        ...
    }
    

Cómo ejecutar el adaptador de sincronización a pedido

Ejecutar el adaptador de sincronización en respuesta a una solicitud del usuario es la estrategia menos preferible. El marco de trabajo está diseñado específicamente para conservar la energía de la batería cuando se ejecutan adaptadores de sincronización según un programa. Las opciones que ejecutan una sincronización en respuesta a los cambios de datos usan la energía de la batería de manera efectiva, ya que la energía se usa para proporcionar nuevos datos.

En cambio, permitir que los usuarios ejecuten una sincronización a pedido significa que la sincronización se ejecuta sola, lo que implica un uso ineficiente de los recursos de red y energía. Además, proporcionar sincronización a pedido lleva a los usuarios a solicitar una sincronización incluso si no hay evidencia de que los datos hayan cambiado, y ejecutar una sincronización que no actualice los datos implica un uso ineficaz de la energía de la batería. En general, tu app debería usar otros indicadores para activar una sincronización, o programarlas a intervalos regulares, sin entrada del usuario.

Sin embargo, si aún deseas ejecutar el adaptador de sincronización a pedido, configura los marcadores de este para ejecutar un adaptador de sincronización manual y, luego, llama a ContentResolver.requestSync().

Ejecuta transferencias a pedido con las siguientes marcas:

SYNC_EXTRAS_MANUAL
Fuerza una sincronización manual. El framework del adaptador de sincronización ignora la configuración existente, como la marca establecida por setSyncAutomatically().
SYNC_EXTRAS_EXPEDITED
Hace que la sincronización comience de inmediato. Si no estableces esta opción, es posible que el sistema espere varios segundos antes de ejecutar la solicitud de sincronización, ya que intentará optimizar el uso de la batería programando muchas solicitudes en un período corto de tiempo.

En el siguiente fragmento de código, se muestra cómo llamar a requestSync() en respuesta a cuando se hace clic en un botón:

Kotlin

    // Constants
    // Content provider authority
    val AUTHORITY = "com.example.android.datasync.provider"
    // Account type
    val ACCOUNT_TYPE = "com.example.android.datasync"
    // Account
    val ACCOUNT = "default_account"
    ...
    class MainActivity : FragmentActivity() {
        ...
        // Instance fields
        private lateinit var mAccount: Account
        ...
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            ...
            /*
             * Create the dummy account. The code for CreateSyncAccount
             * is listed in the lesson Creating a Sync Adapter
             */

            mAccount = createSyncAccount()
            ...
        }

        /**
         * Respond to a button click by calling requestSync(). This is an
         * asynchronous operation.
         *
         * This method is attached to the refresh button in the layout
         * XML file
         *
         * @param v The View associated with the method call,
         * in this case a Button
         */
        fun onRefreshButtonClick(v: View) {
            // Pass the settings flags by inserting them in a bundle
            val settingsBundle = Bundle().apply {
                putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true)
                putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true)
            }
            /*
             * Request the sync for the default account, authority, and
             * manual sync settings
             */
            ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle)
        }
    

Java

    public class MainActivity extends FragmentActivity {
        ...
        // Constants
        // Content provider authority
        public static final String AUTHORITY =
                "com.example.android.datasync.provider";
        // Account type
        public static final String ACCOUNT_TYPE = "com.example.android.datasync";
        // Account
        public static final String ACCOUNT = "default_account";
        // Instance fields
        Account mAccount;
        ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ...
            /*
             * Create the dummy account. The code for CreateSyncAccount
             * is listed in the lesson Creating a Sync Adapter
             */

            mAccount = CreateSyncAccount(this);
            ...
        }
        /**
         * Respond to a button click by calling requestSync(). This is an
         * asynchronous operation.
         *
         * This method is attached to the refresh button in the layout
         * XML file
         *
         * @param v The View associated with the method call,
         * in this case a Button
         */
        public void onRefreshButtonClick(View v) {
            // Pass the settings flags by inserting them in a bundle
            Bundle settingsBundle = new Bundle();
            settingsBundle.putBoolean(
                    ContentResolver.SYNC_EXTRAS_MANUAL, true);
            settingsBundle.putBoolean(
                    ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
            /*
             * Request the sync for the default account, authority, and
             * manual sync settings
             */
            ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle);
        }