Android yedekleme / geri yükleme: dahili bir veritabanı nasıl yedeklenir?


86

Sahip olduğum yerel veritabanını yedeklemek ve geri yüklemek için BackupAgentHelpersağlananları kullanarak uyguladım FileBackupHelper. Bu, genellikle birlikte kullandığınız ContentProvidersve içinde bulunduğu veritabanıdır /data/data/yourpackage/databases/.

Bunun yaygın bir durum olduğu düşünülebilir. Ancak dokümanlar ne yapılacağı konusunda net değil: http://developer.android.com/guide/topics/data/backup.html . Bu tipik veritabanları için özel olarak bir şey yoktur BackupHelper. Bu nedenleFileBackupHelper , " /databases/" içindeki .db dosyama işaret ettim, benim içindeki herhangi bir db işleminin (örneğin db.insert) etrafına kilitler ekledim ContentProvidersve hatta kurulumdan sonra olmadığı için " /databases/" dizinini daha önce oluşturmayı denedim onRestore().

SharedPreferencesGeçmişte farklı bir uygulamada başarılı bir şekilde benzer bir çözüm uyguladım . Bununla birlikte, yeni uygulamamı emülatör-2.2'de test ettiğimde LocalTransport, günlüklerden bir yedeklemenin gerçekleştirildiğini ve ayrıca bir geri yüklemenin gerçekleştirildiğini (ve onRestore()çağrıldığını) görüyorum . Yine de, db dosyasının kendisi asla oluşturulmaz.

Tüm bunların bir yüklemeden sonra ve uygulamanın ilk kez başlatılmasından önce, geri yükleme gerçekleştirildikten sonra olduğunu unutmayın. Bunun dışında test stratejim http://developer.android.com/guide/topics/data/backup.html#Testing'e dayanıyordu .

Lütfen kendim yönettiğim bazı sqlite veritabanlarından veya SD karta, kendi sunucuma veya başka bir yere yedekleme yapmaktan bahsetmediğimi de unutmayın.

Dokümanlarda bir gelenek kullanılmasını tavsiye eden veritabanları hakkında bir söz gördüm, BackupAgentancak ilgili görünmüyor:

Ancak, şunlara ihtiyacınız varsa doğrudan BackupAgent'ı genişletmek isteyebilirsiniz: * Bir veritabanındaki verileri yedekleyin. Kullanıcı uygulamanızı yeniden yüklediğinde geri yüklemek istediğiniz bir SQLite veritabanınız varsa, bir yedekleme işlemi sırasında uygun verileri okuyan özel bir BackupAgent oluşturmanız, ardından tablonuzu oluşturmanız ve bir geri yükleme işlemi sırasında verileri eklemeniz gerekir.

Biraz açıklık lütfen.

Bunu gerçekten SQL seviyesine kadar kendim yapmam gerekiyorsa, aşağıdaki konulardan endişeleniyorum:

  • Açık veritabanları ve işlemler. Onları uygulamamın iş akışı dışında böyle bir tekil sınıftan nasıl kapatacağımı bilmiyorum.

  • Kullanıcıya bir yedeklemenin devam ettiği ve veritabanının kilitlendiği konusunda nasıl bilgi verilir. Uzun zaman alabilir, bu yüzden bir ilerleme çubuğu göstermem gerekebilir.

  • Aynısı geri yüklemede nasıl yapılır. Anladığım kadarıyla, geri yükleme tam da kullanıcı uygulamayı kullanmaya başladığında (ve veri tabanına veri girdiğinde) gerçekleşebilir. Dolayısıyla, yedeklenen verileri yerine geri yükleyemezsiniz (boş veya eski verileri silerek). Bir şekilde ona katılmanız gerekecek, bu da önemsiz olmayan herhangi bir veritabanı için id'ler nedeniyle imkansızdır.

  • Geri yükleme tamamlandıktan sonra, kullanıcıyı - şimdi - erişilemez bir noktada takılıp bırakmadan uygulamanın nasıl yenileneceği.

  • Veritabanının yedekleme veya geri yükleme sırasında zaten yükseltildiğinden emin olabilir miyim? Aksi takdirde beklenen şema eşleşmeyebilir.


Aynı sorunu

Basit yedekleme deliği db yolu yok mu?
Jin35

FileBackupHelper kullanarak bir klasörü yedeklemem gerekiyor. Veritabanı kaydetme yaklaşımını kullanmak, altında çok sayıda alt klasör ve alt dosya bulunan klasörü kaydetmeme izin verir mi?
coolcool1994

Bunu hiç düzgün çalıştırdınız mı?
DeNitE Appz

Evet, aşağıya bakın. Ben de kendi sorumu cevapladım.
pjv

Yanıtlar:


21

Daha temiz bir yaklaşım, bir gelenek yaratmak olacaktır BackupHelper:

public class DbBackupHelper extends FileBackupHelper {

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

ve sonra şunu ekleyin BackupAgentHelper:

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

2
@logray: Bu kod, sorudakiyle aynı. Gereksiz düzenleme.
Linuxios

@Linuxios Kabul ediyorum, ancak kodu körü körüne yapıştırmak yerine yazara uygun atıfta bulunmanın daha iyi olacağını düşünüyorum ... orijinal OP / gönderinin uygulamasından bir bağlantı içerdiğini düşünürsek.
logray

3
Önemli : Dosyaları yedeklemeye yönelik (belgeler) ( developer.android.com/guide/topics/data/backup.html#Files ), işlemin iş parçacığı açısından güvenli olmadığını belirtir , bu nedenle Veritabanı yöntemlerinizi ve yedekleme aracılarınızı
nicopico

@yanchenko FileBackupHelper dokümantasyonu şunu belirtir: "Not: Bu, büyük ikili dosyalar için değil, yalnızca küçük yapılandırma dosyalarıyla kullanılmalıdır." Yani bir veritabanı genellikle büyük bir ikili dosya olarak nitelendirilir ...
IgorGanapolsky

Bu aslında işe yaramıyor! (bir şeyi kaçırmıyorsam ...). Sorun, db'nin mutlak yolunu FileBackupHelper'a iletmenizdir, ancak FileBackupHelper, dosya dizini yolunun başına örn. data / data / <paketiniz> / files, data / data / <paketiniz> / files / data / data / <paketiniz> / databases / <DB Name> gibi yanlış bir yola yol açar. DB mutlak adı ve süper sınıf, dosyalar dizininin başına eklendiğinden, yolu dosyalar dizinine göre yapmanız gerekir.
bitrock

34

Sorumu tekrar gözden geçirdikten sonra, ConnectBot'un bunu nasıl yaptığına baktıktan sonra onu çalıştırmayı başardım . Kenny ve Jeffrey teşekkürler!

Aslında eklemek kadar kolaydır:

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

senin için BackupAgentHelper.

Kaçırdığım nokta, " ../databases/" ile göreceli bir yol kullanmanız gerektiğiydi .

Yine de, bu kesinlikle mükemmel bir çözüm değildir. FileBackupHelperÖrneğin söz konusu dokümanlar : " FileBackupHelpersadece küçük yapılandırma dosyalarıyla kullanılmalıdır, büyük ikili dosyalarla kullanılmamalıdır. ", İkincisi SQLite veritabanları için geçerlidir.

Bizden ne beklendiğine dair daha fazla öneri, içgörü (doğru çözüm nedir) ve bunun nasıl çözülebileceği konusunda tavsiye almak istiyorum.


1
Muhtemelen eklemeliyim, bu biraz resmi olmayan olsa da, bunca zamandır çok iyi çalışıyor / çalışıyor.
pjv

6
Sabit kodlama yolları kötüdür.
Pointer Null

@pjv, her db etkileşimini senkronize hale getiriyor musunuz? Ben aynı şeyi yapıyor ve ben senkronize gerek olmadığını anlamaya çalışıyorum db.open()aracılığıyla db.close()ya da sadece inserttabloların ya da ne.
NSouth

22

Veritabanlarını dosya olarak yedeklemenin daha temiz bir yolu. Sabit kodlanmış yollar yok.

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

Not: GetFilesDir'i geçersiz kılar, böylece FileBackupHelper dosyalar dizininde değil, veritabanları dizininde çalışır.

Başka bir ipucu: tüm DB'lerinizi ve besleme adlarını bu listeden (ana yol olmadan) FileBackupHelper'a almak için de databaseList'i kullanabilirsiniz . Daha sonra tüm uygulamanın DB'leri yedeğe kaydedilir.


Bunu @pjv tarafından verilen çözümle birleştirmek mükemmel çalışıyor! Spesifikasyonların aklındaki tam olarak bu olmayabilir ... ama oldukça iyi çalışıyor!
Tom

1
Veritabanlarınız ve yedeklenecek normal dosyalarınız varsa bu pek bir işe yaramaz .
Dan Hulme

1
@Dan: Bu durumda birden fazla aracı kullanın.
pjv

@Pointer Null Bir tane daha getFilesDir () detaylandırabilir misiniz, neden gerçekten gerekli ve neden? O nasıl çalışır?
powder366

7

Kullanımı FileBackupHelper: yedekleme / sqlite db bazı ciddi sorular ortaya restore
1. uygulama kullanımları alınan imleç ne olur ContentProvider.query()tüm dosyayı geçersiz kılmak için ve yedekleme ajan çalışır?
2. Bağlantı , mükemmel (düşük entrofi) test etmeye güzel bir örnektir. Uygulamayı kaldırırsınız, yeniden yüklersiniz ve yedekleme geri yüklenir. Ancak hayat acımasız olabilir. Bağlantıya bir göz atın . Bir kullanıcının yeni bir cihaz satın aldığı senaryoyu düşünelim. Yedekleme aracısı kendi setine sahip olmadığı için diğer cihazın setini kullanır. Uygulama yüklenir ve backupHelper uygulamanız mevcut db sürüm şemasına sahip eski dosyayı alır. varsayılan uygulama ile SQLiteOpenHelperçağrılar onDowngrade:

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

Kullanıcı ne yaparsa yapsın, uygulamanızı yeni cihazda kullanamaz.

ContentResolverVeri almak için kullanmayı -> _idyedekleme için seri hale getirme ( s olmadan ) ve geri yükleme için veri ekleme -> kullanmayı öneririm .

Not: veri alma / ekleme, ContentResolver aracılığıyla yapılır, böylece para birimi sorunları önlenir. Yedekleme aracınızda serileştirme yapılır. Kendi imlecinizi <-> nesne eşlemesini yaparsanız, bir öğeyi serileştirmek , varlığınızı temsil eden sınıf üzerinde alan _id Serializableile uygulamak kadar basit olabilir transient.

Ayrıca, ContentProviderOperation örneğin toplu ekleme kullanırım ve CursorLoader.setUpdateThrottleböylece uygulama, yedekleme geri yükleme işlemi sırasında veri değişikliği üzerine yükleyiciyi yeniden başlatmaya takılıp kalmaz.

Düşürme durumuyla karşılaşırsanız, geri yükleme verilerini iptal etmeyi veya ContentResolver'ı eski sürümle ilgili alanlarla geri yüklemeyi ve güncellemeyi seçebilirsiniz.

Konunun kolay olmadığını, belgelerde iyi açıklanmadığını ve bazı soruların hala toplu veri boyutu gibi kaldığını kabul ediyorum.

Bu yardımcı olur umarım.


Bazı geçerli soruları tekrar edersiniz. Ancak veritabanını serileştirmenin sorunu nasıl çözdüğünü anlamıyorum. Eşzamanlılık sorunlarına yardımcı olmaz. Veritabanını düşürmek için bir komut dosyası yazamazsanız, eski verileri seriyi kaldırmak için bir komut dosyası yazamazsınız.
pjv

@pjv ContentResolver kullanıyorsanız, eşzamanlılık sorunlarını sizin için çözer.
IgorGanapolsky

@IgorGanapolsky Evet, bunun doğru olduğunu düşünüyorum. Ancak, serileştirme yeterince yavaşsa ve kullanıcı zaten uygulamayı kullanıyor ve veri giriyorsa, geri yüklediğiniz verilerle çakışabilir. Bunu atomik olarak ve kullanıcı uygulamaya erişmeden önce yapabilir misiniz?
pjv

@pjv Gözlemlediğiniz verileri senkronize etmek için bir ContentObserver kaydı yapabilirsiniz. Bu nedenle çatışmalar konusunda endişelenmenize gerek yok.
IgorGanapolsky

DB sürümleri hakkındaki düşünceniz iyi bir şey - sanırım DB sürümünüzü kaydederseniz (örneğin, paylaşılan tercihlerde, onu da yedeklediğinizi varsayarak), geri yüklerken, DB'yi kendi sürümüne yükseltmek için mevcut mantığı kullanabilmeniz gerekir. onDowngrade () yöntemindeki geçerli sürüm. Zaten yükseltme kodunuz yoksa, yine de verileri saklamakla uğraşmazsınız!
Ben Neill

3

Android M'den itibaren, uygulamalarda artık tam veri yedekleme / geri yükleme API'si bulunmaktadır. Bu yeni API, uygulama manifestinde, geliştiricinin hangi dosyaların doğrudan semantik bir şekilde yedekleneceğini açıklamasına olanak tanıyan XML tabanlı bir spesifikasyon içerir: "mydata.db" adlı veritabanını yedekleyin. Bu yeni API, geliştiricilerin kullanması için çok daha kolaydır - farkları takip etmeniz veya açıkça bir yedekleme geçişi talep etmeniz gerekmez ve hangi dosyaların yedekleneceğine ilişkin XML açıklaması genellikle herhangi bir kod yazmanıza gerek olmadığı anlamına gelir hiç.

( Örneğin, geri yükleme gerçekleştiğinde geri arama almak için tam veri yedekleme / geri yükleme işlemine bile dahil olabilirsiniz . Bu şekilde esnektir.)

Bkz Uygulamalar için Yapılandırma Otomatik Yedekleme yeni API nasıl kullanılacağına ilişkin bir açıklama için developer.android.com bölümünde.


Bu yalnızca Android 6.0 (API 23) içindir, eğer kullanıcı daha eski bir sürüme sahipse, yine de Anahtar Değer yedeklemesini uygulamak zorundadır.
live-love

0

Bir seçenek, onu veri tabanının üzerindeki uygulama mantığında oluşturmak olacaktır. Aslında bence böyle bir seviye için çığlık atıyor. Zaten yapıyor musunuz emin değilim, ancak çoğu kişi (android içerik yöneticisi imleç yaklaşımına rağmen) bazı ORM eşlemelerini - ya özel ya da bazı orm-lite yaklaşımını - tanıtacaktır. Ve bu durumda yapmayı tercih ettiğim şey şudur:

  1. Uygulama başlatılırken yeni veriler eklenmiş / kaldırılmış olarak arka planda uygulama / veri eklendiğinde uygulamanızın düzgün çalıştığından emin olmak
  2. bazı Java-> protobuf veya hatta basitçe java serileştirme eşlemesi yapmak ve akıştan verileri okumak için kendi BackupHelper'ınızı yazmak ve basitçe veritabanına eklemek ...

Yani bu durumda bunu db seviyesinde yapmak yerine uygulama seviyesinde yapın.


Sorun, verilerin veritabanından BackupHelperAgent'a nasıl alınacağı veya tam tersi değildir. Lütfen sorumun sonundaki 5 kurşunu okuyun.
pjv

@JarekPotiuk "Çoğu kişi (android içerik yöneticisi imleci yaklaşımına rağmen)" dediniz. Ancak, çoğu insanın veritabanı erişimi için ContentProvider ve ContentResolver kullanmaktan kaçınacağını düşündüren nedir?
IgorGanapolsky
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.