Backup / restauração do Android: como fazer backup de um banco de dados interno?

86

Implementei um BackupAgentHelperusando o fornecido FileBackupHelperpara fazer backup e restaurar o banco de dados nativo que tenho. Este é o banco de dados que você normalmente usa junto com o ContentProvidersqual reside /data/data/yourpackage/databases/.

Alguém poderia pensar que este é um caso comum. No entanto, os documentos não são claros sobre o que fazer: http://developer.android.com/guide/topics/data/backup.html . Não há nenhum BackupHelperespecífico para esses bancos de dados típicos. Portanto, usei oFileBackupHelper , apontei para meu arquivo .db em " /databases/", introduzi bloqueios em qualquer operação db (como db.insert) em meu ContentProviderse até tentei criar o /databases/diretório " " antes onRestore()porque ele não existe após a instalação.

Implementei uma solução semelhante para o SharedPreferencessucesso em um aplicativo diferente no passado. No entanto, quando testo minha nova implementação no emulador-2.2, vejo um backup sendo executado a LocalTransportpartir dos logs, bem como uma restauração sendo realizada (e onRestore()chamada). No entanto, o próprio arquivo db nunca é criado.

Observe que tudo isso ocorre após a instalação e antes da primeira inicialização do aplicativo, após a execução da restauração. Além disso, minha estratégia de teste foi baseada em http://developer.android.com/guide/topics/data/backup.html#Testing .

Observe também que não estou falando sobre algum banco de dados sqlite que eu mesmo gerencio, nem sobre backup em SDcard, servidor próprio ou em outro lugar.

Eu vi uma menção nos documentos sobre bancos de dados que aconselham o uso de um personalizado, BackupAgentmas não parece relacionado:

No entanto, você pode desejar estender o BackupAgent diretamente se precisar: * Fazer backup dos dados em um banco de dados. Se você tiver um banco de dados SQLite que deseja restaurar quando o usuário reinstalar seu aplicativo, será necessário construir um BackupAgent personalizado que leia os dados apropriados durante uma operação de backup e, em seguida, crie sua tabela e insira os dados durante uma operação de restauração.

Alguma clareza, por favor.

Se eu realmente preciso fazer isso sozinho até o nível SQL, estou preocupado com os seguintes tópicos:

  • Abra bancos de dados e transações. Não tenho ideia de como fechá-los a partir de uma classe única fora do fluxo de trabalho do meu aplicativo.

  • Como notificar o usuário de que um backup está em andamento e o banco de dados está bloqueado. Pode demorar muito, então posso precisar mostrar uma barra de progresso.

  • Como fazer o mesmo na restauração. Pelo que entendi, a restauração pode acontecer apenas quando o usuário já começou a usar o aplicativo (e inserir dados no banco de dados). Portanto, você não pode presumir apenas restaurar os dados do backup no local (excluindo os dados vazios ou antigos). Você terá que juntá-lo de alguma forma, o que para qualquer banco de dados não trivial é impossível devido ao id.

  • Como atualizar o aplicativo após a restauração sem deixar o usuário preso em algum ponto - agora - inacessível.

  • Posso ter certeza de que o banco de dados já foi atualizado no backup ou restauração? Caso contrário, o esquema esperado pode não corresponder.

pjv
fonte
Tenho o mesmo problema ...
Não há maneira de fazer backup simples db?
Jin35
Preciso fazer backup de uma pasta usando FileBackupHelper. Usar a abordagem de salvar banco de dados me permitiria salvar aquela pasta que contém muitas subpastas e subarquivos embaixo dela?
coolcool1994
Você já fez isso funcionar corretamente?
DeNitE Appz
Sim, veja abaixo. Eu respondi minha própria pergunta também.
pjv

Respostas:

21

Uma abordagem mais limpa seria criar um personalizado BackupHelper:

public class DbBackupHelper extends FileBackupHelper {

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

e depois adicione-o a BackupAgentHelper:

public void onCreate() {
    addHelper(DATABASE, new DbBackupHelper(this, DB.FILE));
}
Yanchenko
fonte
2
@logray: Esse código é exatamente o mesmo da pergunta. Edição desnecessária.
Linuxios
@Linuxios Eu concordo, porém acho melhor fornecer atribuição adequada ao autor, ao invés de apenas colar código cegamente ... considerando que o OP / post original incluía um link de seu aplicativo.
logray
3
Importante : A (documentação) ( developer.android.com/guide/topics/data/backup.html#Files ) para fazer backup de arquivos afirma que a operação não é threadsafe , então você deve sincronizar seus métodos de banco de dados e seus agentes de backup
nicopico
A documentação de @yanchenko para FileBackupHelper afirma: "Nota: Isso deve ser usado apenas com arquivos de configuração pequenos, não com arquivos binários grandes." Portanto, um banco de dados costuma ser qualificado como um grande arquivo binário ...
IgorGanapolsky
Isso realmente não funciona! (a menos que esteja faltando alguma coisa ...). O problema é que você passa o caminho absoluto do db para o FileBackupHelper, mas o FileBackupHelper adiciona o caminho do diretório de arquivos, por exemplo. dados / dados / <seu pacote> / arquivos que resultam em um caminho incorreto, algo como dados / dados / <seu pacote> / arquivos / dados / dados / <seu pacote> / bases de dados / <Nome do banco de dados> quando tudo o que você deseja é o Nome absoluto do banco de dados e uma vez que a superclasse precede o diretório de arquivos, você precisa fazer o caminho relativo ao diretório de arquivos.
bitrock de
34

Depois de rever minha pergunta, consegui fazê-lo funcionar depois de ver como o ConnectBot o faz . Obrigado Kenny e Jeffrey!

Na verdade, é tão fácil quanto adicionar:

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

para o seu BackupAgentHelper.

O que estava faltando era o fato de que você teria que usar um caminho relativo com " ../databases/".

Ainda assim, esta não é uma solução perfeita. Os documentos para FileBackupHelpermenção, por exemplo: " FileBackupHelperdevem ser usados ​​apenas com pequenos arquivos de configuração, não grandes arquivos binários. ", Sendo o último o caso com bancos de dados SQLite.

Gostaria de obter mais sugestões, percepções sobre o que se espera de nós (qual é a solução adequada) e conselhos sobre como isso pode falhar.

pjv
fonte
1
Eu provavelmente deveria acrescentar que, embora isso seja um pouco não oficial, está / tem funcionado muito bem esse tempo todo.
pjv
6
Caminhos embutidos em código são nocivos.
Pointer Null
@pjv, então você faz toda interação db sincronizada? Estou fazendo a mesma coisa e tentando descobrir se preciso sincronizar db.open()por meio db.close()ou apenas de insertinstruções ou o quê.
NSouth
22

Aqui está uma maneira mais limpa de fazer backup de bancos de dados como arquivos. Nenhum caminho codificado.

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

Nota: ele substitui getFilesDir para que FileBackupHelper funcione no diretório de bancos de dados, não no diretório de arquivos.

Outra dica: você também pode usar databaseList para obter todos os seus nomes de banco de dados e feed desta lista (sem o caminho pai) para FileBackupHelper. Então, todos os bancos de dados do aplicativo seriam salvos no backup.

Pointer Null
fonte
Combinar isso com a solução dada por @pjv funciona perfeitamente! Pode não ser exatamente o que as especificações tinham em mente ... mas funciona muito bem!
Tom,
1
Isso não é muito útil se você tiver bancos de dados e arquivos normais para fazer backup.
Dan Hulme
1
@Dan: Use vários agentes nesse caso.
pjv
@Pointer Null Você poderia elaborar um pouco mais um getFilesDir (), por que ele é realmente necessário e por quê? Como funciona?
Powder366
7

Usar FileBackupHelperpara fazer backup / restaurar sqlite db levanta algumas questões sérias:
1. O que acontecerá se o aplicativo usar o cursor recuperado ContentProvider.query()e o agente de backup tentar substituir o arquivo inteiro?
2. O link é um bom exemplo de teste perfeito (baixa entrofia;). Você desinstala o aplicativo, instala-o novamente e o backup é restaurado. No entanto, a vida pode ser brutal. Dê uma olhada no link . Vamos imaginar o cenário em que um usuário compra um novo dispositivo. Como não tem seu próprio conjunto, o agente de backup usa o conjunto de outro dispositivo. O aplicativo é instalado e seu backupHelper recupera o arquivo antigo com esquema de versão db inferior ao atual. SQLiteOpenHelperchamadas onDowngradecom a implementação padrão:

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

Não importa o que o usuário faça, ele não pode usar seu aplicativo no novo dispositivo.

Eu sugiro usar ContentResolverpara obter dados -> serializar (sem _ids) para backup e desserializar -> inserir dados para restauração.

Nota: obter / inserir dados é feito através do ContentResolver, evitando assim problemas de moeda. A serialização é feita em seu backupAgent. Se você fizer seu próprio mapeamento de cursor <-> objeto, serializar um item pode ser tão simples quanto implementar Serializablecom o transientcampo _id na classe que representa sua entidade.

Eu também usar a granel inserção ou seja ContentProviderOperation exemplo e CursorLoader.setUpdateThrottlepara que o aplicativo não está preso com o reinício loader sobre a mudança de dados durante o backup processo de restauração.

Se você estiver em uma situação de downgrade, pode optar por abortar a restauração dos dados ou restaurar e atualizar o ContentResolver com campos relevantes para a versão rebaixada.

Eu concordo que o assunto não é fácil, não é bem explicado nos documentos e algumas questões ainda permanecem como tamanho de dados em massa etc.

Espero que isto ajude.

Piotr Bazan
fonte
Você repete algumas perguntas válidas. Mas não vejo como a serialização do banco de dados resolve o problema. Isso não ajuda com problemas de simultaneidade. E se você não pode escrever um script para fazer o downgrade do banco de dados, você não pode escrever um script para desserializar dados antigos.
pjv
@pjv Se você usar um ContentResolver, ele resolverá os problemas de simultaneidade para você.
IgorGanapolsky
@IgorGanapolsky Sim, acho que é verdade. No entanto, se a serialização for lenta o suficiente e o usuário já estiver usando o aplicativo e inserindo dados, pode haver conflito com os dados que você está restaurando. Você pode fazer isso atomicamente e antes que o usuário tenha acesso ao aplicativo?
pjv
@pjv Você pode registrar um ContentObserver para sincronizar os dados que está observando para ser notificado. Portanto, você não precisa se preocupar com conflitos.
IgorGanapolsky de
Seu ponto sobre as versões do banco de dados é bom - eu acho que se você salvar sua versão do banco de dados (em preferências compartilhadas, por exemplo, supondo que você também faça backup), então, quando estiver restaurando, você deve ser capaz de usar a lógica existente para atualizar o banco de dados para seu versão atual no método onDowngrade (). Se você ainda não tem o código de atualização, não se preocupa em manter os dados!
Ben Neill
3

A partir do Android M, agora existe uma API de backup / restauração de dados completos disponível para aplicativos. Essa nova API inclui uma especificação baseada em XML no manifesto do aplicativo que permite ao desenvolvedor descrever quais arquivos fazer backup de uma forma semântica direta: 'faça backup do banco de dados chamado "mydata.db"'. Esta nova API é muito mais fácil para os desenvolvedores usarem - você não precisa acompanhar as diferenças ou solicitar uma passagem de backup explicitamente, e a descrição XML de quais arquivos fazer backup significa que muitas vezes você não precisa escrever nenhum código em absoluto.

(Você pode se envolver até mesmo em uma operação de backup / restauração de dados completos para obter um retorno de chamada quando a restauração acontecer, por exemplo. É flexível dessa forma.)

Consulte a seção Configuração do backup automático para aplicativos em developer.android.com para obter uma descrição de como usar a nova API.

ctate
fonte
Isso é apenas para Android 6.0 (API 23), se o usuário tiver uma versão mais antiga, ainda será necessário implementar o backup de valor de chave.
live-love
0

Uma opção será construí-lo na lógica do aplicativo acima do banco de dados. Na verdade, ele grita por tal nível, eu acho. Não tenho certeza se você já está fazendo isso, mas a maioria das pessoas (apesar da abordagem do cursor do gerenciador de conteúdo do Android) irá introduzir algum mapeamento ORM - seja personalizado ou uma abordagem orm-lite. E o que eu prefiro fazer neste caso é:

  1. para garantir que seu aplicativo funcione bem quando o aplicativo / dados forem adicionados em segundo plano com novos dados adicionados / removidos enquanto o aplicativo já foi iniciado
  2. para fazer algum Java-> protobuf ou mesmo simplesmente mapeamento de serialização de java e escrever seu próprio BackupHelper para ler os dados do fluxo e simplesmente adicioná-lo ao banco de dados ....

Portanto, neste caso, em vez de fazer no nível do banco de dados, faça no nível do aplicativo.

Jarek Potiuk
fonte
O problema não é como obter dados do banco de dados para o BackupHelperAgent ou vice-versa. Por favor, leia os 5 marcadores no final da minha pergunta.
pjv
@JarekPotiuk Você disse: "a maioria das pessoas (apesar da abordagem do cursor do gerenciador de conteúdo do Android)". Mas o que o faz pensar que a maioria das pessoas evitaria usar um ContentProvider e ContentResolver para acessar o banco de dados?
IgorGanapolsky