Nickolay.info. Тексты. Java2ME. Сохраняем и читаем данные мидлета

Для сохранения и восстановления данных мидлетов существует традиционный путь - использование хранилища данных Recordstore. Другое дело, что корректно обратиться к нему непросто, что и стало причиной написания этой заметки.

Предполагается, что мы установили всё, что нужно для работы с мобильной явой, если это не так, сначала читаем здесь.

Наш мидлет будет состоять из 2 классов - первый, midlet.java, будет содержать форму, данные которой нужно автоматически сохранять и восстанавливать, а второй, recordStores.java, мы напишем, чтобы выполнять его методами сохранение и чтение данных.

Как минимум, классу recordStores понадобятся следующие методы:

Вот этот класс:

import javax.microedition.lcdui.*;
import javax.microedition.rms.*;
import java.io.*;

class recordStores {
 private RecordStore rs=null;
 String name = "RecordstoreExample";

 public recordStores () {
  try {
   rs = null;
   rs = RecordStore.openRecordStore (name,true);
  }
  catch (RecordStoreFullException e) { rs=null; }
  catch (RecordStoreNotOpenException e) { rs=null; }
  catch (RecordStoreException e) { rs=null; } 
 }

 public int getNumRecords () {
  if (rs!=null) {
   try { return rs.getNumRecords (); }
   catch (RecordStoreNotOpenException e) { return 0; }
   catch (RecordStoreException e) { return 0; }
  }
  else return 0;
 }

 public String getRecord (int n) {
  String s=null;
  if (rs!=null) {
   try {
    byte[] arrData = rs.getRecord(n);
    ByteArrayInputStream bytes = new ByteArrayInputStream(arrData, 0, rs.getRecordSize(n));
    DataInputStream dis = new DataInputStream(bytes);
    s=dis.readUTF();
   }
   catch (IOException ioe) { return null; }        
   catch (RecordStoreException ex) { return null; }
   return s;
  }
  else return null;
 }

 public boolean setRecord (int n,String s) {
  if (rs!=null) {
   ByteArrayOutputStream bytes = new ByteArrayOutputStream();
   DataOutputStream dos = new DataOutputStream (bytes);
   try {   
    dos.writeUTF(s);
    rs.setRecord(n, bytes.toByteArray(), 0, bytes.toByteArray().length);
   }
   catch (IOException ioe) { return false;  }
   catch (InvalidRecordIDException ridex) {
    try {
     rs.addRecord (bytes.toByteArray(), 0, bytes.toByteArray().length);            
    }              
    catch (RecordStoreException ex) { return false; }
   } 
   catch (RecordStoreException ex) { return false; }
   return true;
  } 
  else return false;
 }

 public boolean closeRecords() {
  if (rs!=null) {
   try {
    rs.closeRecordStore();      
    rs=null;
   }
   catch (RecordStoreException ex) { return false; }
   return true;
  }
  else return false;
 }

 public boolean deleteAll () {
  if (rs==null) {
   try {
    RecordStore.deleteRecordStore (name);
   }
   catch (RecordStoreNotFoundException ex) { return false; }
   catch (RecordStoreException ex) { return false; }
   return true;
  }
  else return false;
 }

}

Обратите внимание, что каждое хранилище имеет имя, мы своё обозвали строкой

String name = "RecordstoreExample";

Отдельных методов для работы с данными-строками и данными-числами нам не нужно, всё равно Recordstore читает-пишет только строки (точней, массивы байт byte[], которые класс преобразует к строкам). Просто не будем забывать после чтения преобразовать строку к нужному типу данных (или оставить всё как есть, если строка и читается), а при записи, если пишется число, сложить его с пустой строкой кодом вроде ""+n.

Разумеется, ничто не мешает написать метод, который сразу будет читать и возвращать целое число, например,

 public int getRecord (int n) {
  int v=0;
  if (rs!=null) {
   try {
    byte[] arrData = rs.getRecord(n);
    ByteArrayInputStream bytes = new ByteArrayInputStream(arrData, 0, recordLength);
    DataInputStream dis = new DataInputStream(bytes);
    v=dis.readInt();
   }
   catch (IOException ioe) { return Integer.MIN_VALUE; }
   catch (RecordStoreException ex) { return Integer.MIN_VALUE; }
   return v;
  }
  else return Integer.MIN_VALUE;
 }

- в случае ошибки здесь возвращается константа Integer.MIN_VALUE.

Естественно, в реальном приложении мы должны сначала определить формат хранимых записей - сколько их будет и в каком порядке они будут читаться и писаться. Здесь для простоты примем, что нам нужно сохранить данные из числового поля ввода, данные из поля ввода, содержащего произвольную строку текста и положение переключателя-чекбокса на форме. Всё остальное нетрудно сделать по аналогии.

Напишем класс midlet.java, создающий форму с 2 полями ввода, 1 чекбоксом и 1 командой выхода. Тем не менее, при загрузке этот мидлет восстанавливает последнее содержимое полей ввода и состояние чекбокса (кроме первого запуска, когда хранилище ещё не создано), а перед выходом сохраяет эти данные в хранилище. Вот полный листинг класса:

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class midlet extends MIDlet implements CommandListener { 
 private Display display;
 public recordStores rs=null;
 public Command Quit;
 TextField number,text;
 ChoiceGroup checkbox;
 String options[]={"","","",""};
 boolean isStarted = false;
 Form form;

 public midlet () {
  display = Display.getDisplay(this);
  Quit = new Command("Quit", Command.EXIT, 0);
  number=new TextField ("Number:","",16,TextField.NUMERIC);
  text=new TextField ("String:","",16,TextField.ANY);
  checkbox = new ChoiceGroup ("", ChoiceGroup.MULTIPLE);
  checkbox.append ("Checkbox", null); 
  form = new Form ("Recordstore demo");
  form.addCommand(Quit);
  form.append (number);
  form.append (text);
  form.append (checkbox);
  form.setCommandListener(this);
 }

 private void Read () {
  String s;
  rs = new recordStores ();
  for (int n=1; n<4; n++) {
   s=rs.getRecord (n);
   if (s!=null && s.trim().length()>0) options[n]=s;
  }
 }

 private void Write () {
  String item=number.getString();
  rs.setRecord (1,item);
  item=text.getString();
  rs.setRecord (2,item);
  int n;
  if (checkbox.isSelected(0)==true) n=1; else n=0;
  rs.setRecord (3,""+n);
 }

 protected void startApp() {
  if (isStarted) return;
  Read ();
  number.setString (options[1]);
  text.setString (options[2]);
  if (options[3].compareTo("1")==0) checkbox.setSelectedIndex (0,true);
  else checkbox.setSelectedIndex (0,false);
  display.setCurrent (form);
  isStarted = true;
 }

 protected void destroyApp(boolean unconditional) { 
  Write ();
  rs.closeRecords ();
 }

 protected void pauseApp() { }

 public void quit() { 
  destroyApp(true);
  notifyDestroyed();
 }

 public void commandAction(Command c, Displayable d) {
  if (c == Quit) quit();
 }

}

В нормальном приложении стоило бы ещё проверить, менялись ли данные, а также, не испорчен ли Recordstore перед чтением (скажем, совпадает ли число записей с ожидаемым). Писать не весь массив настроек (например, не трогать первые 4 записи, а записать только изменённую пятую) не советую никогда - в следующий раз получите данные отнюдь не на тех же местах, где они были, плюс может измениться число записей в файле. Если настроек (и вообще хранимых мидлетом данных) много, просто разделите их между несколькими объектами Recordstore и работайте с каждым отдельно.

Наконец, не нужно забывать о расширении мобильной явы JSR-75, дающем полноценный доступ к файловой системе телефона. Жаль только, его поддерживают отнюдь не все модели.

Итак, для решения поставленной задачи в любом мидлете нам достаточно таких действий:

1. Включить в состав проекта класс recordStores.java

2. Описать хранилище как объект этого класса

 public recordStores rs=null;

3. При запуске мидлета, например, в методе startApp(), создать объект класса Recordstore и прочитать в цикле данные, иногда удобнее читать их в массив с именем options, в этот же массив можно засунуть значения опций по умолчанию. Нужно только учесть, что в массивах элементы занумерованы с нуля, а в Recordstore записи всегда нумеруются с единицы, так что массив options должен содержать на одну строку больше, чем имеется записей.

 String options[]={"","","",""}; //описан глобально, все значения по умолчанию пусты
 ...
 String s;
 rs = new recordStores ();
 for (int n=1; n<4; n++) {
  s=rs.getRecord (n);
  if (s!=null && s.trim().length()>0) options[n]=s;
 }

4. Рассовать прочитанные опции туда, где они должны храниться, то есть, в поля форм, значения интерфейсных элементов, просто переменные и т.п. При этом, как уже говорилось, не забываем при необходимости конвертировать данные из строк в нужные типы (обычно целые числа).

  number.setString (options[1]);
  text.setString (options[2]);
  if (options[3].compareTo("1")==0) checkbox.setSelectedIndex (0,true);
  else checkbox.setSelectedIndex (0,false);

5. При завершении мидлета, например, из метода destroyApp(), проделываем обратную операцию - собираем данные в массив options и переписываем его в Recordstore, начиная с первой записи. Ну или сразу пишем нужные значения в нужные записи, как и сделано в моём примере:

  String item=number.getString();
  rs.setRecord (1,item);
  item=text.getString();
  rs.setRecord (2,item);
  int n;
  if (checkbox.isSelected(0)==true) n=1; else n=0;
  rs.setRecord (3,""+n);

Остаётся добавить несколько слов о работе с Recordstore в компиляторе WTK. При отладке мидлетов, работающих с Recordstore, мы наверняка столкнёмся с ситуациями, когда изменилось число записей в хранилище, произошла ошибка и т.п. Чтобы всё "занулить", просто сотрите файл с именем

{папка WTK}\appdb\DefaultColorPhone\Имя_хранилища.db

- если использовали в WTK стандартный эмулятор телефона с именем DefaultColorPhone. Если у вас другой эмулятор - ищите последний по времени создания файл в папке данных этого эмулятора. Если мы оставили имя нашего хранилища RecordstoreExample, файл в WTK будет иметь имя аж run_by_class_storage_#Recordstore#Example.db Как видим, его легко "вычислить" по имени хранилища. Файл нужно удалять при закрытом окне эмулятора с мидлетом. В реальных телефонах Recordstore хранятся как и где угодно, это определяет реализация конкретной ява-машины.

  Скачать архив с этим примером в виде проекта WTK (RecordstoreExample.zip, 7 Кб)

Рейтинг@Mail.ru

вверх гостевая; E-mail