wake-up-neo.com

Android Sichern / Wiederherstellen: Wie wird eine interne Datenbank gesichert?

Ich habe ein BackupAgentHelper implementiert, das das bereitgestellte FileBackupHelper verwendet, um die native Datenbank, die ich habe, zu sichern und wiederherzustellen. Dies ist die Datenbank, die Sie normalerweise zusammen mit ContentProviders verwenden und die sich in /data/data/yourpackage/databases/ Befindet.

Man würde denken, dass dies ein häufiger Fall ist. In den Dokumenten ist jedoch nicht klar, was zu tun ist: http://developer.Android.com/guide/topics/data/backup.html . Speziell für diese typischen Datenbanken gibt es kein BackupHelper. Daher habe ich FileBackupHelper verwendet, auf meine .db-Datei in "/databases/" Verwiesen und Sperren für jede db-Operation (wie db.insert) in meinem ContentProviders und habe sogar versucht, das Verzeichnis "/databases/" vor onRestore() zu erstellen, da es nach der Installation nicht vorhanden ist.

Ich habe in der Vergangenheit eine ähnliche Lösung für SharedPreferences erfolgreich in einer anderen App implementiert. Wenn ich jedoch meine neue Implementierung im Emulator-2.2 teste, sehe ich, dass eine Sicherung in LocalTransport aus den Protokollen durchgeführt und eine Wiederherstellung durchgeführt wird (und onRestore() aufgerufen wird). Die Datenbankdatei selbst wird jedoch nie erstellt.

Beachten Sie, dass dies alles nach einer Installation und vor dem ersten Start der App erfolgt, nachdem die Wiederherstellung durchgeführt wurde. Abgesehen davon basierte meine Teststrategie auf http://developer.Android.com/guide/topics/data/backup.html#Testing .

Bitte beachten Sie auch, dass es sich weder um eine von mir verwaltete SQLite-Datenbank noch um ein Backup auf eine SD-Karte, einen eigenen Server oder anderswo handelt.

Ich habe in den Dokumenten eine Erwähnung über Datenbanken gesehen, die die Verwendung eines benutzerdefinierten BackupAgent empfehlen, aber es scheint nicht verwandt zu sein:

Möglicherweise möchten Sie BackupAgent jedoch direkt erweitern, wenn Sie: * Daten in einer Datenbank sichern müssen. Wenn Sie eine SQLite-Datenbank haben, die Sie wiederherstellen möchten, wenn der Benutzer Ihre Anwendung erneut installiert, müssen Sie einen benutzerdefinierten BackupAgent erstellen, der die entsprechenden Daten während eines Sicherungsvorgangs liest, dann Ihre Tabelle erstellen und die Daten während eines Wiederherstellungsvorgangs einfügen.

Bitte etwas Klarheit.

Wenn ich es wirklich selbst bis zum SQL-Level machen muss, mache ich mir Sorgen um die folgenden Themen:

  • Öffnen Sie Datenbanken und Transaktionen. Ich habe keine Ahnung, wie ich sie außerhalb des Workflows meiner App aus einer solchen Singleton-Klasse schließen kann.

  • So benachrichtigen Sie den Benutzer, dass eine Sicherung ausgeführt wird und die Datenbank gesperrt ist. Es kann lange dauern, daher muss ich möglicherweise einen Fortschrittsbalken anzeigen.

  • So machen Sie dasselbe bei der Wiederherstellung. Soweit ich weiß, kann die Wiederherstellung erst dann erfolgen, wenn der Benutzer die App bereits verwendet hat (und Daten in die Datenbank eingibt). Sie können also nicht davon ausgehen, nur die vorhandenen Daten wiederherzustellen (leere oder alte Daten löschen). Du musst irgendwie mitmachen, was für jede nicht-triviale Datenbank aufgrund der IDs unmöglich ist.

  • So aktualisieren Sie die App nach Abschluss der Wiederherstellung, ohne dass der Benutzer an einem jetzt nicht erreichbaren Punkt hängen bleibt.

  • Kann ich sicher sein, dass die Datenbank bei der Sicherung oder Wiederherstellung bereits aktualisiert wurde? Andernfalls stimmt das erwartete Schema möglicherweise nicht überein.

86
pjv

Ein sauberer Ansatz wäre, ein benutzerdefiniertes BackupHelper zu erstellen:

public class DbBackupHelper extends FileBackupHelper {

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

und füge es dann zu BackupAgentHelper hinzu:

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

Nachdem ich meine Frage überarbeitet hatte, war ich in der Lage, sie zum Laufen zu bringen, nachdem ich mir wie ConnectBot das macht angesehen hatte. Danke Kenny und Jeffrey!

Es ist eigentlich so einfach wie das Hinzufügen:

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

zu Ihrem BackupAgentHelper.

Der Punkt, den ich vermisst habe, war die Tatsache, dass Sie einen relativen Pfad mit "../databases/ ".

Dies ist jedoch keineswegs eine perfekte Lösung. Die Dokumente für FileBackupHelper erwähnen zum Beispiel: "FileBackupHelper sollte nur mit kleinen Konfigurationsdateien verwendet werden, nicht mit großen Binärdateien. ", wobei letzteres bei SQLite-Datenbanken der Fall ist.

Ich möchte mehr Vorschläge, Einblicke in das, was von uns erwartet wird (was ist die richtige Lösung), und Ratschläge, wie dies brechen könnte.

34
pjv

Hier ist noch sauberer Weg, um Datenbanken als Dateien zu sichern. Keine fest codierten Pfade.

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

Hinweis: Es überschreibt getFilesDir , so dass FileBackupHelper im Datenbankverzeichnis und nicht im Dateiverzeichnis funktioniert.

Ein weiterer Hinweis: Sie können auch databaseList verwenden, um alle Ihre DBs und Feed-Namen aus dieser Liste (ohne übergeordneten Pfad) in FileBackupHelper abzurufen. Dann würden alle DBs der App im Backup gespeichert.

22
Pointer Null

Die Verwendung von FileBackupHelper zum Sichern/Wiederherstellen der SQLite-Datenbank wirft einige ernste Fragen auf:
1. Was passiert, wenn die App den von ContentProvider.query() abgerufenen Cursor verwendet und der Sicherungsagent versucht, die gesamte Datei zu überschreiben?
2. Das Link ist ein schönes Beispiel für perfekte (niedrige Entrophie;) Tests. Sie deinstallieren die App, installieren sie erneut und das Backup wird wiederhergestellt. Das Leben kann jedoch brutal sein. Schauen Sie sich link an. Stellen wir uns ein Szenario vor, in dem ein Benutzer ein neues Gerät kauft. Da es keinen eigenen Satz hat, verwendet der Sicherungsagent den Satz eines anderen Geräts. Die App wird installiert und Ihr backupHelper ruft alte Dateien mit einem DB-Versionsschema ab, das niedriger als das aktuelle ist. SQLiteOpenHelper ruft onDowngrade mit der Standardimplementierung auf:

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

Unabhängig davon, was der Benutzer tut, kann er Ihre App auf dem neuen Gerät nicht verwenden.

Ich würde vorschlagen, ContentResolver zu verwenden, um Daten abzurufen -> serialisieren (ohne _ids) zum Sichern und Deserialisieren -> Daten zum Wiederherstellen einfügen.

Hinweis: Das Abrufen/Einfügen von Daten erfolgt über ContentResolver, wodurch Wechselkursschwankungen vermieden werden. Die Serialisierung erfolgt in Ihrem backupAgent. Wenn Sie Ihre eigene Cursor <-> -Objektzuordnung durchführen, kann das Serialisieren eines Elements so einfach sein wie das Implementieren von Serializable mit transient field _id für die Klasse, die Ihre Entität darstellt.

Ich würde auch Bulk Insert verwenden, d. H. ContentProviderOperationBeispiel und CursorLoader.setUpdateThrottle, damit die App beim Neustart des Loaders bei Datenänderungen während des Sicherungswiederherstellungsprozesses nicht hängen bleibt.

Wenn Sie sich in einer Downgrade-Situation befinden, können Sie entweder den Wiederherstellungsvorgang abbrechen oder ContentResolver mit Feldern wiederherstellen und aktualisieren, die für die heruntergestufte Version relevant sind.

Ich bin damit einverstanden, dass das Thema nicht einfach ist, in der Dokumentation nicht gut erklärt wird und einige Fragen immer noch wie die Größe von Massendaten usw. bestehen.

Hoffe das hilft.

7
Piotr Bazan

Ab Android M steht Apps jetzt eine API für die Sicherung/Wiederherstellung vollständiger Daten zur Verfügung. Diese neue API enthält eine XML-basierte Spezifikation im App-Manifest, mit der der Entwickler beschreiben kann, für welche Dateien Sichern auf direkte semantische Weise: 'Sichern der Datenbank mit dem Namen "mydata.db". Diese neue API ist für Entwickler viel einfacher zu verwenden. Sie müssen nicht die Unterschiede nachverfolgen oder explizit einen Sicherungsdurchlauf anfordern. und die XML-Beschreibung der zu sichernden Dateien bedeutet, dass Sie häufig überhaupt keinen Code schreiben müssen.

(Sie können sogar an einer vollständigen Datensicherung/-wiederherstellung teilnehmen, um beispielsweise einen Rückruf zu erhalten, wenn die Wiederherstellung erfolgt. Dies ist flexibel. )

Im Abschnitt Konfigurieren der automatischen Sicherung für Apps auf developer.Android.com finden Sie eine Beschreibung der Verwendung der neuen API.

3
ctate

Eine Möglichkeit besteht darin, sie in die Anwendungslogik über der Datenbank zu integrieren. Ich denke, es schreit tatsächlich nach einem solchen Level. Ich bin mir nicht sicher, ob Sie es bereits tun, aber die meisten Leute (trotz des Android Content Manager-Cursor-Ansatzes)) werden ein ORM-Mapping einführen - entweder einen benutzerdefinierten oder einen orm-lite-Ansatz Dieser Fall ist:

  1. um sicherzustellen, dass Ihre Anwendung einwandfrei funktioniert, wenn die App/Daten im Hintergrund hinzugefügt werden und neue Daten hinzugefügt/entfernt werden, während die Anwendung bereits gestartet ist
  2. um Java-> Protobuf zu erstellen oder einfach Java Serialization Mapping) zu erstellen und einen eigenen BackupHelper zu schreiben, um die Daten aus dem Stream zu lesen und einfach zur Datenbank hinzuzufügen ....

In diesem Fall tun Sie dies also nicht auf Datenbankebene, sondern auf Anwendungsebene.

0
Jarek Potiuk