Использование 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