|
Pers.narod.ru. Тексты. 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 Кб)
|
|