Desenvolvimento para Android - Carregando um banco SQLite criado externamente

19 Feb 2012 . category: article . Comments
#android #java #desenvolvimento #mobile #carnandroid

Introdução

No artigo anterior, Trabalhando com SQLite, ao criar o banco de dados nós também o populamos com algumas informações que eram necessárias para nossa aplicação. Neste caso eram poucas as informações que iriamos utilizar.

Agora, no caso de termos um banco de dados com um números razoável de informação seria interessante utilizarmos um arquivo sqlite criado previamente. Este artigo tem como objetivo mostrar como podemos utilizar um arquivo do sqlite criado externamente em nossa aplicação.

Criação do banco de dados

Seguindo nosso exemplo iremos criar um banco de dados no sqlite chamado linguagens.sqlite. Os comandos sql estão logo abaixo, você pode utilizar sua aplicação favorita para criar este banco ou se você não tem nenhuma experiencia com o sqlite pode seguir os seguintes passos. O sqlite3 está instalado junto com o Android SDK. O comando pode ser encontrado em android-sdk/tools/sqlite3.

./sqlite3 linguagens.sqlite

Após isso digite os comandos sql abaixo.

CREATE TABLE linguagens (
  id INT AUTO_INCREMENT,
  nome VARCHAR(100),
  descricao VARCHAR(200),
  primary key(id)
);

INSERT INTO linguagens VALUES(1, 'Ruby', 'Sobre a linguagem Ruby');
INSERT INTO linguagens VALUES(2, 'Python', 'Sobre a linguagem Python');
INSERT INTO linguagens VALUES(3, 'Perl', 'Sobre a linguagem Perl');
INSERT INTO linguagens VALUES(4, 'Lua', 'Sobre a linguagem Lua');
INSERT INTO linguagens VALUES(5, 'PHP', 'Sobre a linguagem PHP');
INSERT INTO linguagens VALUES(6, 'Go', 'Sobre a linguagem Go');

Além da tabela com nossas informações, também é necessário criarmos uma tabela de controle do android, para isso utilizaremos os seguintes comandos sql. Se essa tabela não for criada, o sistema irá disparar um exception ao abrir o arquivo.

CREATE TABLE "android_metadata" ("locale" TEXT DEFAULT 'pt_BR');

INSERT INTO "android_metadata" VALUES ('pt_BR');

Após ter o arquivo criado, copie o mesmo para o diretório assets da aplicação.

Usando o banco de dados criado

Agora teremos que fazer algumas alteracões na classe DatabaseHelper para utilizar o banco que criamos ao invés de criar um novo arquivo. Mais um vez, todas as explicações sobre as mudanças realizadas estão comentadas no próprio código e qualquer dúvida pode ser envidas através do sistema de comentários do site.

Arquivo: DatabaseHelper.java

package com.diegorubin.search_list;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;

public class DatabaseHelper extends SQLiteOpenHelper {

  /**
   * Este é o endereço onde o android salva os bancos de dados criado pela aplicação,
   * /data/data/<namespace da aplicacao>/databases/
   */
  private static String DBPATH = "/data/data/com.diegorubin.search_list/databases/";

  // Este é o nome do banco de dados que iremos utilizar
  private static String DBNAME ="linguagens.sqlite";
  
  private Context context;
  
  /**
   * O construtor necessita do contexto da aplicação
   */
  public DatabaseHelper(Context context) {
    /* O primeiro argumento é o contexto da aplicacao
     * O segundo argumento é o nome do banco de dados
     * O terceiro é um pondeiro para manipulação de dados, 
     *   não precisaremos dele.
     * O quarto é a versão do banco de dados
     */
    super(context, "linguagens.sqlite", null, 1);
    this.context = context;
  }
  
  /**
   * Os métodos onCreate e onUpgrade precisam ser sobreescrito
   */
  @Override
  public void onCreate(SQLiteDatabase db) {
    /*
     * Estamos utilizando o banco do assets, por isso o 
     * código antigo deste método não é mais necessário.
     */
  }
  
  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    /*
     * Estamos criando a primeira versão do nosso banco de dados,
     * então não precisamos fazer nenhuma alteração neste método.
     * 
     */
  }
  
  /**
   * Método auxiliar que verifica a existencia do banco
   * da aplicação.
   */
  private boolean checkDataBase() {
    
    SQLiteDatabase db = null;
    
    try {
      String path = DBPATH + DBNAME;
      db = 
        SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
      db.close();
    } catch (SQLiteException e) {
      // O banco não existe
    }
    
    // Retorna verdadeiro se o banco existir, pois o ponteiro irá existir,
    // se não houver referencia é porque o banco não existe
    return db != null;
  }
  
  private void createDataBase() 
  throws Exception {
    
    // Primeiro temos que verificar se o banco da aplicação
    // já foi criado
    boolean exists = checkDataBase();
    
    if(!exists) {
      // Chamaremos esse método para que o android
      // crie um banco vazio e o diretório onde iremos copiar
      // no banco que está no assets.
      this.getReadableDatabase();
      
      // Se o banco de dados não existir iremos copiar o nosso
      // arquivo em /assets para o local onde o android os salva
      try {
        copyDatabase();
      } catch (IOException e) {
        throw new Error("Não foi possível copiar o arquivo");
      }
      
    }
  }
  
  /**
   * Esse método é responsável por copiar o banco do diretório
   * assets para o diretório padrão do android.
   */
  private void copyDatabase()
  throws IOException {
    
    String dbPath = DBPATH + DBNAME;
    
    // Abre o arquivo o destino para copiar o banco de dados
    OutputStream dbStream = new FileOutputStream(dbPath);
    
    // Abre Stream do nosso arquivo que esta no assets
    InputStream dbInputStream = 
      context.getAssets().open("linguagens.sqlite");
    
    byte[] buffer = new byte[1024];
    int length;
    while((length = dbInputStream.read(buffer)) > 0) {
      dbStream.write(buffer, 0, length);
    }
    
    dbInputStream.close();
    
    dbStream.flush();
    dbStream.close();
    
  }
  
  public SQLiteDatabase getDatabase() {
    
    try{
      // Verificando se o banco já foi criado e se não foi o
      // mesmo é criado.
      createDataBase();
      
      // Abrindo database
      String path = DBPATH + DBNAME;
      
      return SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READWRITE);
    }catch (Exception e) {
      // Se não conseguir copiar o banco um novo será retornado
      return getWritableDatabase();
    }

  }
}

Consideração importante

Lembrando que estamos em um dispositivo com bem menos recursos de memória que um computador temos que levar em consideração o tamanho do arquivo que iremos copiar no diretório assets. Tive problemas ao abrir um arquivo com 4mb. Por isso recomendo que para evitar problemas, se você for utilizar essa técnica divida o arquivo em pedaços menores e ao copiar o banco leia um pedaço por vez e escreva-os no dbStream. No GNU/Linux essa divisão pode ser feita utilizando o comando split, como no exemplo abaixo:

split arquivo.sqlite -b 1000000

O comando acima divide o arquivo.sqlite em pedaços de 1000000 bytes ou quase 1 mega.

Então o código do método copyDatabase() ficaria assim:

private void copyDatabase()
throws IOException {
  
  String dbPath = DBPATH + DBNAME;
  
  // Abre o arquivo o destino para copiar o banco de dados
  OutputStream dbStream = new FileOutputStream(dbPath);
  
  // NUMERO_PEDACOS: número total de arquivos gerado após a divisão
  // PREFIX: string que prefixa cada pedaço
  for(int i = 1; i <= NUMERO_PEDACOS; i++){
    // Abre Stream do nosso arquivo que esta no assets
    InputStream dbInputStream = 
      context.getAssets().open(PREFIX + i);
  
    byte[] buffer = new byte[1024];
    int length;
    while((length = dbInputStream.read(buffer)) > 0) {
      dbStream.write(buffer, 0, length);
    }
  
    dbInputStream.close();
  }
  
  dbStream.flush();
  dbStream.close();
  
}

Conclusão

Minha intenção com este arquivo era demonstrar como utilizar um banco de dados criado previamente em uma aplicação android. Esse tipo de procedimento foi necessário em uma aplicação que desenvolvi e espero que seja útil para mais alguém.

Para esse exemplo foi necessário a alteração apenas de um arquivo do projeto que estamos utilizando de exemplo. Criei uma tag no repositório do blog e o projeto completo pode ser obtido lá.

Qualquer dúvida utilizem o sistema de comentário do site, terei o maior prazer em respondê-las.


Me

Tenho estudado esse mundo mágico da programação desde 2005. Já consegui sustentar minha família usando Ruby, Java, Python, C++ e Javascript. O resto tenho usado para diversão ou aprendizado.