Este tutorial descreve os passos para utilização da persistência em dispositivos móveis, a API Record Management System (RMS) do MIDP de uma forma que traga uma certa simplicidade para os desenvolvedores construir suas aplicações.

Introdução ao J2ME

Desenvolver sistemas para dispositivos móveis requer mais que um amontoado de códigos. É importante saber como armazenar dados de forma simples nestes aparelhos, para um busca rápida de dados, por exemplo.Assim vendo inúmeros pedidos de iniciantes no desenvolvimento J2ME, resolvi participar desta oportunidade com este tutorial com um exemplo simples de serializar e armazenar objetos no um tanto complicado “RecordStore” Com este tutorial espero trazer a simplicidade do desenvolvimento aos iniciantes.

Apresentação

Antes de entender a serialização vamos então aos requisitos para o desenvolvimento, IDE's, SDK's entre outros. Podemos começar inicialmente fazendo o download do JDK no site da SUN, após concluído download efetue a instalação.Após o término da instalação configure o CLASSPATH corretamente para disponibilizar as APIs padrões do Java para sua aplicação. Algo como CLASSPATH=C:\j2sdk(versao)\lib;.

Faça o download do WTK no site da SUN, após concluído download efetue a instalação no diretório padrão \WTK (versão). Faça o download do Eclipse, dê preferência à versão 3.0, pois é a versão em que o plugin J2ME funciona corretamente. Após download descompacte o arquivo na raiz do diretório principal de seu micro \eclipse.

Faça o download também do plugin J2ME para o Eclipse em http://eclipseme.org/. Após download descompacte arquivo em diretório temporário, neste diretório obtenha dentro da pasta “plugin” os arquivos existentes copie estes para a pasta plugins do diretório do Eclipse.

Finalmente Inicie o Eclipse com o Eclipse.exe existente no diretório eclipse, inicialmente o eclipse deverá solicitar a criação de um diretório de trabalho workspace, mantenha este diretório junto ao diretório \eclipse. Com o IDE aberto, teremos uma tela de boas vindas do eclipse, feche a tela de boas vindas, selecione na barra de tarefas Window >> Open Perpective >> Java, assim teremos a perspectiva que facilitando a visualização de pacotes.

Após este selecione Window >>> Preferences, clique e “Plataform Components” na janela direita clique em Wireless Toolkits >> Add Wireless Toolkit”. Indique o caminho onde foi efetuada a instalação do WTK.

Finalmente reinicie o Eclipse, pronto assim teremos o nosso plugin J2ME perfeitamente instalado. Vamos seguir para a criação da nossa Agenda.

Criando projeto Crie um novo projeto J2ME (File >> New Project >> J2ME >

”, clique com o direito no folder src e crie o pacote descrito acima. Criar também os pacotes entity, midlet, rms e view, abaixo do pacote criado anteriormente. Dentro do pacote entity criar a Midlet principal do aplicativo com o mesmo nome – Agenda.Java. (File >> New >> Other >> J2ME >> J2ME Midlet).

Vamos agora ao que interessa. A API RMS está localizada no pacote ax.microedition.rms e possui as seguintes classes e interfaces: RecordStore, RecordEnumeration, RecordComparator, RecordFilter e RecordLister. Um RecordStore é criado por um MIDlet, q por suas vez faz parte de uma suite de MIDlets. Isso significa, que para o MIDP 1.0, suites de MIDlets nao conseguem acessar RecordStores de suites diferentes, algo que já é diferente para o MIDP 2.0. Vejamos algumas regras para o uso dos RecordStores:

  • Os nomes dos RecordStores são Case Sensitive, devem ter entre 1 e 32 caracteres Unicode.
  • Quando uma suite de MIDlets é removida, todos os RecordStores associados à ela são removidos.
Como abrir e criar RecordStores

Este trecho de código faz parte da classe BaseRMS.


/**
 * Responsável pela abertura do RecordStore para leitura e gravação.
 * @throws Exception - Caso algum erro ocorra.
 */public void open() throws Exception {
      try {
          recordStore = RecordStore.openRecordStore(this.rmsName,true);
      } catch (Exception e) {
            throw new Exception(this.rmsName+"::open::"+e);
      }
}


/**
 * Responsável pelo fechamento do RecordStore.
 * @throws Exception - Caso algum erro ocorra.
 */
public void close() throws Exception {
      if (recordStore != null) {
            try {
                  recordStore.closeRecordStore();
            } catch(Exception e) {
                  throw new Exception(this.rmsName+"::close::"+e);
            }
      }
}

Como podemos ver, o método openRecordStore irá abrir o RecordStore de nome passado no atributo rmsName da classe BaseRMS do nosso projeto; se este já estiver aberto por outro MIDlet, uma referência será retornada. Se ele não existir e o segundo parâmetro for true, o RecordStore é criado. Se fosse falso, um RecordStoreException seria lançado. Ao adicionar os dados ao RecordStore, um ID é retornado. Esse ID é um numero sequencial que inicia do número um. Para se remover um RecordStore, usa-se o método estático deleteRecordStore. Antes de fazer a remoção, o RecordStore deve estar fechado, como foi feito no comando: recordStore.closeRecordStore();

Gravando informações. Este trecho de código faz parte da classe PersistenceContato que estende a classe BaseRMS.


protected void storeData() throws Exception {
      int id = this.getRecordStore().addRecord(this.data,0, data.length);
      this.close();
}

Alguns outros métodos da classe RecordStore são:

  • getLastModified: informa a data (retorna um long) da últuma modificação;
  • getSize: retorna o tamanho ocupado (em bytes);
  • getSizeAvailable: retorna o tamanho disponível (em bytes);
  • getVersion: retorna o número da versão. Para cada operação realizada (inclusão, alteração, deleção) é atualizado o número de versão.
  • addRecordListener e removeRecordListener: para gerenciar os eventos gerados com o RecordStore
  • listRecordStores: método static que retorna um array de Strings com os nomes de todos os RecordStores dentro de uma suite de MIDlets.

Vamos então ver como foi implementado a persistência da nossa agenda, inicialmente foi criada uma classe abstrata base para a persistência, a classe BaseRMS.java este classe defini a criação do RecordStore, abertura e fechamento do Record criado pela nosso aplicativo. Posteriormente foi criada a classe PersistenceContato.java, esta classe estende a classe BaseRMS.java que possui ainda os métodos abstratos que devem ser subscritos pela classe que ira estende-la. Isto não e obrigatório, mas e interessante que se tenha uma classe que defina a assinatura dos métodos da persistência. Métodos que devem ser subscritos na classe PersistenceContato.

Este trecho de código faz parte da classe BaseRMS


protected abstract void loadData() throws Exception;
protected abstract void storeData() throws Exception;
protected abstract void updateData(int index) throws Exception;
protected abstract void removeData(int index) throws Exception;
protected abstract void loadIndexData(int index) throws Exception;

Apresentamos no quadro 2 o método storeData da classe PersistenceContato, este subscrito e incluído o array de bytes data como atributo da classe, temos nesta classe também o atributo que damos nome ao RecordStore criado RMS_CONTATO e ainda um array de String lista para capturar o nome de cada contato que será visualizado na listagem da camada de apresentação.

Criando a camada de persistência. Este trecho de código faz parte da classe PersistenceContato.



public class PersistenceContato extends BaseRMS{
      private final static String RMS_CONTATO = "CONTATO";
      private byte[] data;
      private String[] lista;
      public PersistenceContato(byte[] data) {
            super(RMS_CONTATO);
            this.data = data;
            try {
                  this.open();
                  this.storeData();
            } catch (Exception e) {
                  e.printStackTrace();
            }
      }

Bom, mas vamos mais fundo no assunto, o que precisamos entender e como transformar nossos dados em bytes de forma simples, para que possamos efetuar leituras e buscas no RecordStore.

Assim criamos uma interface Serializable.java que determina novamente a assinatura dos métodos e que seja efetuada a subscrição dos mesmos. Gostaria de esclarecer que estas classes foram criadas para facilitar a implementação de uma aplicação de aplicação de maior porte, pois este tutorial deve servir para que os leitores possam criar aplicações que sejam de seu interesse, e não somente uma simples agenda. Ainda que estas classes básicas servem para aplicações de sincronismo e leitura em desktop com JSE.

Vamos a um exemplo de um software de força de vendas ou controle de estoque, onde e preciso enviar os dados para uma base de dados externa ao móbile tendo em vista que os dispositivos atuais possuem pouca capacidade de armazenamento e preciso providenciar uma forma de sincronismo via http, por exemplo, enviando a um servidor em forma de bytes para que este fique com a carga de processamento dos dados, tendo em vista que nossa aplicação tenha a melhor performance em sua utilização.

Assim estas classes devem servir de base para a criação de um aplicativo que se encarregue de processar estas informações. Vamos seguir com nosso tutorial, assim foi criada a classe Contato.java que implementa a interface Serializable.java e assim implementamos os métodos de forma a serializar os atributos da classe.

Os métodos “serializar e deserializar” são mostrados no quadro 5, utiliza-se ByteArrayOutputStream e DataOutputStream para a serialização de forma que todos os atributos tenham sidos transformados em bytes, o método retorna o array dos mesmos. Utilizamos writeUTF para a leitura de Strings que foram as formas dos nossos atributos, mas e possível criar atributos com outros variações de primitivas, “int, boolean, long, char , etc..(veja API de DataOutputStream).

E para deserializar utilizamos ByteArrayInputStream e DataInputStream e assim deserializamos os dados e transformamos em Objetos para facilitar a manipulação.

Implementando Serializable.java. Este trecho de código faz parte da classe Contato.


/**
* Responsável por serializar o objeto Contato, transformar em um array de
 * bytes para que este seja armazenado em um RecorsStore.
 * 
 * @return byte[] data - Os dados do objeto Contato serializado, um 
rray    de bytes.
 * @see DataInputStream para serializar outros tipos primitivos de dados.
 * @throws IOException, InterruptedException
 */
public byte[] serializar() throws IOException, InterruptedException {
      ByteArrayOutputStream bOut = new ByteArrayOutputStream(); 
      DataOutputStream dOut = new DataOutputStream(bOut);
      dOut.writeUTF(this.nome);
      dOut.writeUTF(this.telefone);
      dOut.writeUTF(this.email);
      //retorna o array de dados.
      return bOut.toByteArray();}

      

/**
* Responsável pela deserialização do objeto Contato, transformar em um 
bjeto
 * completo para que este possa ser manipulado normalmente dentro do 
plicativo.
 * 
 * @see DataInputStream para serializar outros tipos primitivos de dados.
 * @throws IOException, InterruptedException
 */
public void deserializar(byte[] data) throws IOException,    InterruptedException {
      ByteArrayInputStream bIn = new ByteArrayInputStream(data); 
      DataInputStream dIn = new DataInputStream(bIn); 
      this.nome = dIn.readUTF();
      this.telefone = dIn.readUTF();
      this.email = dIn.readUTF();
}

Então estamos com as classes para persistência definidas, agora poderemos trabalhar na implementação do código na apresentação e manipulação destes dados.

Vamos inserir os dados via UI com usuário, a nosso Midlet deverá inicialmente quando de sua abertura chamar uma listagem com o nome dos contatos armazenados no RecordStore, não existindo contatos armazenados pode ainda mostrar um alerta ao usuário.

Assim na classe principal Agenda.java o método startApp() (quadro 6) deve efetuar a chamada de uma lista com estes contatos, para isto foi criada a classe ListarContatos.java que e instanciada quando se inicia a nossa Midlet, a chamada do método listaContatos() (quadro 7) efetua as chamadas de outros métodos responsáveis pela leitura dos dados de contatos armazenados no RecordStore.

Este trecho de código faz parte da classe Agenda:


protected void startApp() throws MIDletStateChangeException {
      //iniciar aplicativo com a lista de contatos existentes
      // cria uma instância da classe ListarContatos.
      lista = new ListarContatos();
      //chamada do método listarContato, efetua o carregamento dos         contatos cadastrados.
      lista.listaContatos();
      lista.addCommand(this.novoCmd); //adiciona o botão novo contato a        listagem de contatos.
      lista.addCommand(this.detalheCmd); //adiciona o botão detalhe a        listagem de contatos.
      lista.addCommand(this.infoCmd); //adiciona o botão info a listagem        de contatos.
      lista.setCommandListener(this); //adiciona o Listener a listagem        para os eventos dos botões.
      Display.getDisplay(this).setCurrent(lista.retornaTela());
}

Este trecho de código faz parte da classe ListarContato


/**
 * Responsável por efetuar as chamadas dos metodos para a
 * criação da listagem de contatos.
 */
public void listaContatos() {
      //cria a instância da classe PersistenceContato      
        PersistenceContato contatos = new PersistenceContato();
      // recebe a lista com o nome dos contatos cadastrados no         RecordStore.
      list = contatos.listaContatos();
      // carrega a lista de nomes de contatos neste List.
      this.carregaListaContatos();
}

Seguindo o fluxo da implementação vamos até a classe PersistenceContato.java para verificar o método listarContato() (quadro 8) , que em sua implementação efetua a chamada do método loadData() (quadro 8), este método e responsável por efetuar a leitura dos dados de cada contato armazenado.

Este trecho de código faz parte da classe PersistenceContato:



public String[] listaContatos()    {
      try {
            this.loadData();
      } catch (Exception e) {
            e.printStackTrace();
      }
      return lista;
}


protected void loadData() throws Exception {
      RecordEnumeration num;
      int cont = 0;
      num = this.getRecordStore().enumerateRecords(null,null,false);
      lista = new String[num.numRecords()];
      while(num.hasPreviousElement())    {
            data = num.previousRecord();
            Contato.getInstance().deserializar(data);
            this.lista[cont] = Contato.getInstance().getNome();
            cont++;
      }
      this.close();
}
Relacionado