Android backup / restore: bagaimana cara membackup database internal?

86

Saya telah menerapkan BackupAgentHelpermenggunakan yang disediakan FileBackupHelperuntuk membuat cadangan dan memulihkan database asli yang saya miliki. Ini adalah database yang biasanya Anda gunakan bersama ContentProvidersdan yang berada di dalamnya /data/data/yourpackage/databases/.

Orang akan mengira ini adalah kasus yang umum. Namun dokumen tersebut tidak menjelaskan apa yang harus dilakukan: http://developer.android.com/guide/topics/data/backup.html . Tidak ada BackupHelperdatabase khusus untuk tipikal ini. Oleh karena itu saya menggunakanFileBackupHelper , mengarahkannya ke file .db saya di " /databases/", memperkenalkan kunci di sekitar operasi db (seperti db.insert) di my ContentProviders, dan bahkan mencoba membuat /databases/direktori " " sebelumnya onRestore()karena tidak ada setelah instalasi.

Saya telah menerapkan solusi serupa untuk SharedPreferencesberhasil di aplikasi yang berbeda di masa lalu. Namun ketika saya menguji implementasi baru saya di emulator-2.2, saya melihat backup sedang dilakukan ke LocalTransportdari log, serta pemulihan sedang dilakukan (dan onRestore()dipanggil). Namun, file db itu sendiri tidak pernah dibuat.

Perhatikan bahwa ini semua setelah penginstalan, dan sebelum peluncuran pertama aplikasi, setelah pemulihan dilakukan. Selain itu, strategi pengujian saya didasarkan pada http://developer.android.com/guide/topics/data/backup.html#Testing .

Harap perhatikan juga bahwa saya tidak berbicara tentang beberapa database sqlite yang saya kelola sendiri, atau tentang mencadangkan ke SDcard, server sendiri, atau di tempat lain.

Saya memang melihat penyebutan di dokumen tentang database yang menyarankan untuk menggunakan kustom BackupAgenttetapi tampaknya tidak terkait:

Namun, Anda mungkin ingin memperluas BackupAgent secara langsung jika Anda perlu: * Mencadangkan data dalam database. Jika Anda memiliki database SQLite yang ingin Anda pulihkan saat pengguna menginstal ulang aplikasi Anda, Anda perlu membuat BackupAgent kustom yang membaca data yang sesuai selama operasi pencadangan, kemudian membuat tabel dan memasukkan data tersebut selama operasi pemulihan.

Mohon kejelasannya.

Jika saya benar-benar perlu melakukannya sendiri hingga level SQL, maka saya khawatir tentang topik berikut:

  • Buka database dan transaksi. Saya tidak tahu cara menutupnya dari kelas tunggal di luar alur kerja aplikasi saya.

  • Cara memberi tahu pengguna bahwa cadangan sedang berlangsung dan database terkunci. Mungkin butuh waktu lama, jadi saya mungkin perlu menampilkan bilah kemajuan.

  • Cara melakukan hal yang sama pada pemulihan. Seperti yang saya pahami, pemulihan mungkin terjadi tepat ketika pengguna sudah mulai menggunakan aplikasi (dan memasukkan data ke dalam database). Jadi Anda tidak bisa berasumsi untuk hanya mengembalikan data yang dicadangkan di tempatnya (menghapus data kosong atau lama). Anda harus entah bagaimana bergabung dengannya, yang tidak mungkin untuk database non-sepele karena id.

  • Cara menyegarkan aplikasi setelah pemulihan selesai tanpa membuat pengguna terjebak di beberapa titik - yang sekarang - tidak dapat dijangkau.

  • Dapatkah saya memastikan bahwa database telah diupgrade saat melakukan backup atau restore? Jika tidak, skema yang diharapkan mungkin tidak cocok.

pjv
sumber
Saya memiliki masalah yang sama ...
Tidak ada cara lubang db cadangan sederhana?
Jin35
Saya perlu mencadangkan folder menggunakan FileBackupHelper. Akankah menggunakan pendekatan penyimpanan database memungkinkan saya untuk menyimpan folder yang memiliki banyak sub folder dan sub file di bawahnya?
coolcool1994
Apakah Anda pernah mendapatkan ini bekerja dengan benar?
DeNitE Appz
Ya, lihat di bawah. Saya menjawab pertanyaan saya sendiri juga.
pjv

Jawaban:

21

Pendekatan yang lebih bersih adalah membuat kustom BackupHelper:

public class DbBackupHelper extends FileBackupHelper {

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

lalu tambahkan ke BackupAgentHelper:

public void onCreate() {
    addHelper(DATABASE, new DbBackupHelper(this, DB.FILE));
}
yanchenko
sumber
2
@logray: Kode itu sama persis dengan yang ada di pertanyaan. Pengeditan yang tidak perlu.
Linuxios
@Linuxios Saya setuju, namun menurut saya lebih baik memberikan atribusi yang tepat kepada penulis, daripada hanya menempelkan kode secara membabi buta ... mengingat OP / posting asli menyertakan tautan dari aplikasinya.
logray
3
Penting : (dokumentasi) ( developer.android.com/guide/topics/data/backup.html#Files ) untuk mencadangkan file menyatakan bahwa operasi tersebut tidak aman untuk thread , jadi Anda harus menyinkronkan metode Database dan agen cadangan Anda
nicopico
Dokumentasi @yanchenko untuk FileBackupHelper menyatakan: "Catatan: Ini harus digunakan hanya dengan file konfigurasi kecil, bukan file biner besar." Jadi database sering kali memenuhi syarat sebagai file biner besar ...
IgorGanapolsky
Ini sebenarnya tidak berhasil! (kecuali saya melewatkan sesuatu ...). Masalahnya adalah Anda meneruskan jalur absolut db ke FileBackupHelper tetapi FileBackupHelper menambahkan jalur dengan jalur direktori file misalnya. data / data / <your package> / files yang menghasilkan jalur yang salah seperti data / data / <your package> / files / data / data / <your package> / databases / <DB Name> ketika yang Anda inginkan hanyalah Nama absolut DB dan karena kelas super menambahkan direktori file, Anda perlu membuat jalur relatif ke direktori file.
bitrock
34

Setelah meninjau kembali pertanyaan saya, saya bisa membuatnya berfungsi setelah melihat bagaimana ConnectBot melakukannya . Terima kasih Kenny dan Jeffrey!

Ini sebenarnya semudah menambahkan:

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

untuk Anda BackupAgentHelper.

Hal yang saya lewatkan adalah kenyataan bahwa Anda harus menggunakan jalur relatif dengan " ../databases/".

Namun, ini bukanlah solusi yang sempurna. Dokumen tersebut FileBackupHelpermisalnya: " FileBackupHelperharus digunakan hanya dengan file konfigurasi kecil, bukan file biner besar. ", Yang terakhir adalah kasus dengan database SQLite.

Saya ingin mendapatkan lebih banyak saran, wawasan tentang apa yang diharapkan dari kita (apa solusi yang tepat), dan saran tentang bagaimana ini bisa rusak.

pjv
sumber
1
Saya mungkin harus menambahkan, bahwa meskipun ini agak tidak resmi, ini / telah bekerja dengan sangat baik selama ini.
pjv
6
Jalur pengkodean keras itu jahat.
Pointer Null
@pjv, Jadi, apakah Anda membuat setiap interaksi db disinkronkan? Saya melakukan hal yang sama dan mencoba mencari tahu apakah saya perlu menyinkronkan db.open()melalui db.close()atau hanya insertpernyataan atau apa.
NSouth
22

Berikut cara yang lebih bersih untuk mem-backup database sebagai file. Tidak ada jalur hardcode.

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

Catatan: ini menggantikan getFilesDir sehingga FileBackupHelper bekerja di database dir, bukan file dir.

Petunjuk lain: Anda juga dapat menggunakan databaseList untuk mendapatkan semua nama DB dan feed Anda dari daftar ini (tanpa jalur induk) ke FileBackupHelper. Kemudian semua DB aplikasi akan disimpan dalam cadangan.

Pointer Null
sumber
Menggabungkan ini dengan solusi yang diberikan oleh @pjv bekerja dengan sempurna! Mungkin tidak persis seperti spesifikasi yang ada dalam pikiran ... tetapi berfungsi dengan cukup baik!
Tom
1
Itu tidak banyak berguna jika Anda memiliki database dan file normal untuk dicadangkan.
Dan Hulme
1
@Dan: Gunakan beberapa agen dalam kasus itu.
pjv
@Pointer Null Bisakah Anda menjelaskan lebih lanjut tentang satu getFilesDir (), mengapa ini benar-benar dibutuhkan dan mengapa? Bagaimana cara kerjanya?
bedak366
7

Menggunakan FileBackupHelperuntuk mencadangkan / memulihkan db sqlite menimbulkan beberapa pertanyaan serius:
1. Apa yang terjadi jika aplikasi menggunakan kursor yang diambil dari ContentProvider.query()dan agen pencadangan mencoba menimpa seluruh file?
2. Tautan adalah contoh bagus dari pengujian sempurna (entropi rendah;). Anda menghapus aplikasi, menginstalnya lagi dan cadangan dipulihkan. Bagaimanapun hidup bisa menjadi brutal. Lihat tautannya . Bayangkan skenario ketika pengguna membeli perangkat baru. Karena tidak memiliki set sendiri, agen backup menggunakan set perangkat lain. Aplikasi diinstal dan backupHelper Anda mengambil file lama dengan skema versi db yang lebih rendah dari yang sekarang. SQLiteOpenHelperpanggilan onDowngradedengan implementasi default:

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

Apa pun yang dilakukan pengguna, dia tidak dapat menggunakan aplikasi Anda di perangkat baru.

Saya sarankan menggunakan ContentResolveruntuk mendapatkan data -> serialize (tanpa _ids) untuk backup dan deserialize -> masukkan data untuk restore.

Catatan: mendapatkan / memasukkan data dilakukan melalui ContentResolver sehingga menghindari masalah cuncurrency. Serialisasi dilakukan di backupAgent Anda. Jika Anda membuat kursor <-> pemetaan objek Anda sendiri, membuat serial item bisa semudah mengimplementasikan Serializabledengan transientfield _id pada kelas yang mewakili entitas Anda.

Saya juga akan menggunakan ContentProviderOperation contoh insert massal dan CursorLoader.setUpdateThrottleagar aplikasi tidak terjebak dengan restart loader pada perubahan data selama proses pemulihan cadangan.

Jika Anda kebetulan berada dalam situasi penurunan, Anda dapat memilih untuk membatalkan pemulihan data atau memulihkan dan memperbarui ContentResolver dengan bidang yang relevan dengan versi yang diturunkan.

Saya setuju bahwa subjeknya tidak mudah, tidak dijelaskan dengan baik di dokumen dan beberapa pertanyaan masih tetap seperti ukuran data massal dll.

Semoga ini membantu.

Piotr Bazan
sumber
Anda mengulangi beberapa pertanyaan yang valid. Tapi saya tidak melihat bagaimana membuat serial database memecahkan masalah. Ini tidak membantu dengan masalah konkurensi. Dan jika Anda tidak dapat menulis skrip untuk menurunkan versi database, maka Anda tidak dapat menulis skrip untuk menghilangkan data lama.
pjv
@pjv Jika Anda menggunakan ContentResolver, ini memecahkan masalah konkurensi untuk Anda.
IgorGanapolsky
@IgorGanapolsky Ya, saya pikir itu benar. Namun, jika pembuatan serial cukup lambat dan pengguna sudah menggunakan aplikasi dan memasukkan data, ini mungkin bertentangan dengan data yang Anda pulihkan. Bisakah Anda melakukannya secara atomik dan sebelum pengguna mendapatkan akses ke aplikasi?
pjv
@pjv Anda dapat mendaftarkan ContentObserver untuk menyinkronkan data yang Anda amati untuk diberi tahu. Karenanya Anda tidak perlu khawatir dengan konflik.
IgorGanapolsky
Poin Anda tentang versi DB bagus - saya kira jika Anda menyimpan versi DB Anda (di sharedpreferences misalnya, dengan asumsi Anda juga mencadangkannya) maka ketika Anda memulihkan Anda harus dapat menggunakan logika yang ada untuk meningkatkan DB ke nya. versi saat ini dalam metode onDowngrade (). Jika Anda belum memiliki kode pemutakhiran, Anda tidak akan repot-repot menyimpan data!
Ben Neill
3

Mulai Android M, sekarang ada API pencadangan / pemulihan data lengkap yang tersedia untuk aplikasi. API baru ini menyertakan spesifikasi berbasis XML dalam manifes aplikasi yang memungkinkan pengembang menjelaskan file mana yang akan dicadangkan dengan cara semantik langsung: 'cadangkan database yang disebut "mydata.db"'. API baru ini jauh lebih mudah digunakan oleh pengembang - Anda tidak perlu melacak diff atau meminta izin cadangan secara eksplisit, dan deskripsi XML tentang file mana yang akan dicadangkan berarti Anda sering tidak perlu menulis kode apa pun sama sekali.

(Anda dapat terlibat bahkan dalam operasi pencadangan / pemulihan data lengkap untuk mendapatkan panggilan balik saat pemulihan terjadi, misalnya. Dengan cara itu fleksibel.)

Lihat Mengonfigurasi Auto Backup untuk Aplikasi di developer.android.com untuk mengetahui deskripsi tentang cara menggunakan API baru.

ctate
sumber
Ini hanya untuk Android 6.0 (API 23), jika pengguna memiliki versi yang lebih lama, ia masih harus menerapkan cadangan Nilai Kunci.
cinta langsung
0

Salah satu opsinya adalah membangunnya dalam logika aplikasi di atas database. Saya pikir itu benar-benar berteriak untuk levell seperti itu. Tidak yakin apakah Anda sudah melakukannya, tetapi kebanyakan orang (meskipun ada pendekatan kursor pengelola konten android) akan memperkenalkan beberapa pemetaan ORM - baik pendekatan kustom atau pendekatan orm-lite. Dan apa yang saya lebih suka lakukan dalam kasus ini adalah:

  1. untuk memastikan aplikasi Anda berfungsi dengan baik saat aplikasi / data ditambahkan di latar belakang dengan data baru ditambahkan / dihapus saat aplikasi sudah dimulai
  2. untuk membuat beberapa Java-> protobuf atau bahkan hanya pemetaan serialisasi java dan menulis BackupHelper Anda sendiri untuk membaca data dari aliran dan menambahkannya ke database ....

Jadi dalam hal ini daripada melakukannya pada level db, lakukanlah pada level aplikasi.

Jarek Potiuk
sumber
Masalahnya bukanlah bagaimana cara mendapatkan data dari database ke dalam BackupHelperAgent atau sebaliknya. Silakan baca 5 butir di akhir pertanyaan saya.
pjv
@JarekPotiuk Anda berkata: "kebanyakan orang (meskipun ada pendekatan kursor pengelola konten android)". Tapi apa yang membuat Anda berpikir bahwa kebanyakan orang akan menghindari penggunaan ContentProvider dan ContentResolver untuk akses database?
IgorGanapolsky