Использование J2ME. Часть 3
Обзор
Данная часть будет не очень большой. В ней речь пойдет об организации долговременного хранения информации в мобильном устройстве с помощью пакета javax.microedition.rms.
Record Management System
Как и все в профиле MIDP система долговременного хранения информации реализована максимально просто. Все хранилище состоит из произвольного колличества RecordStore, которые в свою очередь состоят из произвольного количества записей. Запись является по сути дела массивом байт. Диаграмма основных классов представлена на рис.1.
Рисунок 1.
Cинглетон (ну почти синлетон :), является фабрикой для объектов опять же класса RecordStore. С помощью метода openRecordStore(String, boolean) Вы открываете или создаете новое хранилище записей, а далее добавляете, удаляете, изменяете или ищете в нем записи спомощью доступных методов.
RecordEnumeration
Итератор по множеству записей. Объект, реализующий этот интерфейс возвращается методом RecordStore.enumerateRecords
(RecordFilter, RecordComparator). Среди обычных для итератора возможностей можно выделть то, что во-первых итератор
двунаправленный, а во-вторых он умеет быть up-to-date всем изменениям в записях, по которым он ходит. Множество записей
для итератора задается с помощью фильтра и компаратора.
RecordFilter
Интерфейс, который должен реализовать класс, если он хочет быть фильтром для выборки записей из хранилища. При выборке
методу matches(byte[]) передается кандидат для выборки, если он подходит, метод должен вернуть true.
RecordComparator
Используется для сортировки записей при выборке. Методу compare(byte[], byte[])
передается две записи, которые нужно сравнить.
RecordListener
Вешается на хранилище записей. RecordStore будет уведомлять его о добавлении записи, изменении или удалении.
Использование RecordStore
Понятно, что использование RecordStore в таком виде, как оно описано выше, весьма затруднительно. Принимая во внимание то, что в CLDC и MIDP отсутствует механизм сериализации объектов, это становиться совсем очевидным. Поэтому предлагается создать некоторую обертку для хранилища данных в соответствую со спецификой нашей задачи.
Вспоминая постановку задачи описанную в первой части статьи мы приходим к выводу, что было бы неплохо сохранять в долговременной памяти мобильного устройства следующие параметры:
- Имя хоста (или адрес) сервера системы
- Порт, на котором слушает сервер
- Наш логин и пароль в системе
- Список пользователей (аналогия из аськи - Contact List)
И так, нужно хранить строки и вектора (цифровой параметр "порт" сохраним тоже в виде строки).
Данную задачу будем решать так:
- введем константный идентификатор для каждого параметра, который нужно сохранить (пускай он будет представлен байтом, константный он потому, что не меняет свое значения между перезапусками приложения)
- при сохранении записи этот идентификатор будет записан перед самой полезной информацией
- при извлечении записи идентификатор будет являться критерием поиска необходимого параметра
- вектор хранить очень просто, нужно сохранять строки с одинаковыми идентификаторами
- не будем вводить метод для сохранения вектора, вместо этого несколько раз вызовем метод для сохранения строки
Сначала введем класс - фильтр по первому байту записи.
private class FirstByteFilter implients RecordFilter
{
private byte criteria;
// инициализируем класс нужным
// константным идентификатором
public FirstByteFilter(byte criteria)
{
this.criteria = criteria;
}
// метод возвращает true, если первый байт записи
// совпадает с идентификатором
public boolean matches(byte[] candidate)
{
return (candidate[0] == criteria);
}
}
Таким образом, с учетом что того, что RecordStore уже открыт, методы для сохранения/чтения строк и чтения векторов будут выглядить так:
/**
* метод для чтения строки
* @param criteria константный идентификатор записи
* @return строка
*/
private String getString(byte criteria)
{
String str = "";
try
{
// выполняем поиск запрашиваемого параметра
RecordEnumeration re = rs.enumerateRecords(
new FirstByteFilter(criteria), null, false);
// если нашли - извлекаем строку из массива
// байт пропуская первый байт,
// т.к. он - идентификатор
if (re.numRecords() > 0)
{
byte[] data = re.nextRecord();
str = new String(data, 1, data.length - 1);
}
} catch (Exception e)
{
e.printStackTrace();
}
return str;
}
/**
* метод для сохранения строки
* если уже существует сторка с данным идентификатором,
* этот метод заменит ее значение на новое
* @param criteria идентификатор
* @param value строка для сохранения
*/
private void setString(byte criteria, String value)
{
try
{
// ищем старую запись с данным идентификатором
RecordEnumeration re = rs.enumerateRecords(
new FirstByteFilter(criteria), null, false);
// делаем из строки массив байт,
// с первым байтом идентификатором
byte[] tmp = value.getBytes();
byte[] data = new byte[tmp.length + 1];
data[0] = criteria;
Systi.arraycopy(tmp, 0, data, 1, tmp.length);
// если запись с данным идентификатором уже есть,
// то заменяем ее значение
// если нет, то добавляем новую запись
if (re.numRecords() > 0)
{
int id = re.nextRecordId();
rs.setRecord(id, data, 0, data.length);
} else
{
rs.addRecord(data, 0, data.length);
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
/**
* метод для чтения вектора
* @param criteria first byte of necessary record
* @return Vector of found strings
*/
private Vector getVector(byte criteria)
{
Vector vec = null;
try
{
// ищем все записи с подходящим идентификатором
RecordEnumeration re = rs.enumerateRecords(
new FirstByteFilter(criteria), null, false);
// если нашли, то создаем новый вектор и
// набиваем его значениями из итератора
if (re.numRecords() > 0)
{
vec = new Vector(re.numRecords());
while (re.hasNextElient())
{
byte[] data = re.nextRecord();
// обратите опять внимание, что при создании строки
//мы пропускаем первый байт записи
vec.addElient(new String(data, 1, data.length - 1));
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
return vec;
}
Теперь введем идентификаторы для всех параметров а также методы для сохранения/извлечения каждого параметра отдельно.
/** prefix for user name record */
private static final byte RECORD_USERNAME = 0x01;
/** prefix for password record */
private static final byte RECORD_PASSWORD = 0x02;
/** prefix for server name record */
private static final byte RECORD_SERVERNAME = 0x10;
/** prefix for server port record */
private static final byte RECORD_SERVERPORT = 0x11;
/** prefix for user list record */
private static final byte RECORD_USERLIST = 0x20;
/**
* return user name or null
* if user name not found in record store
*/
public String getUserName()
{
return getString(RECORD_USERNAME);
}
/**
* save user name in record store
*/
public void setUserName(String userName)
{
setString(RECORD_USERNAME, userName);
}
/**
* return server name or null
* if not found in record store
*/
public String getServerName()
{
return getString(RECORD_SERVERNAME);
}
/**
* save server name in record store
*/
public void setServerName(String serverName)
{
setString(RECORD_SERVERNAME, serverName);
}
/**
* return user list or null
* if not found in record store
*/
public Vector getUserList()
{
return getVector(RECORD_USERLIST);
}
И так далее.
Осталась еще одна проблема - сохранение списка пользователей. Решим ее так - введем метод для добавления имени пользователя в список, а также метод для очистки списка пользователей (т.е. по сути удаления всех записей с одинаковыми идентификатороми).
/**
* clear user list record store
*/
public void clearUserList()
{
try
{
// получаем список всех записей,
// которые нужно удалить
RecordEnumeration re = rs.enumerateRecords(
new FirstByteFilter(RECORD_USERLIST), null, false);
// удаляем все найденные записи
if (re.numRecords() > 0)
{
while (re.hasNextElient())
{
rs.deleteRecord(re.nextRecordId());
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
/**
* этот метод похож на setString(),
* за одним исключением - он
* не проверяет присутствие записи
* с таким же идентификатором
*/
public void addUserToList(String user)
{
try
{
byte[] tmp = user.getBytes();
byte[] data = new byte[tmp.length + 1];
data[0] = RECORD_USERLIST;
Systi.arraycopy(tmp, 0, data, 1, tmp.length);
rs.addRecord(data, 0, data.length);
}
catch (Exception e)
{
e.printStackTrace();
}
}
В завершении данной части статьи приведем диаграмму классов того, что получилось.
Рисунок 2. ClientDataStore




Узнай о чем ты на самом деле сейчас думаешь

