Persistência de dados no Java ME

De Aulas

Voltar para Programação para Dispositivos Móveis.

A persistência de dados tem o intuito de manter informações armazenadas para serem utilizadas depois. Atualmente as aplicações se utilizam de Sistemas Gerenciadores de Bancos de Dados (SGBD) para tal. Contudo, os espaços de armazenamento de muitos dispositivos móveis, não incluindo os atuais smarphones, são bastante limitados e não há espaço ou mesmo recursos de processamento para haver um SGBD para o gerenciamento das informações.

A MIDP, especificação para o uso da plataforma Java para dispositivos móveis, não oferece acesso direto ao sistema de arquivos do dispositivo. Contudo, ela oferece o recurso Record Management System (RMS) para armazenar informações na forma de grupo de ítens, que lembra o conceito de tabelas e registros em um banco de dados.

Record Manamement System (RMS)

A utilização do pacote javax.microedition.rms fornece recursos para o gerenciamento de dados persistentes no dispositivo móvel. A classe que iremos utilizar é a RecordStore.

No dispositivo móvel em que nosso MIDlet está alocado, podemos ter uma coleção de registros chamados Record Store, identificado por uma String Java. Nesse recurso, podemos adicionar, excluir, atualizar e recuperar informações armazenadas. Chamaremos aqui o Record Store por apenas RS.

Abrindo um Record Store

Utilizando um método estático chamado openRecordStore, podemos abrir um RS para buscar as informações, caso ele não exista, então pode ser criado. O método utilizado é descrito a seguir:

RecordStore.openRecordStore(String rsname, boolean create);

Parâmetros:

  • rsname: Nome do RS;
  • create: Informa se ele deve ser criado ou não, caso este não exista.

Exceções:

  • RecordStoreException: Erros na criação do RS.
  • RecordStoreNotFoundException: RS não encontrado, se create for false.
  • RecorStoreFullException: O RS está cheio.
  • IllegalArgumentExceptions: Argumentos inválidos.

Finalmente, para fechar um RS, é utilizado o seguinte método:

closeRecordStore();

Recuperando informações de um Record Store

Os métodos utilizados para recuperar informações de um RS são os seguintes:

  • getName: nome do RS;
  • getLastModified: tempo decorrido da última modificação no RS, no formato System.currentTimeMillis();
  • getVersion: retorna um valor inteiro. Este é modificado sempre que um novo registro é inserido, excluído, ou alterado.
  • getSize: Tamanho do RS em bytes;
  • getSizeAvailable: Tamanho em bytes disponível no RS;

Excluindo um Record Store

Para excluir um RS, utilizamos um método estático chamado deleteRecordStore e passando como parâmetro o nome do RS que queremos excluir:

RecordStore.deleteRecordStore(String rsname);

As possíveis exceções :

  • RecorStoreException: Erro ao tentar excluir um RS.
  • RecordStoreNotFoundException: RS não encontrado.

É importante observar que o MIDlet só tem acesso para excluir os RS que a ele pertence, além de que ele precisa estar fechado. Caso contrário, uma exceção será lançada.

Registros no Record Store

Assim com em tabelas nos banco de dados, um RS pode possuir diversos registros. Cada registro está disposto em uma estrutura de array de bytes e possue um valor inteiro para representar cada registro. É comparativamente à um ID de uma tabela. Exemplo de registro:

byte [] meuRegistro;

Algumas observações sobre os identificadores:

  • o primeiro identificador de um RS é sempre valor 1;
  • A cada novo registro inserido no RS, o identificador será o último incluído mais 1 (n + 1);
  • Quando um registro é excluído, o identificador não pode mais ser utilizado.

Estudo de Caso: Agenda de Telefones

A seguir é apresentado um exemplo da utilização de registros, para uma agenda de telefones. Inicialmente é criada uma classe que deve representar cada registro a ser inserido no nosso RS. No caso, são contatos da nossa agendas de telefone, representados na classe Contato:

 1public class Contato {
 2	private String nome;
 3	private String telefone;
 4	private int idade;
 5	
 6	public Contato() { }
 7	
 8	public void set(String nome, String telefone, int idade) {
 9		this.nome = nome;
10		this.telefone = telefone;
11		this.idade = idade;
12	}
13	
14	public String getNome() {
15		return nome;
16	}
17	
18	public void setNome(String nome) {
19		this.nome = nome;
20	}
21	
22	public String getTelefone() {
23		return telefone;
24	}
25	
26	public void setTelefone(String telefone) {
27		this.telefone = telefone;
28	}
29	
30	public int getIdade() {
31		return idade;
32	}
33	
34	public void setIdade(int idade) {
35		this.idade = idade;
36	}
37}

Agora colocamos a classe principal, no nosso caso chamada classe Main:

  1import java.io.ByteArrayInputStream;
  2import java.io.ByteArrayOutputStream;
  3import java.io.DataInputStream;
  4import java.io.DataOutputStream;
  5import java.io.IOException;
  6import javax.microedition.lcdui.Alert;
  7import javax.microedition.lcdui.AlertType;
  8import javax.microedition.lcdui.Command;
  9import javax.microedition.lcdui.CommandListener;
 10import javax.microedition.lcdui.Display;
 11import javax.microedition.lcdui.Displayable;
 12import javax.microedition.lcdui.Form;
 13import javax.microedition.lcdui.TextField;
 14import javax.microedition.lcdui.StringItem;
 15import javax.microedition.midlet.MIDlet;
 16import javax.microedition.midlet.MIDletStateChangeException;
 17import javax.microedition.rms.*;
 18
 19public class Main extends MIDlet implements CommandListener {
 20	private Display display;
 21	private Form form;
 22	private Command sair;
 23	private Command abrir;
 24	private Command proximo;
 25	private Command adicionar;
 26	private Command atualizar;
 27	private Command excluir;
 28	private Command excluirTudo;
 29	private Command limpar;
 30	private RecordStore rs;
 31	private int id;
 32	private StringItem stringId;
 33	private TextField nome;
 34	private TextField telefone;
 35	private TextField idade;
 36
 37	public Main() {
 38		display = Display.getDisplay(this);
 39		form = new Form("Agenda de Telefone");
 40		abrir = new Command("Abrir", Command.OK, 0);
 41		proximo = new Command("Proximo", Command.OK, 0);
 42		adicionar = new Command("Adicionar", Command.OK, 0);
 43		atualizar = new Command("Atualizar", Command.OK, 0);
 44		excluir = new Command("Excluir", Command.OK, 0);
 45		excluirTudo = new Command("Excluir Tudo", Command.OK, 0);
 46		limpar = new Command("Limpar", Command.OK, 0);
 47		sair = new Command("Exit", Command.SCREEN, 1);
 48		form.addCommand(limpar);
 49		form.addCommand(abrir);
 50		form.addCommand(proximo);
 51		form.addCommand(adicionar);
 52		form.addCommand(atualizar);
 53		form.addCommand(excluir);
 54		form.addCommand(excluirTudo);
 55		form.addCommand(sair);
 56		form.setCommandListener(this);
 57
 58		id = 1;
 59		stringId = new StringItem("id", "");
 60		nome = new TextField("Nome", "", 15, TextField.ANY);
 61		telefone = new TextField("Telefone", "", 15, TextField.ANY);
 62		idade = new TextField("Idade", "", 3, TextField.ANY);
 63		form.append(stringId);
 64		form.append(nome);
 65		form.append(telefone);
 66		form.append(idade);
 67
 68		try {
 69			rs = RecordStore.openRecordStore("agenda", true);
 70		} catch (RecordStoreFullException e) {
 71			message("Erro", e.getMessage());
 72		} catch (RecordStoreNotFoundException e) {
 73			message("Erro", e.getMessage());
 74		} catch (RecordStoreException e) {
 75			message("Erro", e.getMessage());
 76		}
 77	}
 78
 79	protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
 80		try {
 81			rs.closeRecordStore();
 82		} catch (RecordStoreNotOpenException e) {
 83			e.printStackTrace();
 84		} catch (RecordStoreException e) {
 85			e.printStackTrace();
 86		}
 87	}
 88
 89	protected void pauseApp() {
 90	}
 91
 92	protected void startApp() throws MIDletStateChangeException {
 93		display.setCurrent(form);
 94	}
 95
 96	public void commandAction(Command cmd, Displayable d) {
 97		if (cmd == sair) {
 98			try {
 99				destroyApp(false);
100			} catch (MIDletStateChangeException e) {
101				e.printStackTrace();
102			}
103			notifyDestroyed();
104		}
105		if (cmd == limpar) {
106 			id = 0;
107			stringId.setText("");
108			nome.setString("");
109			telefone.setString("");
110			idade.setString("");
111		}
112		/*
113		 *  O resto dos comandos são comentados de
114		 *  forma mais detalhada logo a seguir.
115		 */
116	}
117
118	private void message(String title, String msg) {
119		Alert alert = new Alert(title, msg, null, AlertType.INFO);
120		display.setCurrent(alert);
121	}
122}

O método commandAction agora mostrado de forma comentada, cada if específico em relação aos tratamentos de persistência das informações.

Adicionar Registro

Para adicionar registros, addRecord, é necessário a utilização do método addRecord. Contudo, de forma a facilitar a entrada de informações no RS, trabalhamos com Streams de saída. É importante também, que nem addRecord passemos o tamanho em bytes das informações, no caso, utilizamos o método length (data.length).

if (cmd == adicionar) {
	try {
		Contato contato = new Contato();
		int intIdade = Integer.valueOf(idade.getString()).intValue();
		contato.set(nome.getString(), telefone.getString(), intIdade);

		// Cria um stream de saida
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		DataOutputStream os = new DataOutputStream(baos);

		// Escreve os valoers no stream de saida
		os.writeUTF(contato.getNome());
		os.writeUTF(contato.getTelefone());
		os.writeInt(contato.getIdade());
		os.close();

		// Retorna a array de bytes para ser salva
		byte[] data = baos.toByteArray();

		// Escreve o registro no Record Store
		id = rs.addRecord(data, 0, data.length);
		stringId.setText(String.valueOf(id));
	} catch (RecordStoreNotOpenException e) {
		message("Erro", e.getMessage());
	} catch (RecordStoreFullException e) {
		message("Erro", e.getMessage());
	} catch (RecordStoreException e) {
		message("Erro", e.getMessage());
	} catch (IOException e) {
		message("Erro", e.getMessage());
	}
}

Atualizar Registro

A atualização, setRecord, funciona tal como a função de adicionar registro. Contudo, é necessário que passemos também a informação de qual id do registro que queremos atualizar.

if (cmd == atualizar) {
	if (id < 1) return;
	try {
		Contato contato = new Contato();
		int intIdade = Integer.valueOf(idade.getString()).intValue();
		contato.set(nome.getString(), telefone.getString(), intIdade);

		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		DataOutputStream os = new DataOutputStream(baos);

		os.writeUTF(contato.getNome());
		os.writeUTF(contato.getTelefone());
		os.writeInt(contato.getIdade());
		os.close();

		byte[] data = baos.toByteArray();
		// Escreve o registro, sobrescrevendo se existir
		rs.setRecord(id, data, 0, data.length);
	} catch (RecordStoreNotOpenException e) {
		message("Erro", e.getMessage());
	} catch (RecordStoreFullException e) {
		message("Erro", e.getMessage());
	} catch (RecordStoreException e) {
		message("Erro", e.getMessage());
	} catch (IOException e) {
		message("Erro", e.getMessage());
	}
}

Recuperar Registro

Em ambos os comandos, utilizamos um método que busca um registro a partir do atributo global da classe chamado id. Para isso, a gente atualiza o id e chamamos abrirContato.

if (cmd == abrir) {
	id = 1;
	stringId.setText(String.valueOf(id));
	abrirContato();
}
if (cmd == proximo) {
	id++;
	stringId.setText(String.valueOf(id));
	abrirContato();
}

O método abrirContato se utiliza de streams de entrada para buscar as informações. Caminho oposto do adicionar informações. O método utilizado no Record Store para buscar as informações, necessita que se passe como parâmetro o id que se quer recuperar.

private void abrirContato() {
	try {
		byte[] data;
		data = rs.getRecord(id);
		DataInputStream is = new DataInputStream(new ByteArrayInputStream(data));
		Contato contato = new Contato();
		contato.set(is.readUTF(), is.readUTF(), is.readInt());
		is.close();
		nome.setString(contato.getNome());
		telefone.setString(contato.getTelefone());
		idade.setString(String.valueOf(contato.getIdade()));
	} catch (IOException e) {
		message("Erro", e.getMessage());
	} catch (RecordStoreNotOpenException e) {
		message("Erro", e.getMessage());
 	} catch (InvalidRecordIDException e) {
		message("Erro", e.getMessage());
	} catch (RecordStoreException e) {
		message("Erro", e.getMessage());
	}
}

Excluir Registro

Para excluir um registro, basta utilizar o método deleteRecord, passando como parâmetro o id do registro que se quer excluir. Lembrando que um id não pode mais ser utilizado.

if (cmd == excluir) {
	try {
		rs.deleteRecord(id);
	} catch (RecordStoreNotOpenException e) {
		message("Erro", e.getMessage());
	} catch (InvalidRecordIDException e) {
		message("Erro", e.getMessage());
	} catch (RecordStoreException e) {
		message("Erro", e.getMessage());
	}
}

Excluir Tudo

Neste comando, na verdade, é feito um reset do Record Store. Dessa forma, os ids perdidos podem ser reutilizados. Para isso, fechamos o RS com closeRecordStore, apagamos o mesmo com deleteRecordStore e o criamos novamente com openRecordStore.

if (cmd == excluirTudo) {
	try {
		rs.closeRecordStore();
		RecordStore.deleteRecordStore("agenda");
		rs = RecordStore.openRecordStore("agenda", true);
	} catch (RecordStoreFullException e) {
		message("Erro", e.getMessage());
	} catch (RecordStoreNotFoundException e) {
		message("Erro", e.getMessage());
	} catch (RecordStoreException e) {
		message("Erro", e.getMessage());
	}
}