web-dev-qa-db-fra.com

Android sauvegarde / restauration: 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 ContentProviders et qui réside dans /data/data/yourpackage/databases/.

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

J'ai déjà implémenté une solution similaire pour 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 en cours d'exécution vers LocalTransport à partir des journaux, ainsi qu'une restauration en cours d'exécution (et onRestore() appelée). Pourtant, le fichier db lui-même n'est jamais créé.

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

Veuillez également noter que je ne parle pas d'une base de données sqlite que je gère moi-même, ni de la sauvegarde sur SDcard, sur son propre serveur ou ailleurs.

J'ai vu une mention dans les documents sur les bases de données conseillant d'utiliser un BackupAgent personnalisé mais cela ne semble pas lié:

Cependant, vous souhaiterez peut-être étendre BackupAgent directement si vous devez: * 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 créer un BackupAgent personnalisé qui lit les données appropriées lors d'une opération de sauvegarde, puis créez votre table et insérez les données lors d'une opération de restauration.

Une certaine clarté s'il vous plaît.

Si j'ai vraiment besoin de le faire moi-même jusqu'au niveau SQL, je m'inquiète pour les sujets suivants:

  • Ouvrir des bases de données et des transactions. Je ne sais pas comment les fermer à partir d'une telle classe singleton en dehors du flux de travail 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 beaucoup de temps, il se peut donc que j'aie besoin d'afficher une barre de progression.

  • Comment faire de même lors de la restauration. Si je comprends bien, la restauration peut se produire juste au moment où l'utilisateur a déjà commencé à utiliser l'application (et à entrer des données dans la base de données). Vous ne pouvez donc pas présumer de simplement restaurer les données sauvegardées en place (supprimer les données vides ou anciennes). Vous devrez en quelque sorte le rejoindre, ce qui est impossible pour toute base de données non triviale en raison des identifiants.

  • Comment actualiser l'application une fois la restauration terminée sans bloquer l'utilisateur à un moment - maintenant - inaccessible.

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

86
pjv

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 ajoutez-le à BackupAgentHelper:

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

Après avoir revu ma question, j'ai pu la faire fonctionner après avoir regardé comment ConnectBot le fait . Merci Kenny et Jeffrey!

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 qui me manquait était le fait que vous deviez utiliser un chemin relatif avec "../databases/ ".

Pourtant, ce n'est en aucun cas une solution parfaite. Les documents pour FileBackupHelper mentionnent par exemple: "FileBackupHelper ne doit être utilisé qu'avec de petits fichiers de configuration, pas de gros fichiers binaires. ", ce dernier étant le cas des bases de données SQLite.

J'aimerais avoir plus de suggestions, un aperçu de ce qu'on attend de nous (quelle est la bonne solution) et des conseils sur la façon dont cela pourrait se briser.

34
pjv

Voici un moyen encore plus propre de sauvegarder des bases de données sous forme de 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: il remplace getFilesDir pour que FileBackupHelper fonctionne dans le répertoire des bases de données, pas dans le répertoire des fichiers.

Un autre indice: vous pouvez également utiliser databaseList pour obtenir tous vos noms de bases de données et de flux de cette liste (sans chemin parent) dans FileBackupHelper. Ensuite, toutes les bases de données de l'application seraient enregistrées dans la sauvegarde.

22
Pointer Null

L'utilisation de FileBackupHelper pour sauvegarder/restaurer sqlite db soulève de sérieuses questions:
1. Que se passe-t-il si l'application utilise le curseur récupéré de ContentProvider.query() et que l'agent de sauvegarde essaie de remplacer le fichier entier?
2. Le lien est un bel exemple de test parfait (faible entrophie;). Vous désinstallez l'application, réinstallez-la et la sauvegarde est restaurée. Mais la vie peut être brutale. Jetez un oeil à lien . Imaginons un scénario lorsqu'un utilisateur achète un nouvel appareil. Puisqu'il n'a pas son propre ensemble, l'agent de sauvegarde utilise l'ensemble d'un autre appareil. L'application est installée et votre backupHelper récupère l'ancien fichier avec un schéma de version db inférieur à l'actuel. SQLiteOpenHelper appelle onDowngrade avec l'implémentation par défaut:

public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    throw new SQLiteException("Can't downgrade database from version " +
            oldVersion + " to " + newVersion);
}

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

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

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

J'utiliserais également l'insertion en masse, c'est-à-dire ContentProviderOperationexemple et CursorLoader.setUpdateThrottle afin que l'application ne soit pas bloquée par le redémarrage du chargeur lors du changement de données pendant le processus de restauration de la sauvegarde.

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

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

J'espère que cela t'aides.

7
Piotr Bazan

Depuis Android M, une API de sauvegarde/restauration de données complètes est désormais disponible pour les applications. Cette nouvelle API inclut une spécification basée sur XML dans le manifeste de l'application qui permet au développeur de décrire les fichiers à sauvegardez de manière sémantique directe: 'sauvegardez la base de données appelée "mydata.db"'. Cette nouvelle API est beaucoup plus facile à utiliser pour les développeurs - vous n'avez pas besoin de suivre les différences ni de demander explicitement une passe de sauvegarde, et la description XML des fichiers à sauvegarder signifie que vous n'avez souvent pas besoin d'écrire de code.

(Vous pouvez vous impliquer même dans une opération de sauvegarde/restauration de données complètes pour obtenir un rappel lorsque la restauration se produit, par exemple. C'est flexible de cette façon. )

Voir la section Configuration de la sauvegarde automatique pour les applications sur developer.Android.com pour une description de l'utilisation de la nouvelle API.

3
ctate

Une option sera de le construire dans la logique d'application au-dessus de la base de données. Il crie en fait pour un tel levell je pense. Je ne sais pas si vous le faites déjà, mais la plupart des gens (malgré Android approche du curseur du gestionnaire de contenu) introduiront un mappage ORM - personnalisé ou une approche orm-lite. Et ce que je préfère faire dans ce cas est:

  1. pour vous assurer que votre application fonctionne correctement lorsque l'application/les données sont ajoutées en arrière-plan avec de nouvelles données ajoutées/supprimées alors que l'application a déjà démarré
  2. pour faire du Java-> protobuf ou même simplement Java mappage de sérialisation et écrire votre propre BackupHelper pour lire les données du flux et simplement les ajouter à la base de données ....

Dans ce cas, plutôt que de le faire au niveau db, faites-le au niveau de l'application.

0
Jarek Potiuk