86 votes

Sauvegarde/restauration Android : comment sauvegarder une base de données interne?

J'ai implémenté un BackupAgentHelper en utilisant le FileBackupHelper fourni pour sauvegarder et restaurer la base de données native que j'ai. Il s'agit de la base de données que vous utilisez généralement avec les ContentProviders et qui réside dans /data/data/votrepackage/databases/.

On pourrait penser que c'est un cas courant. Cependant, la documentation n'est pas claire sur ce qu'il faut faire : http://developer.android.com/guide/topics/data/backup.html. Il n'y a pas de BackupHelper spécifique pour ces bases de données typiques. J'ai donc utilisé le FileBackupHelper, en lui indiquant mon fichier .db dans "/databases/", en introduisant des verrous autour de toute opération de base de données (comme db.insert) dans mes ContentProviders, et même essayé de créer le répertoire "/databases/" avant onRestore() car il n'existe pas après l'installation.

J'ai mis en place une solution similaire pour les SharedPreferences avec succès dans une autre application par le passé. Cependant, lorsque je teste ma nouvelle implémentation dans l'émulateur-2.2, je vois une sauvegarde être effectuée vers LocalTransport depuis les logs, ainsi qu'une restauration (et l'appel à onRestore()). Cependant, le fichier de base de données en lui-même n'est jamais créé.

Remarquez que tout cela se produit après une installation, et avant le premier lancement de l'application, après la restauration. En dehors de cela, ma stratégie de test était basée sur http://developer.android.com/guide/topics/data/backup.html#Testing.

Notez également que je ne parle pas d'une base de données SQLite que je gère moi-même, ni de sauvegarde sur une carte SD, un serveur personnel ou ailleurs.

J'ai vu une mention dans la documentation concernant les bases de données conseillant d'utiliser un BackupAgent personnalisé, mais cela ne semble pas être lié :

Cependant, vous voudrez peut-être étendre directement BackupAgent si vous avez besoin de : * Sauvegarder des données dans une base de données. Si vous avez une base de données SQLite que vous souhaitez restaurer lorsque l'utilisateur réinstalle votre application, vous devez construire un BackupAgent personnalisé qui lit les données appropriées pendant une opération de sauvegarde, puis crée votre table et insère les données pendant une opération de restauration.

Un peu de clarté s'il vous plaît.

Si je dois vraiment le faire moi-même jusqu'au niveau SQL, alors je m'inquiète des points suivants :

  • Ouvrir des bases de données et des transactions. Je ne sais pas comment les fermer à partir d'une classe singleton en dehors du flux de mon application.

  • Comment informer l'utilisateur qu'une sauvegarde est en cours et que la base de données est verrouillée. Cela peut prendre du temps, donc je pourrais avoir besoin d'afficher une barre de progression.

  • Comment faire la même chose lors de la restauration. Comme je le comprends, la restauration peut se produire lorsque l'utilisateur a déjà commencé à utiliser l'application (et à saisir des données dans la base de données). Vous ne pouvez donc pas supposer simplement restaurer les données sauvegardées en place (en supprimant les données vides ou anciennes). Vous devrez d'une manière ou d'une autre les fusionner, ce qui est impossible pour toute base de données non-triviale en raison des identifiants.

  • Comment rafraîchir l'application une fois la restauration terminée sans que l'utilisateur se retrouve bloqué à un point désormais inaccessible.

  • Puis-je être sûr que la base de données a déjà été mise à jour lors de la sauvegarde ou de la restauration ? Sinon, le schéma attendu peut ne pas correspondre.

34voto

pjv Points 4527

Après avoir revu ma question, j'ai réussi à le faire fonctionner en regardant comment ConnectBot le fait. Merci Kenny et Jeffrey !

En fait, c'est aussi simple que d'ajouter :

FileBackupHelper hosts = new FileBackupHelper(this,
    "../databases/" + HostDatabase.DB_NAME);
addHelper(HostDatabase.DB_NAME, hosts);

à votre BackupAgentHelper.

Le point que je manquais était le fait que vous devriez utiliser un chemin relatif avec "../databases/".

Cependant, ce n'est en aucun cas une solution parfaite. Les docs pour FileBackupHelper mentionnent par exemple : "FileBackupHelper devrait être utilisé uniquement avec de petits fichiers de configuration, et non de grands fichiers binaires.", ce qui est le cas avec les bases de données SQLite.

J'aimerais avoir plus de suggestions, d'aperçus de ce qui est attendu de notre part (quelle est la solution appropriée), et des conseils sur la façon dont cela pourrait ne pas fonctionner.

22voto

Pointer Null Points 14229

Voici une manière encore plus propre de sauvegarder des bases de données en tant que fichiers. Aucun chemin codé en dur.

class MyBackupAgent extends BackupAgentHelper{
   private static final String DB_NAME = "my_db";

   @Override
   public void onCreate(){
      FileBackupHelper dbs = new FileBackupHelper(this, DB_NAME);
      addHelper("dbs", dbs);
   }

   @Override
   public File getFilesDir(){
      File path = getDatabasePath(DB_NAME);
      return path.getParentFile();
   }
}

Remarque: cela remplace getFilesDir afin que FileBackupHelper fonctionne dans le répertoire des bases de données, pas dans le répertoire des fichiers.

Autre astuce: vous pouvez également utiliser databaseList pour obtenir toutes vos bases de données et extraire les noms de cette liste (sans le chemin parent) pour les alimenter dans FileBackupHelper. Ainsi, toutes les bases de données de l'application seront sauvegardées.

21voto

yanchenko Points 24142

Une approche plus propre serait de créer un BackupHelper personnalisé :

public class DbBackupHelper extends FileBackupHelper {

    public DbBackupHelper(Context ctx, String dbName) {
        super(ctx, ctx.getDatabasePath(dbName).getAbsolutePath());
    }
}

puis l'ajouter à BackupAgentHelper :

public void onCreate() {
    addHelper(DATABASE, new DbBackupHelper(this, DB.FILE));
}

7voto

Piotr Bazan Points 101

Utiliser FileBackupHelper pour sauvegarder/restaurer la base de données sqlite pose quelques questions sérieuses:
1. Que se passe-t-il si l'application utilise un curseur récupéré de ContentProvider.query() et que l'agent de sauvegarde tente d'écraser tout le fichier?
2. Le lien est un bel exemple de test parfait (faible entropie ;) ). Vous désinstallez l'application, la réinstallez et la sauvegarde est restaurée. Cependant la vie peut être brutale. Jetez un œil à ce lien. Imaginons un scénario où un utilisateur achète un nouvel appareil. Comme il n'a pas son propre ensemble, l'agent de sauvegarde utilise l'ensemble de l'autre appareil. L'application est installée et votre backupHelper récupère l'ancien fichier avec un schéma de version de base de données inférieur à la version actuelle. SQLiteOpenHelper appelle onDowngrade avec l'implémentation par défaut:

public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    throw new SQLiteException("Ne peut pas rétrograder la base de données de la version " +
            oldVersion + " à " + newVersion);
}

Peu importe ce que l'utilisateur fait, il/elle ne peut pas utiliser votre application sur le nouvel appareil.

Je suggère d'utiliser ContentResolver pour obtenir des données -> sérialiser (sans les _ids) pour la sauvegarde et désérialiser -> insérer les données pour la restauration.

Remarque : obtenir/insérer les données se fait via ContentResolver évitant ainsi les problèmes de concurrence. La sérialisation est faite dans votre backupAgent. Si vous faites votre propre mapping curseur<->objet, la sérialisation d'un élément peut être aussi simple que d'implémenter Serializable avec le champ transient _id sur la classe représentant votre entité.

J'utiliserais également l'insertion en masse c'est-à-dire ContentProviderOperation exemple et CursorLoader.setUpdateThrottle afin que l'application ne reste pas bloquée en redémarrant le chargeur lors d'un changement de données pendant le processus de restauration de sauvegarde.

Si vous vous trouvez dans une situation de rétrogradation, vous pouvez choisir soit d'annuler la restauration des données, soit de restaurer et mettre à jour ContentResolver avec les champs pertinents pour la version rétrogradée.

Je suis d'accord que le sujet n'est pas facile, pas bien expliqué dans la documentation et certaines questions restent en suspens comme la taille des données en vrac, etc.

J'espère que cela vous aidera.

0voto

Jarek Potiuk Points 5104

Une option sera de le construire dans la logique de l'application au-dessus de la base de données. Cela en a vraiment besoin, je pense. Je ne suis pas sûr si vous le faites déjà mais la plupart des gens (malgré l'approche android content manager) introduiront une certaine cartographie ORM - soit personnalisée soit une approche orm-lite. Et ce que je ferais plutôt dans ce cas est :

  1. de vous assurer que votre application fonctionne bien lorsque l'application/les données sont ajoutées en arrière-plan avec de nouvelles données ajoutées/supprimées pendant que l'application est déjà démarrée
  2. de faire un mappage Java->protobuf ou même simplement java serialization et écrire votre propre BackupHelper pour lire les données de l'écoulement et simplement les ajouter à la base de données....

Alors dans ce cas plutôt que de le faire au niveau de la base de données, faites-le au niveau de l'application.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X