7 dicas valiosas de uso do SQLite no Android

SQLite é o nono banco de dados mais utilizado do mundo e provavelmente o #1 do segmento móvel, embora essa informação seja mais difícil de mensurar. Mas provavelmente essa minha afirmação é verdade uma vez que SQLite roda em dispositivos Android, em iOS, em Blu-ray players e diversos dispositivos Smart (como as famosas TVs). E sabemos também que existem mais desses dispositivos no mundo do que servidores capazes de rodar Oracles e cia.

Curiosamente, não acho muito conteúdo bom a respeito desse banco de dados na Internet, principalmente em Português, e por isso resolvi criar esse post para ajudar quem está começando a criar seus apps que usam banco de dados local, como exemplificado neste post completão.

E então, preparado para conhecer as melhores dicas de SQLite para seus apps Android?

As dicas que veremos são (clique no link para ver uma específica):

  1. Boas práticas de conexões SQLite
  2. Como criar e atualizar banco de dados corretamente no Android
  3. Como utilizar um banco SQLite já existente
  4. Tamanho máximo da base SQLite
  5. Ferramentas ORM para Android/SQLite
  6. Como paginar consultas no SQLite
  7. SQLite e os tipos de dados

#1 – Boas práticas de conexões SQLite

Vamos partir do princípio: a conexão com o banco de dados SQLite.

A classe SQLiteOpenHelper que criamos para lidar com as conexões (e criação do banco de dados) nos fornece os métodos getReadableDatabase e getWritableDatabase, conexão de leitura e de escrita com o banco de dados, respectivamente. Se você criar a conexão em modo de leitura, somente SELECTs serão permitidos, mas o desempenho geral do seu banco será melhor (mais rápido).

Até aí tudo bem, isso sempre é explicado nos cursos. Mas você sabia que para cada instância do objeto da classe SQLiteOpenHelper (no exemplo temos sua subclasse DatabaseHelper) temos apenas uma conexão com o banco?

Isso pode lhe gerar alguns problemas se não lidar adequadamente com sua conexão e/ou instâncias de SQLiteOpenHelper, como usá-la de maneira static sem pensar antes ou se estiver operando com multithreading (com processos rodando em background no Android, por exemplo).

“Ah, mas então é só usar várias instâncias de SQLiteOpenHelper e terei múltiplas conexões ao mesmo tempo…”.

Não se você estiver usando conexões em modo de escrita (writable). Nesse caso, se a escrita for ao mesmo tempo, uma delas irá falhar. A dica aqui é: use apenas uma instância de SQLiteOpenHelper (singleton?). Se realmente precisar de várias conexões simultâneas, certifique-se de que somente uma conexão será em modo de escrita e as demais somente leitura. Ponto.

Outras dicas extremamente válidas, para reduzir o tempo de lock na base é usar transações, assim como você faria em bancos tradicionais. Uma última dica ainda é o uso de índices nas colunas usadas como filtro em WHEREs. Nenhuma novidade, não é mesmo? Infelizmente não vejo muitos devs se preocupando com o SQL da mesma forma que se preocupam com um MySQL.

#2 – Como criar e atualizar banco de dados no Android corretamente

No momento em que chamamos um dos dois métodos para obter conexão com o banco de dados é que os métodos onCreate ou onUpgrade da classe SQLiteOpenHelper serão chamados. Até que este momento chegue, o seu banco SQLite não existe ainda no smartphone do usuário.

Quando o banco não existe no dispositivo, o método onCreate é disparado automaticamente na primeira solicitação de conexão, executando os seus comandos SQL de criação de tabelas e salvando no banco a informação do número de versão do mesmo (número esse que é passado no construtor de SQLiteOpenHelper). Caso não aconteça nenhum erro dentro do onCreate, assume-se que o banco foi criado com sucesso.

Agora, se o banco já existir no dispositivo e no momento da instanciação de SQLiteOpenHelper for passado um número de versão diferente do existente no banco local, ao invés do método onCreate, o método onUpgrade será chamado automaticamente. Nesse caso, é nesse método que colocamos os comandos SQL para alteração de tabelas e dados, significando que o banco de dados será atualizado.

A dica aqui é: só use o onUpgrade se o app já estiver em produção, com usuários utilizando-o e você não tiver como comunicá-los para reinstalar o app. Isso porque o onUpgrade é extremamente complexo de implementar de maneira correta, uma vez que você terá de escrever uma série de scripts que vão alterar o banco de dados com o mesmo sendo executado, o que é algo extremamente perigoso para a integridade do mesmo. Em ambiente de desenvolvimento, ou de produção controlada (um app para uso pelos funcionários de sua empresa, por exemplo), o ideal é limpar os dados do app nas configurações do aplicativo ou mesmo reinstalá-lo.

Em um caso ainda mais caótico, onde o seu app já tenha passado por diversas versões de banco de dados e seus usuários podem estar “saltando” entre as versões sem passar pelas intermediárias (ex: está na versão 1 mas a versão mais recente é a 3, e ele mandou atualizar agora) a recomendação é que você faça as alterações incrementais a cada versão até a mais recente, usando um switch, testando a versão “antiga” do usuário e fazendo os ajustes SQL necessários para ele avançar cada uma das versões e cada vez (note a falta de breaks nos cases 1 e 2, pois somente na 3 é que iremos parar):

#3 – Como utilizar um banco SQLite já existente

Muitos desenvolvedores preferem modelar (e até mesmo popular) seu banco SQLite em ferramentas externas como SQLite Manager e SQLite Studio e depois ficam na dúvida sobre como importar esse banco pra dentro do app.

Primeiro, salve o seu arquivo do banco dentro da pasta assets no seu app.

Segundo, use o código abaixo como exemplo para criar a sua DbHelper (classe que herda de SQLiteOpenHelper responsável por criar o banco na primeira conexão, lembra?) que carregará o banco já existente:

Para o correto uso do banco já existente, você terá de mandar criar o banco em seu DbAdapter, como no exemplo abaixo:

Para usar essa classe corretamente, dê uma olhada no exemplo abaixo:

#4 – Tamanho máximo da base SQLite

Em diversos pontos da documentação oficial do SQLite é dito que ele não é indicado para aplicações com uso intensivo de dados ou com grandes quantidades de dados, embora isso soe um tanto subjetivo. Na página sobre limites, muito completa aliás, fala-se que  tamanho teórico que o SQLite comporta é 140TB. Sim, terabytes.

Achei muito engraçado quando li isso, pois mesmo SGBDs mais corporativos como SQL Server tem sérios problemas quando chegam na casa das dezenas de GB sem uma arquitetura eficiente, imagina o coitado do SQLite que todo mundo usa de qualquer jeito…A pergunta que não quer calar é:

Na prática, que cuidados eu devo tomar com o tamanho do arquivo do meu banco SQLite?

Procurando por cases de uso intenso de SQLite, consegui achar estes números, que não são lá muito conclusivos, mas que são melhor do que nada:

  • usando apenas uma tabela com 8 colunas e 4 índices: o teste deveria inserir 50GB de dados (que é o limite de tamanho por tabela do MySQL, por exemplo), mas após 48h ainda não havia terminado, pois o tempo dos INSERTs estava astronômico (culpa dos índices vs tamanho).
  • usando 700 tabelas com 8 colunas e 4 índices cada, foi possível inserir os 50GB de teste sem grandes problemas com os INSERTs, no entanto a fragmentação de disco ficou absurda, dificultando um pouco os SELECTs depois.

Sendo assim, a conclusão que podemos tirar disso é: evite tabelas muito grandes, mas não e preocupe tanto com a quantidade de tabelas.

Onde fica o arquivo do banco de dados?

Antes de encerrar essa dica, muitos alunos me perguntam onde o emulador do Android guarda a base SQLite e como pegar esse arquivo, até mesmo para verificar o tamanho dele. Não é muito difícil de achar (embora pudesse ser mais fácil…), com o seu emulador rodando o app, abra o Android Device Monitor (fica no meu de ferramentas do Android) e use a opção de verificar o sistema de arquivos para ter acesso à estrutura de pastas do emulador, incluindo a pasta data/data.package-name/databases onde o banco fica dentro. Basta usar o ícone do disquete para salvar o arquivo do SQLite no seu PC.

Caso esteja testando o app direto no smartphone, essa dica só funciona se o app estiver rodando como root.

#5 – Ferramentas ORM para Android/SQLite

O uso de ferramentas de mapeamento de banco de dados (ORM – Object-Relational Mapping) são praticamente regra no mercado de trabalho. Afinal, elas tornam o trabalho dos CRUDs (além de outras tarefas) muito mais fáceis uma vez que o programador não precisa se preocupar com SQL, apenas com objetos.

Mas…existe algum ORM que possa ser usado com SQLite? Não seriam essas bibliotecas muito pesadas?

As respostas são: sim e depende, respectivamente.

Para elucidar essa questão, separei abaixo alguns ORM popularmente usados com SQLite (nem pense em colocar um Hibernate dentro do Android ou você vai acabar o com telefone do usuário!):

  • ORMLite: documentação com muitos exemplos, tamanho minúsculo.
  • GreenDAO ORM: bem famoso e muito utilizado em apps de produção pois consome pouca RAM, pouco disco e tem uma performance muito boa.
  • ORMDroid: um ORM minúsculo que provavelmente roda em qualquer smartphone
  • ActiveAndroid: equipe de desenvolvimento bastante ativa
  • DroidParts: possui injeção de dependência e uma equipe ativa
  • AndrORM: sem comentários
  • Sprinkles: sem comentários, mas sugerido por um programador experiente
  • Realm.io: esse aqui não é apenas um ORM, mas todo um mecanismo de persistência standalone que substitui o SQLite, funcionando inclusive para iOS também.

Tem algo a declarar sobre os ORMs acima? Alguma outra sugestão que eu não lembrei? Deixe nos comentários!

#6 – Como paginar consultas no SQLite

Os cuidados com consultas/queries no SQLite são praticamente os mesmos que em outros bancos de dados mais robustos como SQL Server e MySQL, por exemplo: paginação. Sempre é uma boa ideia retornar somente o número de linhas que você irá usar imediatamente e jamais trazer toda uma tabela principalmente em um dispositivo limitado como um smartphone Android.

Aqui entra em cena a palavra reservada LIMIT, velha conhecida dos usuários de MySQL, mas que no SQL Server seria o equivalente ao TOP. Com a LIMIT nós especificamos o número máximo de elementos que queremos que nossa consulta retorne, da seguinte maneira:

Neste exemplo, retornaremos apenas os 5 primeiros elementos retornados pela query. Mas e se queremos os 5 elementos após o décimo elemento (ou outra posição arbitrária qualquer)?

Aí usamos o OFFSET, que seria algo como um SKIP, ignorando alguns elementos retornados pela consulta, antes de aplicar o LIMIT:

Um atalho de linguagem (syntax sugar) seria como abaixo, onde apenas a ordem dos parâmetros é invertida:

Se você nunca teve de paginar dados no SQLite, não sabe como esse comando acima é útil. 🙂

#7 – SQLite e os tipos de dados…

O SQLite possui uma tipagem muito fraca em suas colunas, embora seja existente. Além de possuir poucos tipos de dados, eles podem ser praticamente ignorados em diversas circunstâncias.

No entanto, a falta de dois tipos específicos causa muita confusão em desenvolvedores acostumados com bancos mais tradicionais como SQL Server e MySQL: datas (dates, times e datetimes) e booleans (bits). Logo, vou quebrar essa dica em duas partes:

Datas no SQLite

Se o seu objetivo é apenas armazenar as datas para ser exibida depois (você não vai filtrar por ela, embora seja possível), você pode armazená-la como um texto no formato UTC: yyyy-MM-dd HH:mm:ss, que é o formato default quando você chama a função datetime(‘now’) do SQLite (hmmm, seria essa outra dica interessante?). Salve nesse formato e, mais tarde, quando quiser exibir pro usuário a data no formato que ele conhece, use o método abaixo:

 

Agora, se você quer poder pesquisar a data e até mesmo ter uma performance boa com o seu armazenamento e retorno, eu sugiro transformar a sua data em um número inteiro e armazená-la assim. Não é nada prático, principalmente se você gosta de fazer consultas no banco para ver como as coisas estão por lá, mas muito eficiente.

Para transformar seu objeto Date em um inteiro, use o seguinte método:

Ou, dependendo de como você monta seus INSERTs no SQLite…

Já para transformar o inteiro novamente em Date, pode usar o construtor da classe Date que recebe um long por parâmetro, como no método abaixo de exemplo:

O mais bacana dessa abordagem é a eficiência até mesmo em queries usando essa coluna como ordenação. Apenas lembre-se de sempre salvar a data UTC como inteiro no banco caso esteja usando a classe Calendar do Java para regionalizar e adaptar as time-zones do dispositivo do usuário.

Booleans no SQLite

Não, SQLite não possui um tipo boolean. No entanto, a própria documentação sugere uma solução para isso:

“SQLite does not have a separate Boolean storage class. Instead, Boolean values are stored as integers 0 (false) and 1 (true).”

Em uma tradução literal: SQLite não tem uma classe de armazenamento Boolean separada. Ao invés disso, valores Boolean são armazenados como inteiros 0 (false) e 1 (true).

Simples, não?!

E se você acha isso pouco performático, lembre-se que o INTEGER do SQLite possui tamanho dinâmico, partindo de 1 byte (8 possibilidades) até 8 bytes (equivalente a um LONG), sendo assim, usar um INTEGER como Boolean vai ocupar 8-bit de disco.

E aí, tem alguma dica valiosa de SQLite que queira compartilhar de graça com o pessoal? Deixe aí nos comentários!

* OBS: curtiu o post? Então dá uma olhada no meu livro de Android clicando no banner abaixo pra aprender a criar outros tantos apps incríveis!

Criando apps para empresas com Android

O que achou desse artigo?
[Total: 19 Média: 3.7]

Publicado por

Luiz Duarte

Pós-graduado em computação, professor, empreendedor, autor, Agile Coach e programador nas horas vagas.