Quando se trata de armazenar dados de aplicativos localmente, os desenvolvedores Android têm várias opções disponíveis. Além do acesso direto às áreas de armazenamento interno e externo de um dispositivo Android, a plataforma oferece bancos de dados SQLite para armazenamento de dados relacionais e arquivos especiais para armazenar pares de chave-valores.

Além disso, os aplicativos Android também podem usar bancos de dados externos de terceiros.

Neste tutorial, vou mostrar como usar todas essas opções de armazenamento de dados local nos seus aplicativos Android. Também ajudarei você a entender como escolher a opção de armazenamento mais adequada para seus dados.

Armazenando de Pares de Chave-valor

Se você está procurando uma maneira rápida de armazenar dados de aplicativos localmente, como sequências de caracteres ou números, considere usar um arquivo Preference.

As Activities e Services do Android podem usar o método getDefaultSharedPreferences() da classe PreferenceManager para obter uma referência a um objeto SharedPreferences que pode ser usado para ler e gravar no arquivo de preferências padrão.

SharedPreferences myPreferences = PreferenceManager.getDefaultSharedPreferences(MyActivity.this);

Para começar a gravar no arquivo de preferências, você deve chamar o método edit() do objeto SharedPreferences, que retorna um objeto SharedPreferences.Editor.

SharedPreferences.Editor myEditor = myPreferences.edit();

O objeto SharedPreferences.Editor possui vários métodos intuitivos que você pode usar para armazenar novos pares de chave-valor no arquivo de preferências.

Por exemplo, você poderia usar o método putString() para colocar um par de chave-valor do tipo String. Da mesma forma, você poderia usar o método putFloat() para colocar um par de chave-valor do tipo float.

O exemplo de código a seguir cria três pares de chave-valor:

myEditor.putString("NAME", "Alice");
myEditor.putInt("AGE", 25);
myEditor.putBoolean("SINGLE?", true);

Depois de adicionar todos os pares, você deve chamar o método commit() do objeto SharedPreferences.Editor para persistir os dados.

myEditor.commit();

Ler de um objeto SharedPreferences é muito mais fácil. Tudo o que você precisa fazer é chamar o método get() apropriado. Por exemplo, para obter um par de chave-valor do tipo String, você deve chamar o método getString().

Aqui está um exemplo de código que recupera todos os valores que adicionamos anteriormente:

String name = myPreferences.getString("NAME", "unknown");
int age = myPreferences.getInt("AGE", 0);
boolean isSingle = myPreferences.getBoolean("SINGLE?", false);

Como você pode ver no código acima, como segundo parâmetro, todos os métodos get() esperam um valor padrão, que é o valor que deve ser retornado se a chave não estiver presente no arquivo de preferências.

Observe que os arquivos de preferências são limitados apenas a Strings e tipos de dados primitivos. Se você deseja armazenar tipos de dados mais complexos ou dados binários, você deve escolher uma opção de armazenamento diferente.

Usando o Banco de Dados SQLite Local

Cada aplicativo Android pode criar e usar um bancos de dados SQLite para armazenar grandes quantidades de dados estruturados.

Como você já deve saber, o SQLite não é apenas leve, mas também muito rápido. Se você tiver experiência em trabalhar com sistemas de gerenciamento de banco de dados relacional e estiver familiarizado com o SQL e JDBC, essa pode ser sua opção de armazenamento preferida.

Para criar um novo banco de dados SQLite, ou para abrir um que já exista, você pode usar o método openOrCreateDatabase() dentro de sua Activity ou Service. Como argumentos, você deve passar o nome do banco de dados e o modo em que deseja abri-lo. O modo mais usado é o MODE_PRIVATE, que garante que o banco de dados seja acessível apenas ao seu aplicativo.

Por exemplo, aqui está como você abriria ou criaria um banco de dados chamado my.db:

SQLiteDatabase myDB = openOrCreateDatabase("my.db", MODE_PRIVATE, null);

Depois que o banco de dados tiver sido criado, você poderá usar o método execSQL() para executar instruções SQL nele. O código a seguir mostra como usar a instrução CREATE TABLE para criar uma tabela chamada user, que possui três colunas:

myDB.execSQL("CREATE TABLE IF NOT EXISTS user (name VARCHAR(200), age INT, is_single INT)");

Embora seja possível inserir novas linhas na tabela usando o método execSQL(), é melhor usar o método insert(). O método insert() espera um objeto ContentValues com os valores para cada coluna da tabela. Um objeto ContentValues é muito semelhante a um objeto Map e contém pares de chave-valor.

Aqui estão dois objetos ContentValues que você pode usar com a tabela de usuários:

ContentValues row1 = new ContentValues();
row1.put("name", "Alice");
row1.put("age", 25);
row1.put("is_single", 1);
 
ContentValues row2 = new ContentValues();
row2.put("name", "Bob");
row2.put("age", 20);
row2.put("is_single", 0);

Como você pode ver, as chaves que você passa para o método put() devem corresponder aos nomes das colunas na tabela.

Quando seus objetos ContentValues estiverem prontos, você poderá passá-los para o método insert() junto com o nome da tabela.

myDB.insert("user", null, row1);
myDB.insert("user", null, row2);

Para consultar o banco de dados, você pode usar o método rawQuery(), que retorna um objeto Cursor contendo os resultados da consulta.

Cursor myCursor = myDB.rawQuery("select name, age, is_single from user", null);

Um objeto Cursor pode conter zero ou mais linhas. A maneira mais fácil de percorrer todas as suas linhas é chamar seu método moveToNext() dentro de um loop while.

Para buscar o valor de uma coluna individual, você deve usar métodos como getString() e getInt(), que esperam o índice da coluna.

Por exemplo, aqui está como você recuperaria todos os valores inseridos na tabela de usuários:

while(myCursor.moveToNext()) {
    String name = myCursor.getString(0);
    int age = myCursor.getInt(1);
    boolean isSingle = (myCursor.getInt(2)) == 1 ? true:false;
}

Depois de obter todos os resultados da consulta, certifique-se de chamar o método close() do objeto Cursor para liberar todos os recursos que ele contém.

myCursor.close();

Da mesma forma, quando você terminar todas as operações do banco de dados, não se esqueça de chamar o método close() do objeto SQLiteDatabase para fechar a conexão.

myDB.close();

Usando o Armazenamento Interno

Cada aplicativo Android tem um diretório de armazenamento interno privado associado a ele, no qual o aplicativo pode armazenar arquivos de texto e binários. Os arquivos dentro desse diretório não podem ser acessados pelo usuário ou por outros aplicativos instalados no dispositivo. Eles também são removidos automaticamente quando o usuário desinstala o aplicativo.

Antes de poder usar o diretório de armazenamento interno, você deve determinar sua localização. Para fazer isso, você pode chamar o método getFilesDir(), que está disponível nas Activities e Services.

File internalStorageDir = getFilesDir();

Para obter uma referência a um arquivo dentro do diretório, você pode passar o nome do arquivo junto com o local determinado. Por exemplo, aqui está como você obteria uma referência a um arquivo chamado androidpro.csv:

File alice = new File(internalStorageDir, "androidpro.csv");

A partir deste ponto, você pode usar seu conhecimento de classes e métodos do Java I/O para ler ou gravar no arquivo. O exemplo de código a seguir mostra como usar um objeto FileOutputStream e seu método write() para gravar no arquivo:

// Cria o output stream do arquivo
fos = new FileOutputStream(alice);
// Escreve uma linha no arquivo
fos.write("Alice,25,1".getBytes());
// Fecha o output stream do arquivo
fos.close();

Usando o Armazenamento Externo

Como a capacidade de armazenamento interno dos dispositivos Android geralmente é fixa, e muitas vezes bastante limitada, vários dispositivos Android suportam mídia de armazenamento externo, como cartões micro-SD. Eu recomendo que você use essa opção de armazenamento para arquivos grandes, como fotos e vídeos.

Ao contrário do armazenamento interno, o armazenamento externo pode nem sempre estar disponível. Portanto, você deve sempre verificar se está montado antes de usá-lo. Para fazer isso, use o método getExternalStorageState() da classe Environment.

if(Environment.getExternalStorageState()
              .equals(Environment.MEDIA_MOUNTED)) {
    // External storage is usable
} else {
    // External storage is not usable
    // Try again later
}

Quando tiver certeza de que o armazenamento externo está disponível, você poderá obter o caminho do diretório de armazenamento externo do seu aplicativo chamando o método getExternalFilesDir() e passando null como um argumento para ele.

Você pode então usar o caminho para fazer referência a arquivos dentro do diretório. Por exemplo, aqui está como você faria referência a um arquivo chamado fillipe.jpg no diretório de armazenamento externo do seu aplicativo:

File fillipe = new File(getExternalFilesDir(null), "fillipe.jpg");

Ao solicitar que o usuário conceda a permissão WRITE_EXTERNAL_STORAGE, você pode obter acesso de leitura/gravação a todo o sistema de arquivos no armazenamento externo. Você pode usar diretórios públicos conhecidos para armazenar suas fotos, filmes e outros arquivos de mídia.

A classe Environment oferece um método chamado getExternalStoragePublicDirectory() para determinar os caminhos desses diretórios públicos.

Por exemplo, ao passar o valor Environment.DIRECTORY_PICTURES para o método, você pode determinar o caminho do diretório público no qual você pode armazenar fotos. Da mesma forma, se você passar o valor Environment.DIRECTORY_MOVIES para o método, obterá o caminho do diretório público no qual os filmes podem ser armazenados.

Veja como você faria referência a um arquivo chamado fillipe.jpg no diretório de imagens públicas:

File fillipeInPictures = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),"fillipe.jpg");

Depois de ter o objeto File, você pode usar novamente as classes FileInputStream e FileOutputStream para ler ou gravar nele.

Conclusão

Agora você sabe como aproveitar ao máximo as opções para armazenar dados de aplicativos localmente fornecidas pelo Android SDK.

Independentemente da opção de armazenamento escolhida, as operações de leitura/gravação podem ser demoradas, se grandes quantidades de dados estiverem envolvidas. Portanto, para garantir que a Thread da interface do usuário principal permaneça sempre responsivo, você deve considerar a execução das operações em um Thread diferente usando uma AsyncTask.

Leia também


Fillipe Cordeiro
Fillipe Cordeiro

Engenheiro da computação e desenvolvedor de software a quase 10 anos, com experiência em tecnologias como Java, Python e Android. Agora, quero te ajudar a mergulhar no universo do Desenvolvimento Android.