Подтвердить что ты не робот

Резервное копирование/восстановление Android: как создать резервную копию внутренней базы данных?

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

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

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

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

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

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

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

Некоторая ясность пожалуйста.

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

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

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

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

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

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

4b9b3361

Ответ 1

Более чистым подходом было бы создание пользовательского 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

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

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

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

на ваш BackupAgentHelper.

То, что мне не хватало, было то, что вам пришлось бы использовать относительный путь с "../databases/".

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

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

Ответ 3

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

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 работает в каталоге dir, а не в файлах dir.

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

Ответ 4

Использование FileBackupHelper для резервного копирования/восстановления sqlite 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. Если вы сделаете свой собственный курсор ↔ сопоставление объектов, сериализирующий элемент, может быть так же просто, как реализовать Serializable с полем transient _id в классе, представляющем вашу сущность.

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

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

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

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

Ответ 5

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

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

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

Ответ 6

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

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

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