Nickolay.info. Тексты. Java2ME. Сохраняем и читаем данные мидлета |
Для сохранения и восстановления данных мидлетов существует
традиционный путь - использование хранилища данных Recordstore
.
Другое дело, что корректно обратиться к нему непросто, что и стало
причиной написания этой заметки.
Предполагается, что мы установили всё, что нужно для работы с мобильной явой, если это не так, сначала читаем здесь.
Наш мидлет будет состоять из 2 классов - первый, midlet.java
,
будет содержать форму, данные которой нужно автоматически сохранять и
восстанавливать, а
второй, recordStores.java
, мы напишем, чтобы выполнять
его методами сохранение и
чтение данных.
Как минимум, классу recordStores
понадобятся следующие методы:
recordStores ()
- конструктор, чтоб создать экземпляр класса;
int getNumRecords ()
- получение числа записей в имеющемся хранилище;
String getRecord (int n)
- получить из хранилища запись с номером n и вернуть её как строку;
boolean setRecord (int n,String s)
- записать в хранилище под номером n запись, содержащую строку s;
boolean closeRecords()
- закрыть хранилище и вернуть результат закрытия;
boolean deleteAll ()
- удалить все записи хранилища.
Вот этот класс:
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 Кб)
гостевая; E-mail |