quinta-feira, 2 de junho de 2011

Copiando um banco de dados SQLite no Android

Olá povo,

As pessoas constantemente me perguntam como criar um banco de dados SQLite no Android que já venha com alguns registros. No meu post sobre SQLite no Android eu recomendo que isso seja feito na classe SQLiteOpenHelper através de um arquivo XML (por exemplo) por ser uma forma padronizada e centralizada para manutenção dos BDs no Android.
Entretanto algumas pessoas querem mesmo é copiar um banco de dados já prontinho pra aplicação, e que diga-se de passagem pode ser uma boa opção. Desta forma, segue abaixo o código que faz isso:
class Util {
public static void copiaBanco(
Context ctx, String nomeDB){

// Cria o banco vazio
SQLiteDatabase db = ctx.openOrCreateDatabase(
nomeDB, Context.MODE_WORLD_WRITEABLE, null);

db.close();

try {
// Abre o arquivo que deve estar na pasta assets
InputStream is = ctx.getAssets().open(nomeDB);
// Abre o arquivo do banco vazio ele fica em:
// /data/data/nome.do.pacote.da.app/databases
FileOutputStream fos = new FileOutputStream(
ctx.getDatabasePath(nomeDB));

// Copia byte a byte o arquivo do assets para
// o aparelho/emulador
byte[] buffer = new byte[1024];
int read;
while ((read = is.read(buffer)) > 0){
fos.write(buffer, 0, read);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

Notem alguns detalhes interessantes aqui. O método openOrCreateDatabase além de criar a pasta "databases" dentro do diretório onde os bancos de dados devem ficar, ele cria também o arquivo de banco de dados, só que vazio. Uma vez com a estrutura pronta, basta abrir o banco de dados que está com as informações (que no meu exemplo coloquei na pasta "assets") e sobrepor o banco vazio.
Para chamar o método de uma Activity e visualizar uma informação do banco fazemos conforme abaixo:
// carroDB é nome do banco e deve
// ser o nome do arquivo do banco
Util.copiaBanco(this, "carroDB");

SQLiteDatabase db = openOrCreateDatabase(
"dbCarro", MODE_WORLD_WRITEABLE, null);

Cursor c = db.query("carro", null, null, null, null, null, null);
while (c.moveToNext()){
Log.d("NGVL", "Carro: "+ c.getString(1));
}
c.close();
db.close();


4br4ç05,
nglauber

18 comentários:

Telmo Pimentel Mota disse...

Oi Glauber,

É possível usar a solução indicada nessa página[1] com um arquivo dentro dos assets de uma aplicação?

[1] http://stackoverflow.com/questions/1055188/can-i-download-an-sqlite-db-on-sdcard-and-access-it-from-my-android-app

Nelson Glauber de Vasconcelos Leal disse...

Oi Telmo,

Acho que não, pois esse método recebe um objeto File e para obter um arquivo do assets, o que vem é um FileInputStream.

4br4ç05,
nglauber

Fernando disse...

Olá Glauber,

Primeiramente, parabéns pelo post.
Estou com um problema: eu coloquei meu BD no diretório "assets", porém ele sobrepõe toda vez. Como posso verificar se ele já foi sobreposto??

Obrigado,
Fernando

Nelson Glauber de Vasconcelos Leal disse...

Oi Fernando,

Não sei se entendi bem tua dúvida, mas lá vai... O método getDatabasePath(String) da classe android.content.Context retorna um objeto da classe java.io.File, então é só checar se o arquivo existe, se não existir, copie...

4br4ç05,
nglauber

Fernando disse...

Oi Glauber,

É isso mesmo que você entendeu...
O problema é que o BD sempre vai existir, pois antes desta verificação, ele cria o BD em branco.
Tem como verificar se o BD está vazio?

Abraços,
Fernando

Nelson Glauber de Vasconcelos Leal disse...

Oi Fernando,

Neste caso, uma opção seria executar uma pesquisa no banco para checar se existe alguma tabela:
Cursor c = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='suaTabela', null);
if (c.getCount() == 0)
// tabela não existe

4br4ç05,
nglauber

Fernando disse...

Olá Glauber..

Perfeito.. Agora ele funcionou!!

Instalei no meu celular e em um tablet com sucesso!!

Muito obrigado pela ajuda e o seu blog já está em meus favoritos!!

Parabéns pelo trabalho.

Abraços,
Fernando

Nelson Glauber de Vasconcelos Leal disse...

Se o seu banco de dados padrão for muito grande e receber essa mensagem no logcat: "Data exceeds UNCOMPRESS_DATA_MAX".
É porque o Android faz uma compactação em alguns tipos de arquivos de recursos.
Para resolver o problema, renomeei o banco que eu precisei copiar para ficar com a extensão *.jet.

http://www.nutprof.com/2010/12/data-exceeds-uncompressdatamax.html

WalterLOVale disse...

Sem querer ser chato e já sendo, você teria algum exemplo funcional usando este método? Não consegui implementar ele aqui.

Nelson Glauber de Vasconcelos Leal disse...

Olá Walter,

O exemplo acima está funcionando. Existe apenas uma ressalva para quando o banco inicial é grande. Dá uma olhada nos comments anteriores.

4br4ç05,
nglauber

Marcos disse...

No exemplo tem duas classes distintas ou é tudo uma classe só? Está um pouco complicado de entender...

Nelson Glauber de Vasconcelos Leal disse...

Oi Marcos,

Se você ler diretinho, o primeiro trecho mostra a classe Util, e antes do segundo trecho de código tem "Para chamar o método de uma Activity e visualizar uma informação do banco fazemos conforme abaixo"

4br4ç05,
nglauber

David disse...

Marcos,

Ja tenho uma aplicação rodando em um aparelho smartphone, como eu faco para copiar o banco de dados que esta nele? para salvar em outro local, como uma forma de backup?

gostaria de criar uma opcao que faria uma copia no cartao de memoria.


teria outra sugestao para o meu caso?

Nelson Glauber de Vasconcelos Leal disse...

Oi David,

Seria basicamente o mesmo método, mas mudando isso aqui.
InputStream is = new FileInputStream(
getDatabasePath(nomeDB));
FileOutputStream fos = new FileOutputStream(
new File(getExternalFilesDir(null), nomeDB));

4br4ç05,
nglauber

Tiago disse...

Marcos,

Em relação a uma aplicação rodando em um aparelho, como eu faco para copiar o banco de dados que esta nele? para salvar em outro local, como uma forma de backup?
Vc mostrou mais ou menos um exemplo mais não to conseguindo usar ele... poderia me ajudar
Obrigado

Unknown disse...

Glauber, ótimo post. Veja se entendi. Coloco o banco com os dados na pasta assets. Crio então o banco vazio qdo a app é iniciada e depois copia os dados do banco que está em assets para o banco criado pelo aplicativo que vai estar em branco, certo? Como que posso fazer para assim que copiar tudo apagar esse banco na pasta assets?

Unknown disse...

Esse método funciona ao instalar o apk? Onde no código referencia a pasta "assets"?

Nelson Glauber de Vasconcelos Leal disse...

Oi Unknown,

1) Você terá que ter uma classe que centralize o acesso a essa função e te retorne uma instância do banco. Nela, você checa se o banco existe, caso não exista, copia o banco. E usar essa classe em todo lugar que precisar do banco. Leve em consideração que o usuário pode apagar o banco a qualquer momento indo na opção "Clear Data" no settings do aparelho.
Outro detalhe é que o conteúdo do assets está no APK e não pode ser apagado.
2) Esse método deve ser chamado por você em todos os lugares onde você precisar do banco, por isso disse pra criar uma classe que centralize isso. A referência a pasta assets está aqui:
ctx.getAssets().open(nomeDB);

4br4ç05,
nglauber