Introdução

O Android fornece várias opções para salvar os dados de aplicativos persistentes. A solução que for escolhida irá depender de necessidades específicas, como se os dados devem ser privados de sua aplicação ou acessíveis a outras aplicações (e de usuário) e quanto espaço os seus dados requerem.

Vamos mostrar as seguintes formas de armazenamento neste artigo:

  • SharedPreferences: Armazenar dados particulares primitivos em pares chave-valor.
  • Internal Storage: Armazenar dados privados na memória do dispositivo. ( com persistência de objetos )
  • External Storage: Armazenar dados públicos sobre o armazenamento externo compartilhado.
  • SQLite Databases: Armazenar dados estruturados em um banco de dados privado.

SharedPreferences

A classe SharedPreferences permite salvar e recuperar pares de chave/valor de tipos de dados primitivos. Podemos usar o SharedPreferences para salvar os dados primitivos: booleans, floats, ints, longs, e strings. Estes dados irão persistir na sessão do usuário (mesmo que sua aplicação seja morta).

Para obter um objeto SharedPreferences para sua aplicação, utilize um dos dois métodos:

  • getSharedPreferences(String nome, int modo) – Utilize se você precisa de vários arquivos de preferências identificados pelo nome que será passado no primeiro parâmetro.
  • getPreferences(int modo) – Utilize se você só precisa de um arquivo de preferências para a sua atividade.

Para escrever valores:

  • Chamar o método edit() para obter uma SharedPreferences.Editor;
  • Adicionar valores com métodos tais como putBoolean() e putString();
  • Persistir os novos valores com commit().

Para ler os valores do SharedPreferences utilize os métodos como getBoolean() e getString().

Abaixo está um exemplo de como armazenar e retirar dados de uma preferência:

Listagem 1: Exemplo de persistência com SharedPrefereces


    public static final String PREFS_NAME = "MyPrefsFile";
    private boolean teste;


    private void armazenar(){
       // Precisamos de um objeto Editor para fazer mudanças de preferência.
       // Todos os objetos são de android.context.Context
      SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
      SharedPreferences.Editor editor = settings.edit();
      editor.putBoolean("teste", teste);

      // Commit as edições
      editor.commit();
    }
    
    private void recuperar(){
       SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
       teste = settings.getBoolean("teste", false);
    }

Armazenamento interno

Você pode salvar arquivos diretamente na memória interna do aparelho. Por padrão, os arquivos salvos para o armazenamento interno são privados de sua aplicação, permitindo que outros aplicativos não possam acessá-los. Quando o usuário desinstala o aplicativo, esses arquivos são removidos.

Para criar e gravar um arquivo privado para o armazenamento interno:

  • Chamar openFileOutput() com o nome do arquivo e o modo de funcionamento (no caso MODE_PRIVATE). Isso retorna um FileOutputStream;
  • Escreva no arquivo com o write();
  • Fechar o fluxo com close().

Exemplo:

Listagem 2: Exemplo de persistencia simples com FileOutputStream


String FILENAME = "hello_file";
String string = "hello world!";

FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);
fos.write(string.getBytes());
fos.close();

Apesar da vantagem de se gravar em arquivo não seja essa, este recurso é utilizado com eficiência para gravar arquivos mais complexos ou até mesmo objetos serializáveis.

Podemos gravar qualquer objeto em disco se ele implementar a interface Serializable do pacote java.io, isso nos permite construir um objeto padrão de configuração, controlando melhor os dados persistentes. Mas lembre-se que se alterarmos esse objeto depois de gravado ele irá gerar um problema de deserialização quando tentarmos retornar ele para memória.

Abaixo segue um exemplo de um objeto que implementa Serializable:

Listagem 3: Objeto com a interface Serializable


public class ObTeste implements  Serializable {
    private int codigo;
    private String descricao;
    public ObTeste(int codigo, String descricao) {
        this.codigo = codigo;
        this.descricao = descricao;
    }
    public int getCodigo() {
        return codigo;
    }
    public void setCodigo(int codigo) {
        this.codigo = codigo;
    }
    public String getDescricao() {
        return descricao;
    }
    public void setDescricao(String descricao) {
        this.descricao = descricao;
    }
}

Dada a classe, para grava-la é só seguir o passo da Listagem 2, porém vamos gravar o objeto como um todo veja:

Listagem 4: Gravando objeto serializable


//Para Gravar
String FILENAME = "hello_file";
File file =getFileStreamPath(FILENAME);

FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(objeto);
oos.close();
fos.close();

//Para Ler
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
ObTeste retorno = (ObTeste) ois.readObject();
fis.close();
ois.close();

Armazenamento externo

Cada dispositivo compatível com Android suporta uma “memória externa” compartilhada que você pode usar para salvar arquivos. Isso pode ser uma mídia de armazenamento removível (como um cartão SD) ou uma memória interna (não removível). Os arquivos salvos para o armazenamento externo são de leitura para todos e podem ser modificados pelo usuário quando permitem armazenamento em massa USB para transferir arquivos de um computador.

Antes de fazer qualquer trabalho com o armazenamento externo, você deve sempre chamar Environment.getExternalStorageState() para verificar se a mídia está disponível. A mídia pode estar montada a um computador, faltando, somente leitura, ou em algum outro estado.

Listagem 5: Verificando estado da media


boolean mExternalStorageAvailable = false;
boolean mExternalStorageWriteable = false;
String state = Environment.getExternalStorageState();

if (Environment.MEDIA_MOUNTED.equals(state)) {
    // Podemos ler e escrever os meios de comunicação
    mExternalStorageAvailable = mExternalStorageWriteable = true;
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
    // Só podemos ler a mídia
    mExternalStorageAvailable = true;
    mExternalStorageWriteable = false;
} else {
     // Outra coisa que está errada.  Pode ser um de muitos outros estados, mas tudo o que precisamos
     // Para saber é que não sabem ler nem escrever
    mExternalStorageAvailable = mExternalStorageWriteable = false;
}

Acessar arquivos em armazenamento externo

Vamos usar o recurso getExternalFilesDir() para abrir um File que representa o diretório de armazenamento externo onde você deve salvar seus arquivos. Este método requer um parâmetro type que especifica o tipo de subdiretório que você deseja, como: Environment.DIRECTORY_MUSIC e Environment.DIRECTORY_RINGTONES (null para receber a raiz do diretório do seu aplicativo). Este método irá criar o diretório apropriado, se necessário. Ao especificar o tipo de diretório, você garante que o scanner de media do Android será devidamente categorizado em seus arquivos do sistema (por exemplo, ringtones são identificados como ringtones e não como música). Se o usuário desinstala o aplicativo, este diretório e todo seu conteúdo é apagado.

Se você deseja salvar os arquivos que não são específicos para sua aplicação e que não devem ser excluídos quando o aplicativo é desinstalado, esses diretórios devem ficar na raiz do armazenamento externo, como Music/, Pictures/, Ringtones/, e outros. Para isso vamos utilizar o método Environment.getExternalStoragePublicDirectory(), passando o tipo de diretório público que você quer, como: Environment.DIRECTORY_MUSIC, Environment.DIRECTORY_RINGTONES ou outros. Este método irá criar o diretório apropriado, se necessário.

O método de gravação é similar aos exemplos já mostrados usando FileOutputStream e Input para ler.

Listagem 6: Abrindo file publico e gravando dados


        File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
        File arquivo = new File(dir, "teste.obj");

        FileOutputStream fos = new FileOutputStream(arquivo);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(objeto);

Banco de dados SQLite

Um Banco de dados é muito útil para qualquer sistema de grande ou pequeno porte, a não ser que seu sistema lida apenas com dados simples, não utilizando de um banco para armazenar informações.

O Android usa o banco de dados SQLite que é open-source, e muito utilizado em aplicações populares. Outros exemplo de quem utiliza o SQLite são o Mozilla Firefox e iPhone.

No Android o banco de dados que você cria em uma aplicação só é acessível para a mesma, a não ser que você utilize um provedor de conteúdo. Uma vez criado o banco de dados, ele é armazenado no diretório “/data/data/{nome do pacote}/databases/{nome do banco}”, além de gerenciar o banco por código você pode fazê-lo pelo adb utilizando a ferramenta sqlite3.

O método recomendado para criar um banco de dados SQLite novo é criar uma subclasse de SQLiteOpenHelper e sobrescrever o método onCreate(), no qual você pode executar um comando SQLite para criar tabelas no banco de dados.

Veja abaixo como criar o gerenciador do banco:

Listagem 7: Classe responsável por gerenciar o banco de dados


public class Banco extends SQLiteOpenHelper {

    public Banco(Context context, String name, int version) {
        super(context, name, null, version);
    }

    @Override
    public void onCreate(SQLiteDatabase sqld) {
        sqld.execSQL("CREATE TABLE usuarios_tbl ("
                + "id_usuarios INTEGER PRIMARY KEY autoincrement,"
                + " usuario varchar(45) NOT NULL ,"
                + " senha varchar(45) NOT NULL,"
                + " nome_completo varchar(45) NOT NULL"
                + ");");
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqld, int i, int i1) {
        //IMPLEMENTAR
    }
}

Para utilizar vamos instanciá-la passando o nome e a versão (inicia em 1), se a versão for alterada, o método onUpgrade é chamado e lá podemos fazer qualquer tipo de tratamento para alteração do banco.

Após instanciar vamos ter acesso ao banco através do método getWritableDatabase(), com ele podemos fazer insert,delet,update,select entre outros recursos.

Listagem 8: Utilizando a classe Banco


        //Construção do banco
        Banco banco = new Banco(this, "blaa", 1);
        
        //Insert
        ContentValues contentValues = new ContentValues();
        contentValues.put("usuario", "guilherme");
        contentValues.put("senha", "123");
        contentValues.put("nome_completo", "Guilherme Biff Zarelli");
        banco.getWritableDatabase().insert("usuarios_tbl", null, contentValues);

        //Select na coluna usuario, nome_completo
        Cursor cursor = banco.getWritableDatabase().query("usuarios_tbl", new String[]{"usuario", "nome_completo"}, null, null, null, null, null);
        while (cursor.moveToNext()) {
            System.out.println("-> " + cursor.getString(0));
            System.out.println("-> " + cursor.getString(1));
        }
        //Lembre-se de sempre fechar o cursor
        cursor.close();

Conclusão

Em quase todos os software desenvolvidos necessitamos de persistência de dados, imagine um sistema que contém usuário e senha, ou até mesmo um menu de configurações onde você possa ter um perfil próprio. Todos esses sistemas tendem a armazenar dados em seu dispositivo, o recurso de persistência deve ser estudado a fundo para escolher a melhor maneira de se fazer, para não utilizarmos recursos avançados em sistemas simples ou vice-versa.

Com isso finalizo esse artigo, um grande abraço e obrigado.

Referências

Developer Android, Using Shared Preferences, acessado em 16-01-2013