Резервное копирование / восстановление Android: как сделать резервную копию внутренней базы данных? - PullRequest
86 голосов
/ 12 марта 2011

Я реализовал BackupAgentHelper, используя предоставленный FileBackupHelper для резервного копирования и восстановления собственной базы данных, которая у меня есть.Это база данных, которую вы обычно используете вместе с ContentProviders и которая находится в /data/data/yourpackage/databases/.

Можно подумать, что это распространенный случай.Однако в документах не ясно, что делать: http://developer.android.com/guide/topics/data/backup.html. Специально для этих типовых баз данных не существует BackupHelper.Поэтому я использовал FileBackupHelper, указал его на мой файл .db в "/databases/", ввел блокировки вокруг любой операции с БД (такой как db.insert) в моем ContentProviders, и даже попытался создать "/databases/ "каталог до onRestore(), поскольку он не существует после установки.

В прошлом я успешно реализовывал подобное решение для SharedPreferences в другом приложении.Однако, когда я тестирую свою новую реализацию в эмуляторе-2.2, я вижу, что из журналов выполняется резервное копирование до LocalTransport, а также выполняется восстановление (и вызывается onRestore()). Тем не менее, сам файл db никогда не создается.

Обратите внимание, что это все после установки и до первого запуска приложения, после того, как восстановление было выполнено.Кроме того, моя стратегия тестирования была основана на http://developer.android.com/guide/topics/data/backup.html#Testing.

Пожалуйста, обратите внимание, что я говорю не о какой-то базе данных sqlite, которой я управляю, ни о резервном копировании на SD-карту, собственный сервер или где-либо еще.

Я видел упоминание в документах о базах данных, в которых рекомендуется использовать пользовательский BackupAgent, но это не похоже на:

Однако вы можете расширить BackupAgent напрямую, еслиВам необходимо: * Резервное копирование данных в базе данных.Если у вас есть база данных SQLite, которую вы хотите восстановить, когда пользователь переустанавливает ваше приложение, вам нужно создать специальный BackupAgent, который считывает соответствующие данные во время операции резервного копирования, затем создайте таблицу и вставьте данные во время операции восстановления.

Немного ясности, пожалуйста.

Если мне действительно нужно сделать это самостоятельно до уровня SQL, тогда я беспокоюсь о следующих темах:

  • Открытые базы данных и транзакции.Я понятия не имею, как закрыть их из такого одноэлементного класса вне рабочего процесса моего приложения.

  • Как уведомить пользователя о том, что выполняется резервное копирование и база данных заблокирована.Это может занять много времени, поэтому мне может потребоваться показать индикатор выполнения.

  • Как сделать то же самое при восстановлении.Как я понимаю, восстановление может произойти только тогда, когда пользователь уже начал использовать приложение (и вводить данные в базу данных).Таким образом, вы не можете позволить себе просто восстановить резервные копии данных на месте (удалив пустые или старые данные).Вам нужно как-то присоединиться к нему, что для любой нетривиальной базы данных невозможно из-за идентификаторов.

  • Как обновить приложение после того, как восстановление выполнено без получения пользователязастрял в какой-то - сейчас - недоступной точке.

  • Могу ли я быть уверен, что база данных уже была обновлена ​​при резервном копировании или восстановлении?В противном случае ожидаемая схема может не совпадать.

Ответы [ 6 ]

34 голосов
/ 13 июня 2011

После повторного рассмотрения моего вопроса я смог заставить его работать после просмотра того, как ConnectBot делает это . Спасибо Кенни и Джеффри!

На самом деле это так же просто, как добавить:

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

к вашему BackupAgentHelper.

Я упустил момент, когда вам нужно было использовать относительный путь с "../databases/".

Тем не менее, это ни в коем случае не идеальное решение. Документы для FileBackupHelper, например, упоминают: «FileBackupHelper следует использовать только с небольшими файлами конфигурации, а не с большими двоичными файлами. », последний вариант относится к базам данных SQLite.

Я хотел бы получить больше предложений, понимание того, что от нас ожидается (каково правильное решение), и советы о том, как это может сломаться.

22 голосов
/ 15 марта 2012

Вот еще более чистый способ резервного копирования баз данных в виде файлов. Нет жестко закодированных путей.

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

Примечание: он переопределяет getFilesDir , так что FileBackupHelper работает в каталогах баз данных, а не в каталогах файлов.

Еще один совет: вы также можете использовать databaseList , чтобы получить все имена ваших баз данных и каналов из этого списка (без родительского пути) в FileBackupHelper. Тогда все базы данных приложения будут сохранены в резервной копии.

21 голосов
/ 31 марта 2012

Более чистым подходом будет создание пользовательского 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));
}
7 голосов
/ 31 октября 2013

Использование FileBackupHelper для резервного копирования / восстановления sqlite db поднимает несколько серьезных вопросов:
1. Что произойдет, если приложение использует курсор, извлеченный из ContentProvider.query(), и агент резервного копирования попытается переопределить весь файл?
2. Ссылка является хорошим примером идеального (с низким уровнем энтрофии;) тестирования. Вы удаляете приложение, устанавливаете его снова и резервная копия восстанавливается. Однако жизнь может быть жестокой. Взгляните на ссылку . Давайте представим сценарий, когда пользователь покупает новое устройство. Поскольку у него нет собственного набора, агент резервного копирования использует набор другого устройства. Приложение установлено, и ваш backupHelper извлекает старый файл со схемой версии базы данных ниже текущей. 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, что позволяет избежать проблем с наличностью. Сериализация выполняется в вашем резервном агенте. Если вы делаете свое собственное отображение курсора <->, сериализация элемента может быть такой же простой, как реализация Serializable с transient field _id для класса, представляющего вашу сущность.

Я бы также использовал массовую вставку, т.е. ContentProviderOperation , например и CursorLoader.setUpdateThrottle, чтобы приложение не зависало при перезапуске загрузчика при изменении данных во время процесса резервного копирования.

Если вы оказались в ситуации понижения версии, вы можете прервать восстановление или восстановить и обновить ContentResolver с полями, относящимися к более ранней версии.

Я согласен с тем, что тема не простая, не очень хорошо объяснена в документах, и некоторые вопросы все еще остаются, как объем данных и т. Д.

Надеюсь, это поможет.

3 голосов
/ 27 декабря 2015

Начиная с Android M, теперь приложениям доступно API полного резервного копирования / восстановления данных.Этот новый API включает в себя основанную на XML спецификацию в манифесте приложения, которая позволяет разработчику описывать, какие файлы для резервного копирования прямым семантическим способом: «резервное копирование базы данных с именем« mydata.db »».Этот новый API намного проще для разработчиков - вам не нужно отслеживать различия или явно запрашивать передачу резервной копии, а XML-описание файлов для резервного копирования означает, что вам часто не нужно писать какой-либо кодвообще.

(Вы можете участвовать даже в операции полного резервного копирования / восстановления данных, чтобы получить обратный вызов, например, когда восстановление произойдет. В таком случае это гибко.)

См. Раздел Настройка автоматического резервного копирования для приложений на developer.android.com, где описано, как использовать новый API.

0 голосов
/ 20 июня 2011

Одним из вариантов будет встроить его в логику приложения над базой данных.Это на самом деле кричит о таком уровне, я думаю.Не уверен, что вы уже делаете это, но большинство людей (несмотря на подход курсора менеджера контента Android) представят какое-то отображение ORM - либо пользовательское, либо какое-то подход orm-lite.И что я предпочел бы сделать в этом случае:

  1. , чтобы убедиться, что ваше приложение работает нормально, когда приложение / данные добавляются в фоновом режиме с добавлением / удалением новых данных, когда приложение уже запущено
  2. для создания некоторого Java-> protobuf или даже простого сопоставления сериализации Java и написания собственного BackupHelper для чтения данных из потока и простого добавления их в базу данных ....

Так что вв этом случае вместо того, чтобы делать это на уровне базы данных, делайте это на уровне приложения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...