Cómo crear una copia de seguridad de los pares clave-valor con Android Backup Service

Android Backup Service ofrece el servicio de copia de seguridad y restablecimiento del almacenamiento en la nube para datos de pares clave-valor en tu app para Android. Durante una operación de copia de seguridad de clave-valor, esos datos de la app se envían al transporte alternativo del dispositivo. Si el dispositivo usa el transporte alternativo predeterminado de Google, los datos se envían a Android Backup Service y se archivan.

La cantidad de datos se limita a 5 MB por usuario de tu app y no existe ningún cargo por almacenar datos de copia de seguridad.

Para obtener una descripción general de las opciones de copia de seguridad de Android y orientación sobre qué datos necesitan esa copia y restablecimiento, consulta la Descripción general de la copia de seguridad de datos.

Nota: Para la mayoría de las apps, se debe usar la copia de seguridad automática para implementar ese tipo de copias y restablecimiento. Tu app solo puede implementar copias de seguridad automáticas o copias de seguridad de clave-valor, no ambas. Las primeras no requieren código y crean una copia de seguridad de archivos enteros, mientras que las segundas requieren que escribas un código para definir de forma explícita tu contenido de copia de seguridad en la forma de pares clave-valor.

Cómo implementar copias de seguridad de clave-valor

Para crear una copia de seguridad de los datos de tu app, debes implementar un agente de ese tipo. El administrador de copias de seguridad llama a ese agente tanto durante esa actividad como durante el restablecimiento.

Para implementar un agente de copia de seguridad, debes hacer lo siguiente:

  1. Declara tu agente de copia de seguridad en tu archivo de manifiesto con el atributo android:backupAgent.
  2. Registra tu app con Android Backup Service.
  3. Define un agente de copia de seguridad mediante una de las siguientes opciones:
    1. Cómo extender BackupAgent

      La clase BackupAgent proporciona la interfaz central mediante la cual tu app se comunica con el administrador de copias de seguridad. Si extiendes esa clase directamente, debes anular onBackup() y onRestore() para procesar las operaciones de copia de seguridad y restablecimiento de tus datos.

    2. O bien:

    3. Cómo extender BackupAgentHelper

      La clase BackupAgentHelper proporciona un wrapper práctico alrededor de la clase BackupAgent, lo que minimiza la cantidad de código que debes escribir. En tu BackupAgentHelper, debes usar uno o más objetos "auxiliares", que crean copias de seguridad y restablecen automáticamente ciertos tipos de datos para que no tengas que implementar onBackup() ni onRestore(). A menos que necesites tener el control total de las copias de seguridad de tu app, te recomendamos que uses BackupAgentHelper para procesarlas.

      Actualmente, Android ofrece asistentes de copia de seguridad que las crean y que también realizan restablecimientos de archivos completos de SharedPreferences y almacenamiento interno.

Cómo declarar el agente de copia de seguridad en el manifiesto

Este es el paso más fácil; por lo tanto, cuando hayas decidido el nombre de la clase para tu agente de copia de seguridad, decláralo en tu manifiesto con el atributo android:backupAgent en la etiqueta <application>.

Por ejemplo:

    <manifest ... >
        ...
        <application android:label="MyApplication"
                     android:backupAgent="MyBackupAgent">
            <activity ... >
                ...
            </activity>
        </application>
    </manifest>
    

Otro atributo que recomendamos usar es android:restoreAnyVersion. Ese atributo toma un valor booleano para indicar si quieres restablecer los datos de la app independientemente de su versión actual en comparación con la versión que produjo los datos de copia de seguridad. (El valor predeterminado es "false"). Para obtener más información, consulta Cómo verificar la versión de restablecimiento de datos.

Cómo registrarse para usar Android Backup Service

Google ofrece un transporte alternativo con Android Backup Service para la mayoría de los dispositivos que ejecutan Android 2.2 o versiones posteriores.

Para que tu app realice una copia de seguridad con Android Backup Service, debes registrarla con el servicio a fin de recibir una clave del servicio de copia de seguridad y declararla en tu manifiesto de Android.

Para obtener la clave del servicio de copia de seguridad, regístrate en Android Backup Service. Cuando te registras, recibes una clave del servicio de copia de seguridad y el código XML <meta-data> apropiado para tu archivo de manifiesto de Android, que debes incluir como elemento secundario de <application>. Por ejemplo:

    <application android:label="MyApplication"
                 android:backupAgent="MyBackupAgent">
        ...
        <meta-data android:name="com.google.android.backup.api_key"
            android:value="AEdPqrEAAAAIDaYEVgU6DJnyJdBmU7KLH3kszDXLv_4DIsEIyQ" />
    </application>
    

android:name debe ser "com.google.android.backup.api_key", y android:value debe ser la clave del servicio de copia de seguridad recibida por el registro de Android Backup Service.

Si tienes varias apps, debes registrar cada una con el nombre del paquete correspondiente.

Nota: No se garantiza que el transporte alternativo que proporciona Android Backup Service esté disponible en todos los dispositivos Android que admitan copias de seguridad. Algunos dispositivos admiten copias de seguridad con un medio de transporte diferente. Es posible que algunos dispositivos no admitan la copia de seguridad y no existe forma de que tu app sepa qué tipo de transporte se usa en el dispositivo. Sin embargo, si implementas una copia de seguridad para tu app, siempre debes incluir una clave de ese servicio para Android Backup Service a fin de que tu app pueda realizarla cuando el dispositivo use el transporte de Android Backup Service. Si el dispositivo no usa Android Backup Service, se ignora el elemento <meta-data> con la clave del servicio de copia de seguridad.

Cómo extender BackupAgentHelper

Debes crear tu agente de copia de seguridad mediante el uso de BackupAgentHelper si deseas crear una copia de seguridad de los archivos completos (de SharedPreferences o del almacenamiento interno). Crear tu agente de copia de seguridad con BackupAgentHelper requiere mucho menos códigos que extender BackupAgent, porque no tienes que implementar onBackup() y onRestore().

Tu implementación de BackupAgentHelper debe usar uno o más asistentes de copia de seguridad. Un asistente de copia de seguridad es un componente especializado que BackupAgentHelper invoca a fin de realizar operaciones de copia de seguridad y restablecimiento para un tipo particular de datos. El marco de trabajo de Android actualmente proporciona dos asistentes diferentes:

Puedes incluir varios asistentes en tu BackupAgentHelper, pero solo se necesita un asistente para cada tipo de datos. Es decir, si tienes varios archivos SharedPreferences, solo necesitan un SharedPreferencesBackupHelper.

Para cada asistente que quieras agregar a tu BackupAgentHelper, debes hacer lo siguiente durante tu método onCreate():

  1. Crea una instancia de la clase de asistente que quieras. En el constructor de clases, debes especificar los archivos para los que quieres crear copias de seguridad.
  2. Llama al addHelper() para agregar el asistente a tu BackupAgentHelper.

En las siguientes secciones, se describe cómo crear un agente de copia de seguridad con cada uno de los asistentes disponibles.

Cómo crear copias de seguridad de SharedPreferences

Cuando creas una instancia de SharedPreferencesBackupHelper, debes incluir el nombre de uno o más archivos SharedPreferences.

Por ejemplo, para crear una copia de seguridad de un archivo SharedPreferences llamado user_preferences, un agente completo que usa BackupAgentHelper tiene este estilo:

Kotlin

    // The name of the SharedPreferences file
    const val PREFS = "user_preferences"

    // A key to uniquely identify the set of backup data
    const val PREFS_BACKUP_KEY = "prefs"

    class MyPrefsBackupAgent : BackupAgentHelper() {
        override fun onCreate() {
            // Allocate a helper and add it to the backup agent
            SharedPreferencesBackupHelper(this, PREFS).also {
                addHelper(PREFS_BACKUP_KEY, it)
            }
        }
    }

    

Java

    public class MyPrefsBackupAgent extends BackupAgentHelper {
        // The name of the SharedPreferences file
        static final String PREFS = "user_preferences";

        // A key to uniquely identify the set of backup data
        static final String PREFS_BACKUP_KEY = "prefs";

        // Allocate a helper and add it to the backup agent
        @Override
        public void onCreate() {
            SharedPreferencesBackupHelper helper =
                    new SharedPreferencesBackupHelper(this, PREFS);
            addHelper(PREFS_BACKUP_KEY, helper);
        }
    }

    

Se ve así. SharedPreferencesBackupHelper incluye todos los códigos necesarios para crear copias de seguridad y restablecer un archivo SharedPreferences.

Cuando el administrador de copias de seguridad llama a onBackup() y onRestore(), BackupAgentHelper llama a tus asistentes para realizar copias de seguridad y restablecimientos de tus archivos específicos.

Nota: Los métodos de SharedPreferences son seguros para los subprocesos, por lo que puedes leer y escribir con seguridad el archivo de preferencias compartidas de tu agente de copia de seguridad y otras actividades.

Cómo crear copias de seguridad de otros archivos

Cuando creas una instancia de un FileBackupHelper, debes incluir el nombre de uno o más de los archivos que se guardan en el almacenamiento interno de tu app (según lo que especifica getFilesDir(), que es la misma ubicación en la que openFileOutput() escribe archivos).

Por ejemplo, para crear copias de seguridad de dos archivos llamados scores y stats, un agente de ese tipo que usa BackupAgentHelper tiene el siguiente estilo:

Kotlin

    // The name of the file
    const val TOP_SCORES = "scores"
    const val PLAYER_STATS = "stats"
    // A key to uniquely identify the set of backup data
    const val FILES_BACKUP_KEY = "myfiles"

    class MyFileBackupAgent : BackupAgentHelper() {
        override fun onCreate() {
            // Allocate a helper and add it to the backup agent
            FileBackupHelper(this, TOP_SCORES, PLAYER_STATS).also {
                addHelper(FILES_BACKUP_KEY, it)
            }
        }
    }

    

Java

    public class MyFileBackupAgent extends BackupAgentHelper {
        // The name of the file
        static final String TOP_SCORES = "scores";
        static final String PLAYER_STATS = "stats";

        // A key to uniquely identify the set of backup data
        static final String FILES_BACKUP_KEY = "myfiles";

        // Allocate a helper and add it to the backup agent
        @Override
        public void onCreate() {
            FileBackupHelper helper = new FileBackupHelper(this,
                    TOP_SCORES, PLAYER_STATS);
            addHelper(FILES_BACKUP_KEY, helper);
        }
    }

    

FileBackupHelper incluye todo el código necesario para crear una copia de seguridad y restablecer los archivos guardados en el almacenamiento interno de tu app.

Sin embargo, leer y escribir en archivos del almacenamiento interno no es seguro para los subprocesos. Para asegurarte de que tu agente de copia de seguridad no lea ni escriba archivos al mismo tiempo que las actividades, debes usar instrucciones sincronizadas cada vez que leas o escribas. Por ejemplo, en cualquier actividad en la que lees y escribes el archivo, debes usar un objeto como el bloqueo intrínseco para las instrucciones sincronizadas:

Kotlin

    // Object for intrinsic lock
    companion object {
        val sDataLock = Any()
    }

    

Java

    // Object for intrinsic lock
    static final Object sDataLock = new Object();

    

Luego, crea una instrucción sincronizada con este bloqueo cada vez que leas o escribas los archivos. Por ejemplo, aquí tienes una instrucción sincronizada para escribir la puntuación más reciente de un juego en un archivo:

Kotlin

    try {
        synchronized(MyActivity.sDataLock) {
            val dataFile = File(filesDir, TOP_SCORES)
            RandomAccessFile(dataFile, "rw").apply {
                writeInt(score)
            }
        }
    } catch (e: IOException) {
        Log.e(TAG, "Unable to write to file")
    }

    

Java

    try {
        synchronized (MyActivity.sDataLock) {
            File dataFile = new File(getFilesDir(), TOP_SCORES);
            RandomAccessFile raFile = new RandomAccessFile(dataFile, "rw");
            raFile.writeInt(score);
        }
    } catch (IOException e) {
        Log.e(TAG, "Unable to write to file");
    }

    

Debes sincronizar tus instrucciones de lectura con el mismo bloqueo.

Luego, en tu BackupAgentHelper, debes anular onBackup() y onRestore() para sincronizar las operaciones de copia de seguridad y restablecimiento con el mismo bloqueo intrínseco. En el ejemplo anterior de MyFileBackupAgent, necesitas los siguientes métodos:

Kotlin

    @Throws(IOException::class)
    override fun onBackup(
            oldState: ParcelFileDescriptor,
            data: BackupDataOutput,
            newState: ParcelFileDescriptor
    ) {
        // Hold the lock while the FileBackupHelper performs backup
        synchronized(MyActivity.sDataLock) {
            super.onBackup(oldState, data, newState)
        }
    }

    @Throws(IOException::class)
    override fun onRestore(
            data: BackupDataInput,
            appVersionCode: Int,
            newState: ParcelFileDescriptor
    ) {
        // Hold the lock while the FileBackupHelper restores the file
        synchronized(MyActivity.sDataLock) {
            super.onRestore(data, appVersionCode, newState)
        }
    }

    

Java

    @Override
    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
              ParcelFileDescriptor newState) throws IOException {
        // Hold the lock while the FileBackupHelper performs backup
        synchronized (MyActivity.sDataLock) {
            super.onBackup(oldState, data, newState);
        }
    }

    @Override
    public void onRestore(BackupDataInput data, int appVersionCode,
            ParcelFileDescriptor newState) throws IOException {
        // Hold the lock while the FileBackupHelper restores the file
        synchronized (MyActivity.sDataLock) {
            super.onRestore(data, appVersionCode, newState);
        }
    }

    

Eso es todo lo que necesitas para crear una copia de seguridad y restablecer los datos con BackupAgentHelper.

Cómo extender BackupAgent

La mayoría de las aplicaciones no deberían extender la clase BackupAgent directamente, sino que deberían extender BackupAgentHelper para aprovechar las clases de ayuda integradas que crean copias de seguridad y restablecen tus archivos automáticamente. Sin embargo, quizás quieras extender BackupAgent directamente si necesitas hacerlo:

  • Controla la versión de tu formato de datos. Por ejemplo, si anticipas la necesidad de revisar el formato en el que escribes los datos de tu app, puedes crear un agente de copia de seguridad para hacer una verificación cruzada de la versión de tu app durante una operación de restablecimiento y realizar las tareas de compatibilidad necesarias si la versión del dispositivo es diferente de la de los datos de copia de seguridad. Para obtener más información, consulta Cómo verificar la versión de restablecimiento de datos.
  • En vez de crear una copia de seguridad de un archivo completo, puedes especificar qué datos requieren esa copia y cómo se restablecerá cada parte en el dispositivo. (Esto también puede ayudarte a administrar diferentes versiones, porque lees y escribes tus datos como entidades únicas en vez de archivos completos).
  • Crea una copia de seguridad de datos en una base de datos. Si tienes una base de datos SQLite que quieres restablecer cuando el usuario reinstala tu app, debes crear un BackupAgent personalizado que lea los datos adecuados durante una operación de copia de seguridad. Luego, crea la tabla e inserta los datos durante esa operación.

Si no necesitas realizar ninguna de las tareas anteriores y quieres crear una copia de seguridad de los archivos de SharedPreferences o del almacenamiento interno, consulta Cómo extender BackupAgentHelper.

Métodos obligatorios

Cuando creas un BackupAgent, debes implementar los siguientes métodos de devolución de llamada:

onBackup()
El administrador de copias de seguridad llama a este método después de solicitar una copia de seguridad. En este método, lees los datos de tu app desde el dispositivo y pasas los datos que deseas copiar al administrador de copias de seguridad, como se describe a continuación en Cómo crear una copia de seguridad.
onRestore()
El administrador de copias de seguridad llama a este método durante una operación de restablecimiento. El método entrega tus datos de copia de seguridad, que tu app puede usar para restablecer su estado anterior, como se describe a continuación en Cómo realizar un restablecimiento.

El sistema llama a este método para restablecer cualquier dato de copia de seguridad cuando el usuario reinstala tu app, pero esta también puede solicitar un restablecimiento.

Cómo crear una copia de seguridad

Cuando llega el momento de crear una copia de seguridad de los datos de tu app, el administrador de copias de seguridad llama a tu método onBackup(). Aquí es donde debes proporcionar los datos de tu app al administrador de copias de seguridad para poder guardarlos en el almacenamiento en la nube.

Solo el administrador de copias de seguridad puede llamar al método onBackup() de tu agente. Cada vez que cambien los datos de tu app y quieras realizar una copia de seguridad, debes solicitar esa operación llamando a dataChanged() (consulta Cómo solicitar una copia de seguridad para obtener más información). Una solicitud de copia de seguridad no genera una llamada inmediata a tu método onBackup(). En cambio, el administrador de copias de seguridad espera el momento adecuado; luego, crea una copia de seguridad para todas las aplicaciones que la han solicitado desde que se realizó la última.

Sugerencia: Mientras desarrollas tu app, puedes iniciar una operación inmediata desde el administrador de copias de seguridad con la herramienta bmgr.

Cuando el administrador de copias de seguridad llama a tu método onBackup(), pasa por tres parámetros:

oldState
Es un ParcelFileDescriptor abierto y de solo lectura que apunta al último estado de copia de seguridad proporcionado por tu app. No se trata de los datos de copia de seguridad del almacenamiento en la nube, sino de una representación local de los datos de la copia de seguridad que se creó la última vez que se llamó a onBackup() (como se define en newState, a continuación, o desde onRestore(); en la siguiente sección se proporciona más información). Como onBackup() no te permite leer datos de copia de seguridad existentes en el almacenamiento en la nube, puedes usar esa representación local para determinar si cambiaron desde que se creó la última.
data
Es un objeto BackupDataOutput que usas para entregar tus datos al administrador de copias de seguridad.
newState
Es un ParcelFileDescriptor abierto de lectura/escritura que apunta a un archivo en el que debes escribir una representación de los datos que entregaste a data (una representación puede ser tan simple como la última marca de tiempo que modificaste en tu archivo). Este objeto se muestra como oldState la próxima vez que el administrador de copias de seguridad llama a tu método onBackup(). Si no escribes tus datos de copia de seguridad en newState, oldState apuntará a un archivo vacío la próxima vez que el administrador de copias de seguridad llame a onBackup().

Con estos parámetros, debes implementar tu método onBackup() para hacer lo siguiente:

  1. Comprueba si cambiaron tus datos desde la última copia de seguridad. Para ello, compara oldState con los datos actuales. La forma en que lees los datos en oldState depende de cómo los escribiste originalmente en newState (consulta el paso 3). La manera más fácil de registrar el estado de un archivo es con su última marca de tiempo modificada. Por ejemplo, así puedes leer y comparar una marca de tiempo de oldState:

    Kotlin

        val instream = FileInputStream(oldState.fileDescriptor)
        val dataInputStream = DataInputStream(instream)
        try {
            // Get the last modified timestamp from the state file and data file
            val stateModified = dataInputStream.readLong()
            val fileModified: Long = dataFile.lastModified()
            if (stateModified != fileModified) {
                // The file has been modified, so do a backup
                // Or the time on the device changed, so be safe and do a backup
            } else {
                // Don't back up because the file hasn't changed
                return
            }
        } catch (e: IOException) {
            // Unable to read state file... be safe and do a backup
        }
    
        

    Java

        // Get the oldState input stream
        FileInputStream instream = new FileInputStream(oldState.getFileDescriptor());
        DataInputStream in = new DataInputStream(instream);
    
        try {
            // Get the last modified timestamp from the state file and data file
            long stateModified = in.readLong();
            long fileModified = dataFile.lastModified();
    
            if (stateModified != fileModified) {
                // The file has been modified, so do a backup
                // Or the time on the device changed, so be safe and do a backup
            } else {
                // Don't back up because the file hasn't changed
                return;
            }
        } catch (IOException e) {
            // Unable to read state file... be safe and do a backup
        }
    
        

    Si nada ha cambiado y no necesitas crear una copia de seguridad, continúa con el paso 3.

  2. Si se modificaron tus datos en comparación con oldState, escribe los datos actuales en data para crear una copia de seguridad en el almacenamiento en la nube.

    Debes escribir cada fragmento de datos como una "entidad" en BackupDataOutput. Una entidad es un registro de datos binarios plano que se identifica mediante una string de clave única. Por lo tanto, el conjunto de datos para el que creas la copia de seguridad es conceptualmente un conjunto de pares clave-valor.

    Para agregar una entidad a tu conjunto de datos de copia de seguridad, debes hacer lo siguiente:

    1. Llama a writeEntityHeader() pasando una clave de string única para los datos que estás por escribir y el tamaño de esos datos.
    2. Llama a writeEntityData() pasando un búfer de bytes que contenga tus datos y la cantidad de bytes para escribir desde el búfer (que deben coincidir con el tamaño que se pasó a writeEntityHeader()).

    Por ejemplo, el siguiente código aplana algunos datos en un flujo de bytes y los escribe en una sola entidad:

    Kotlin

        val buffer: ByteArray = ByteArrayOutputStream().run {
            DataOutputStream(this).apply {
                writeInt(playerName)
                writeInt(playerScore)
            }
            toByteArray()
        }
        val len: Int = buffer.size
        data.apply {
            writeEntityHeader(TOPSCORE_BACKUP_KEY, len)
            writeEntityData(buffer, len)
        }
    
        

    Java

        // Create buffer stream and data output stream for our data
        ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
        DataOutputStream outWriter = new DataOutputStream(bufStream);
        // Write structured data
        outWriter.writeUTF(playerName);
        outWriter.writeInt(playerScore);
        // Send the data to the Backup Manager via the BackupDataOutput
        byte[] buffer = bufStream.toByteArray();
        int len = buffer.length;
        data.writeEntityHeader(TOPSCORE_BACKUP_KEY, len);
        data.writeEntityData(buffer, len);
    
        

    Realiza esta acción para cada dato que requiera una copia de seguridad. Tú eliges la forma de dividir tus datos en entidades (y tienes la posibilidad de usar solo una entidad).

  3. Ya sea que crees o no una copia de seguridad (en el paso 2), escribe una representación de los datos actuales en newState ParcelFileDescriptor. El administrador de copias de seguridad tiene este objeto a nivel local como una representación de los datos que las tienen. Te devuelve esto como oldState la próxima vez que llama a onBackup() para que puedas determinar si se necesita otra copia de seguridad (como se procesó en el paso 1). Si no escribes el estado actual de los datos en este archivo, oldState estará vacío durante la próxima devolución de llamada.

    En el siguiente ejemplo, se guarda una representación de los datos actuales en newState mediante el uso de la última marca de tiempo modificada del archivo:

    Kotlin

        val modified = dataFile.lastModified()
        FileOutputStream(newState.fileDescriptor).also {
            DataOutputStream(it).apply {
                writeLong(modified)
            }
        }
    
        

    Java

        FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
        DataOutputStream out = new DataOutputStream(outstream);
    
        long modified = dataFile.lastModified();
        out.writeLong(modified);
    
        

Precaución: Si se guardan los datos de tu app en un archivo, asegúrate de usar instrucciones sincronizadas cuando accedas a él para que tu agente de copia de seguridad no lo lea mientras una actividad de tu app también lo esté escribiendo.

Cómo realizar un restablecimiento

Cuando sea necesario restablecer los datos de tu app, el administrador de copias de seguridad llamará al método onRestore() del agente correspondiente. Cuando llama a ese método, el administrador de copias de seguridad entrega tus datos para que puedas restablecerlos en el dispositivo.

Solo el administrador de copias de seguridad puede llamar a onRestore(), lo que sucede automáticamente cuando el sistema instala tu app y encuentra los datos de copia de seguridad existentes.

Nota: Mientras desarrollas tu app, también puedes solicitar una operación de restablecimiento con la herramienta bmgr.

Cuando el administrador de copias de seguridad llama a tu método onRestore(), pasa tres parámetros:

data
Es un BackupDataInput, que te permite leer tus datos de copia de seguridad.
appVersionCode
Es un número entero que representa el valor del atributo de manifiesto android:versionCode de tu app, igual que cuando se creó una copia de seguridad de estos datos. Puedes usar esta opción para hacer una verificación cruzada de la versión actual de la app y determinar si es compatible el formato de los datos. Para obtener más información sobre cómo usar esto para procesar diferentes versiones de datos de restablecimiento, consulta la siguiente sección Cómo verificar la versión de los datos de restablecimiento.
newState
Es un ParcelFileDescriptor abierto de lectura/escritura que apunta a un archivo en el que debes escribir el último estado de copia de seguridad que se proporcionó con data. Este objeto se mostrará como oldState la próxima vez que se llame a onBackup(). Recuerda que también debes escribir el mismo objeto newState en la devolución de llamada onBackup(); hacerlo también aquí garantiza que el objeto oldState dado a onBackup() sea válido incluso la primera vez que se llame a onBackup() después de restablecer el dispositivo.

En tu implementación de onRestore(), debes llamar a readNextHeader() en data para iterar a través de todas las entidades del conjunto de datos. Para cada entidad encontrada, haz lo siguiente:

  1. Obtén la clave de entidad con getKey().
  2. Compara la clave de entidad con una lista de valores clave conocidos que debes haber declarado como strings finales estáticas dentro de tu clase BackupAgent. Cuando la clave coincida con una de las strings de claves conocidas, escríbela en una instrucción para extraer los datos de la entidad y guardarlos en el dispositivo:
    1. Obtén el tamaño de los datos de la entidad con getDataSize() y crea un arreglo de bytes de ese tamaño.
    2. Llama a readEntityData(), pásale el arreglo de bytes, que es donde irán los datos, y especifica el desplazamiento de inicio y el tamaño que se debe leer.
    3. Ahora, tu arreglo de bytes está lleno, y puedes leer los datos y escribirlos en el dispositivo como quieras.
  3. Después de leer y escribir tus datos en el dispositivo, escribe su estado en el parámetro newState de la misma manera que lo hiciste durante onBackup().

Por ejemplo, de esta forma, puedes restablecer los datos con copia de seguridad en el ejemplo de la sección anterior:

Kotlin

    @Throws(IOException::class)
    override fun onRestore(data: BackupDataInput, appVersionCode: Int,
                           newState: ParcelFileDescriptor) {
        with(data) {
            // There should be only one entity, but the safest
            // way to consume it is using a while loop
            while (readNextHeader()) {
                when(key) {
                    TOPSCORE_BACKUP_KEY -> {
                        val dataBuf = ByteArray(dataSize).also {
                            readEntityData(it, 0, dataSize)
                        }
                        ByteArrayInputStream(dataBuf).also {
                            DataInputStream(it).apply {
                                // Read the player name and score from the backup data
                                playerName = readUTF()
                                playerScore = readInt()
                            }
                            // Record the score on the device (to a file or something)
                            recordScore(playerName, playerScore)
                        }
                    }
                    else -> skipEntityData()
                }
            }
        }

        // Finally, write to the state blob (newState) that describes the restored data
        FileOutputStream(newState.fileDescriptor).also {
            DataOutputStream(it).apply {
                writeUTF(playerName)
                writeInt(mPlayerScore)
            }
        }
    }

    

Java

    @Override
    public void onRestore(BackupDataInput data, int appVersionCode,
                          ParcelFileDescriptor newState) throws IOException {
        // There should be only one entity, but the safest
        // way to consume it is using a while loop
        while (data.readNextHeader()) {
            String key = data.getKey();
            int dataSize = data.getDataSize();

            // If the key is ours (for saving top score). Note this key was used when
            // we wrote the backup entity header
            if (TOPSCORE_BACKUP_KEY.equals(key)) {
                // Create an input stream for the BackupDataInput
                byte[] dataBuf = new byte[dataSize];
                data.readEntityData(dataBuf, 0, dataSize);
                ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
                DataInputStream in = new DataInputStream(baStream);

                // Read the player name and score from the backup data
                playerName = in.readUTF();
                playerScore = in.readInt();

                // Record the score on the device (to a file or something)
                recordScore(playerName, playerScore);
            } else {
                // We don't know this entity key. Skip it. (Shouldn't happen.)
                data.skipEntityData();
            }
        }

        // Finally, write to the state blob (newState) that describes the restored data
        FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
        DataOutputStream out = new DataOutputStream(outstream);
        out.writeUTF(playerName);
        out.writeInt(mPlayerScore);
    }

    

En este ejemplo, no se usa el parámetro appVersionCode que se pasó a onRestore(). Sin embargo, quizás quieras usarlo si has elegido crear una copia de seguridad cuando la versión del usuario de la app realmente haya retrocedido (por ejemplo, si el usuario pasó de la versión 1.5 a la 1.0). Para obtener más información, consulta la sección Cómo verificar la versión de restablecimiento de datos.

Cómo verificar la versión de restablecimiento de datos

Cuando el administrador de copias de seguridad guarda tus datos en el almacenamiento en la nube, incluye automáticamente la versión de tu app, según lo define el atributo android:versionCode de tu archivo de manifiesto. Antes de que el administrador de copias de seguridad llame a tu agente para restablecer tus datos, observa android:versionCode de la app instalada y lo compara con el valor registrado en el conjunto de datos de restablecimiento. Si la versión registrada en el conjunto de datos de restablecimiento es más nueva que la versión de la app del dispositivo, entonces el usuario cambió a una versión inferior de su app. En este caso, el administrador de copias de seguridad anulará la operación de restablecimiento de tu app y no llamará al método onRestore(), dado que el conjunto de restablecimiento no se considera significativo para una versión anterior.

Puedes anular ese comportamiento con el atributo android:restoreAnyVersion. Este atributo es "true" o "false" para indicar si quieres restablecer la app independientemente de la versión del conjunto de restablecimiento. El valor predeterminado es "false". Si defines esto como "true", el administrador de copias de seguridad ignorará android:versionCode y llamará a tu método onRestore() en todos los casos. De esta manera, puedes verificar manualmente la diferencia de versión en tu método onRestore() y tomar las medidas necesarias para que los datos sean compatibles si las versiones entran en conflicto.

Para ayudarte a procesar diferentes versiones durante una operación de restablecimiento, el método onRestore() te pasa el código de versión incluido con el conjunto de datos de restablecimiento como el parámetro appVersionCode. Luego, puedes consultar el código de versión actual de la app en el campo PackageInfo.versionCode. Por ejemplo:

Kotlin

    val info: PackageInfo? = try {
        packageManager.getPackageInfo(packageName, 0)
    } catch (e: PackageManager.NameNotFoundException) {
        null
    }

    val version: Int = info?.versionCode ?: 0

    

Java

    PackageInfo info;
    try {
        String name = getPackageName();
        info = getPackageManager().getPackageInfo(name, 0);
    } catch (NameNotFoundException nnfe) {
        info = null;
    }

    int version;
    if (info != null) {
        version = info.versionCode;
    }

    

Después, simplemente compara el version adquirido de PackageInfo con el appVersionCode que se pasó a onRestore().

Precaución: Asegúrate de comprender las consecuencias de configurar android:restoreAnyVersion como "true" para tu app. Si cada versión de tu app que admite la copia de seguridad no tiene en cuenta de forma adecuada las variaciones de tu formato de datos durante onRestore(), posiblemente se guarden los datos del dispositivo en un formato incompatible con la versión instalada.

Cómo solicitar una copia de seguridad

Puedes solicitar una operación de copia de seguridad en cualquier momento llamando a dataChanged(). Este método notifica al administrador de copias de seguridad que quieres hacer una con tu agente. Entonces, el administrador de copias de seguridad llamará al método onBackup() de tu agente en el momento oportuno en el futuro. Por lo general, debes solicitar una copia de seguridad cada vez que cambien tus datos (por ejemplo, cuando el usuario cambia una preferencia de la app que quieres conservar en la copia). Si llamas a dataChanged() varias veces consecutivas, antes de que el administrador de copias de seguridad solicite una a tu agente, ese agente recibe una sola llamada a onBackup().

Nota: Mientras desarrollas tu app, puedes solicitar una copia de seguridad e iniciarla de forma inmediata con la herramienta bmgr.

Cómo solicitar un restablecimiento

Durante la vida útil normal de tu app, no es necesario que solicites una operación de restablecimiento. El sistema verifica automáticamente los datos de copia de seguridad y realiza un restablecimiento cuando se instala la app.

Nota: Mientras desarrollas tu app, puedes solicitar una operación de restablecimiento con la herramienta bmgr.

Cómo migrar a la copia de seguridad automática

Puedes hacer la transición de tu app a copias de seguridad de datos completos mediante la configuración de android:fullBackupOnly en true en el elemento <application> del archivo de manifiesto. Cuando se ejecuta en un dispositivo con Android 5.1 (API nivel 22) o versiones anteriores, tu app ignora este valor del manifiesto y continúa creando copias de seguridad de pares clave-valor. Cuando se ejecuta en un dispositivo con Android 6.0 (API nivel 23) o versiones posteriores, tu app crea copias de seguridad automáticas en vez de copias de seguridad de pares clave-valor.

Privacidad del usuario

En Google, somos muy conscientes de la confianza que los usuarios depositan en nosotros y de nuestra responsabilidad de proteger su privacidad. Google transmite de forma segura los datos de copia de seguridad hacia y desde sus servidores para ofrecer funciones de este tipo y de restablecimiento. Google trata esos datos como información personal de acuerdo con su Política de Privacidad.

Además, los usuarios pueden inhabilitar la función de copia de seguridad de datos a través de la configuración de privacidad del sistema Android. Cuando un usuario inhabilita la copia de seguridad, Android Backup Service borra todos los datos de este tipo que se hayan guardado. Un usuario puede volver a habilitar la copia de seguridad del dispositivo, pero Android Backup Service no restablecerá ningún dato borrado anteriormente.