Androidのバックアップ/復元:内部データベースをバックアップする方法は?


86

BackupAgentHelper提供されているを使用して、所有してFileBackupHelperいるネイティブデータベースをバックアップおよび復元するように実装しました。これは、通常一緒に使用するデータベースでContentProvidersあり、に存在し/data/data/yourpackage/databases/ます。

これは一般的なケースだと思うでしょう。ただし、ドキュメントでは何をすべきかが明確ではありません:http//developer.android.com/guide/topics/data/backup.htmlこれらの典型的なデータベースに特化したものはありませんBackupHelper。したがって、を使用しFileBackupHelper、「/databases/」の.dbファイルをポイントし、「」のdb操作(などdb.insert)の周りにロックを導入し、インストール後に存在しないため、以前にContentProviders/databases/」ディレクトリを作成しようとしましたonRestore()

私はSharedPreferences過去に別のアプリで成功するために同様のソリューションを実装しました。ただし、emulator-2.2で新しい実装をテストするLocalTransportと、ログからのバックアップが実行され、復元が実行されている(およびonRestore()呼び出されている)ことがわかります。ただし、dbファイル自体は作成されません。

これはすべて、インストール後、アプリの最初の起動前、復元の実行後であることに注意してください。それとは別に、私のテスト戦略はhttp://developer.android.com/guide/topics/data/backup.html#Testingに基づいていました。

また、自分で管理しているsqliteデータベースについても、SDカードや独自のサーバーなどへのバックアップについても話していません。

カスタムの使用を推奨するデータベースについての言及をドキュメントで見ましたが、BackupAgent関連していないようです。

ただし、次のことが必要な場合は、BackupAgentを直接拡張することをお勧めします。*データベース内のデータをバックアップする。ユーザーがアプリケーションを再インストールしたときに復元するSQLiteデータベースがある場合は、バックアップ操作中に適切なデータを読み取るカスタムBackupAgentを構築してから、テーブルを作成し、復元操作中にデータを挿入する必要があります。

少し明快にしてください。

本当にSQLレベルまで自分で行う必要がある場合は、次のトピックについて心配しています。

  • データベースとトランザクションを開きます。アプリのワークフローの外で、このようなシングルトンクラスからそれらを閉じる方法がわかりません。

  • バックアップが進行中であり、データベースがロックされていることをユーザーに通知する方法。時間がかかる場合があるので、プログレスバーを表示する必要があるかもしれません。

  • 復元時に同じことを行う方法。私が理解しているように、復元は、ユーザーがすでにアプリの使用を開始したとき(およびデータベースにデータを入力したとき)に発生する可能性があります。したがって、バックアップされたデータを元の場所に復元する(空のデータまたは古いデータを削除する)だけだと推測することはできません。どういうわけかそれに参加する必要がありますが、IDが原因で、重要なデータベースでは不可能です。

  • 復元が完了した後、ユーザーが到達不能な時点で立ち往生することなくアプリを更新する方法。

  • データベースがバックアップまたは復元時にすでにアップグレードされていることを確認できますか?そうしないと、期待されるスキーマが一致しない可能性があります。


私は...同じ問題を持っている

単純なバックアップホールデータベースの方法はありませんか?
Jin35 2011

FileBackupHelperを使用してフォルダをバックアップする必要があります。データベースの保存アプローチを使用すると、その下に多数のサブフォルダーとサブファイルがあるフォルダーを保存できますか?
coolcool1994 2014

これを正しく機能させたことがありますか?
DeNitE Appz 2015

はい、以下を参照してください。私も自分の質問に答えました。
pjv 2015

回答:


21

よりクリーンなアプローチは、カスタムを作成することですBackupHelper

public class DbBackupHelper extends FileBackupHelper {

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

そしてそれをに追加します BackupAgentHelperます:

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

2
@logray:そのコードは質問とまったく同じです。不必要な編集。
Linuxios 2013

@Linuxios同意しますが、コードを盲目的に貼り付けるよりも、作者に適切な帰属を提供する方が良いと思います...元のOP /投稿に彼のアプリからのリンクが含まれていることを考慮してください。
logray 2013

3
重要:ファイルをバックアップするための(ドキュメント)(developer.android.com/guide/topics/data/backup.html#Files)には、操作がスレッドセーフではないと記載されているため、データベースメソッドとバックアップエージェントを同期する必要があります
nicopico

FileBackupHelperの@yanchenkoドキュメントには、「注:これは、大きなバイナリファイルではなく、小さな構成ファイルでのみ使用する必要があります」と記載されています。だから、データベースがしばしば...大きなバイナリファイルとしての資格う
IgorGanapolsky

これは実際には機能しません!(私が何かを逃していない限り...)。問題は、dbの絶対パスをFileBackupHelperに渡しても、FileBackupHelperがパスの前にファイルディレクトリパスを追加することです。data / data / <your package> / filesは、data / data / <your package> / files / data / data / <your package> / databases / <DB Name>のような間違ったパスになりますが、必要なのはDBの絶対名。スーパークラスはファイルディレクトリの先頭にあるため、ファイルディレクトリからの相対パスを作成する必要があります。
ビットロック2014

34

私の質問を再検討した後、ConnectBotがどのように機能するかを調べた後、それを機能させることができまし。ケニーとジェフリーに感謝します!

実際には、次を追加するのと同じくらい簡単です。

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

あなたにBackupAgentHelper

私が見逃していた点は、「../databases/」で相対パスを使用する必要があるという事実でした。

それでも、これは決して完璧な解決策ではありません。FileBackupHelperたとえば、言及するドキュメント:「FileBackupHelper大きなバイナリファイルではなく、小さな構成ファイルでのみ使用する必要があります。」後者はSQLiteデータベースの場合です。

より多くの提案、私たちに期待されていること(適切な解決策は何か)についての洞察、およびこれがどのように壊れるかについてのアドバイスを取得したいと思います。


1
これは少し非公式ですが、これまでずっとうまく機能していたことを付け加えておきます。
pjv 2011

6
パスをハードコーディングするのは悪です。
ポインタヌル

@pjv、それで、すべてのdbインタラクションを同期させますか?私は同じことをしていて、ステートメントだけで同期db.open()する必要があるのか、それとも何を同期する必要があるのか​​を理解しようとしています。db.close()insert
nSouth 2014年

22

データベースをファイルとしてバックアップするさらにクリーンな方法があります。ハードコードされたパスはありません。

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

注:FileBackupHelperがファイルディレクトリではなくデータベースディレクトリで機能するように、getFilesDirをオーバーライドします。

別のヒント:databaseListを使用して、すべてのDBとフィード名をこのリスト(親パスなし)からFileBackupHelperに取得することもできます。次に、すべてのアプリのDBがバックアップに保存されます。


これを@pjvによって与えられた解決策と組み合わせると完全に機能します!仕様が意図していたものとは正確に一致しないかもしれません...しかし、それは非常にうまく機能します!
トム

1
バックアップするデータベース通常のファイルがある場合、これはあまり役に立ちません。
Dan Hulme 2013年

1
@Dan:その場合は複数のエージェントを使用してください。
pjv 2013年

@Pointer Null getFilesDir()をもう少し詳しく説明していただけますか。なぜそれが本当に必要なのか、そしてその理由は何ですか。それはどのように機能しますか?
Powder366 2016年

7

FileBackupHelpersqlite dbのバックアップ/復元に使用すると、いくつかの深刻な問題
が発生します。1。アプリが取得したカーソルを使用しContentProvider.query()、バックアップエージェントがファイル全体を上書きしようとするとどうなりますか?
2.リンクは、完璧な(低エントロピー;)テストの良い例です。アプリをアンインストールして再度インストールすると、バックアップが復元されます。しかし、人生は残酷なものになる可能性があります。リンクを見てください。ユーザーが新しいデバイスを購入するシナリオを想像してみましょう。独自のセットがないため、バックアップエージェントは他のデバイスのセットを使用します。アプリがインストールされ、backupHelperが現在よりも低いdbバージョンスキーマを持つ古いファイルを取得します。デフォルトの実装でのSQLiteOpenHelper呼び出しonDowngrade

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

ユーザーが何をしても、新しいデバイスでアプリを使用することはできません。

ContentResolverデータの取得->_idバックアップ用のシリアル化(sなし)および逆シリアル化->復元用のデータの挿入に使用することをお勧めします。

注:データの取得/挿入はContentResolverを介して行われるため、通貨の問題を回避できます。シリアル化はbackupAgentで行われます。あなたがあなた自身のカーソルを行う場合は、< - >アイテムをシリアライズオブジェクトマッピングは、実装としてシンプルにすることができSerializabletransient、あなたのエンティティを表すクラスにフィールド_id。

また、一括挿入、つまりContentProviderOperation CursorLoader.setUpdateThrottle使用して、バックアップの復元プロセス中にデータが変更されたときにアプリがローダーの再起動でスタックしないようにします。

ダウングレードの状況が発生した場合は、復元データを中止するか、ダウングレードされたバージョンに関連するフィールドでContentResolverを復元および更新するかを選択できます。

私は、主題が簡単ではなく、ドキュメントで十分に説明されておらず、バルクデータサイズなどのいくつかの質問がまだ残っていることに同意します。

お役に立てれば。


あなたはいくつかの有効な質問を繰り返します。しかし、データベースをシリアル化することで問題がどのように解決されるかわかりません。並行性の問題には役立ちません。また、データベースをダウングレードするためのスクリプトを作成できない場合は、古いデータを逆シリアル化するためのスクリプトを作成することはできません。
pjv 2013年

@pjv ContentResolverを使用すると、並行性の問題が解決されます。
IgorGanapolsky 2014年

@IgorGanapolskyはい、それは本当だと思います。ただし、シリアル化が十分に遅く、ユーザーがすでにアプリを使用してデータを入力している場合は、復元するデータと競合する可能性があります。ユーザーがアプリにアクセスする前に、アトミックにそれを行うことはできますか?
pjv 2014年

@pjv ContentObserverを登録して、通知を受けるために監視しているデータを同期することができます。したがって、競合について心配する必要はありません。
IgorGanapolsky 2014年

DBバージョンについてのあなたのポイントは良いものです-DBバージョンを保存すると(たとえば、共有設定で、それをバックアップすると仮定して)、復元するときに、既存のロジックを使用してDBをそのにアップグレードできるはずですonDowngrade()メソッドの現在のバージョン。アップグレードコードをまだ持っていない場合は、とにかくデータを保持することに悩まされることはありません!
ベン・ニール

3

Android Mの時点で、アプリで利用できる完全なデータのバックアップ/復元APIがあります。この新しいAPIには、アプリマニフェストにXMLベースの仕様が含まれています。これにより、開発者はバックアップするファイルを直接セマンティックな方法で記述できます。 '「mydata.db」というデータベースをバックアップします。この新しいAPIは、開発者にとってはるかに使いやすくなっています。差分を追跡したり、バックアップパスを明示的に要求したりする必要はありません。また、バックアップするファイルのXML記述により、コードを記述する必要がないことがよくあります。まったく。

(あなたがすることができさえ、フルデータバックアップに巻き込ま/例えば、たまたま、復元時にコールバックを取得するために、操作を復元します。これは、そのように柔軟です。)

新しいAPIの使用方法の説明については、developer.android.comの「アプリの自動バックアップ構成」セクションを参照してください。


これはAndroid6.0(API 23)の場合のみで、ユーザーが古いバージョンを使用している場合でも、キー値のバックアップを実装する必要があります。
ライブラブ

0

1つのオプションは、データベースの上のアプリケーションロジックでビルドすることです。それは実際に私が思うそのようなlevellのために悲鳴を上げます。すでに行っているかどうかはわかりませんが、ほとんどの人は(Androidコンテンツマネージャーのカーソルアプローチにもかかわらず)、カスタムまたはorm-liteアプローチのいずれかのORMマッピングを導入します。そして、この場合、私がむしろやりたいことは次のとおりです。

  1. アプリ/データがバックグラウンドで追加され、アプリケーションの起動中に新しいデータが追加/削除されたときに、アプリケーションが正常に動作することを確認します
  2. Java-> protobufまたは単にJavaシリアル化マッピングを作成し、独自のBackupHelperを作成して、ストリームからデータを読み取り、データベースに追加するだけです。

したがって、この場合、dbレベルで実行するのではなく、アプリケーションレベルで実行します。


問題は、データベースからBackupHelperAgentにデータを取得する方法やその逆ではありません。私の質問の最後にある5つの箇条書きを読んでください。
pjv 2011年

@JarekPotiukあなたは言った:「ほとんどの人(Androidコンテンツマネージャーのカーソルアプローチにもかかわらず)」。しかし、ほとんどの人がデータベースアクセスにContentProviderとContentResolverを使用することを避けると思う理由は何ですか?
IgorGanapolsky 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.