Вступление, постановка задачи
Во время учёбы в университете, был сайт с расписанием занятий. Он упрощал информирование, но нужно было постоянное подключение к сети. Если интернета нет или он медленный, ты не узнаешь какие у тебя пары, если не успел сохранить. Также требовалось постоянно проверять информацию на свежесть. Исходя из этого, была поставлена задача реализовать мобильное приложение. Примерный концепт был озвучен и переведен в техническое задание, которое периодически дополнялось.
В процессе разработки я столкнулся со многими проблемами: определение верхней и нижней недели, создание своих адаптеров, поиск изменений. Опыт проектирования андроид-приложений был очень мал, что-то я решал по статьям, что-то самостоятельно. Я расскажу, как решал проблему определения изменений.
В своём решении я постепенно улучшал базовый алгоритм сравнения. В заданных условиях это было единственным решением. Благодаря оптимизациям, я ускорил проверку получаемой информации на изменение, добавив структуры данных и алгоритм хеширования. Надеюсь, этот рассказ поможет вам в изучении или подтолкнет кого-то на использование подобных методов в задачах.
Первое решение
Для начала, стоило реализовать простое обновление. Загрузил, преобразовал, сохранил. Приложение делало запрос на сервер и получала данные. Принятый JSON быстро разбирала библиотека GSON. Алгоритм проходил по сущностям, попутно собирая главное расписание в массив по дням. Информация преобразовывалась в строку и сохранялась в SharedPreferences, доставалась она оттуда уже при входе.
SharedPreferences — это
класс хранилища на платформе Android, используемый для хранения различной информации о конфигурации приложения. В нём данные сохраняются в XML-файл, в виде пар «ключ-значение».
Когда логика загрузки и сохранения была готова, стоял вопрос проверки. Нужно было как-то понять расходятся ли данные на сервере и на устройстве. Расписание могло часто меняться, несколько раз в день, а доступа к серверу у меня не было.
Я изначально стал хранить обработанный JSON расписания пользователя в памяти телефона. Поэтому, берём новый с сервера и достаем локальный. Обрабатываем и ищем различия по сущностям. Алгоритм, брал занятия пользователя, искал группу. Проходясь по ней, сравнивал информацию о паре. Нашли несоответствия. Данные сохраняем, пользователя уведомляем. Сложность всех действий была O(n) по размеру массива сохраненных занятий и работала довольно быстро, не учитывая сортировку по дням недели и времени.
В процессе в техническое задание дополнялось. Появилась задача сохранения дополнительных расписаний помимо основного. К ним нужно применить функциональность главного. Теперь пользователю, можно было добавить отдельно занятия других групп, преподавателей, а также время работы кабинетов.
Хорошо. Расширяем алгоритм, применяя логику к вспомогательным, а это уже становится довольно проблемно, выгрузи - найди - сравни. Хранятся они обособленно друг от друга. Расписания постоянно достаются из внутреннего хранилища, когда пользователь его ищет в сохраненных. А при обновлении - постоянно, по нескольку раз. И так с каждым. Добавление новых всё усложняло.
Читатель может сказать, можно вообще не смотреть на них и не проверять. Просто замени. Увы. Вернемся к техническому заданию и увидим "нужно применить функциональность главного". Об изменениях можно было не писать, но указать пользователю на них нужно. Идём дальше.
Добавление структуры данных HashMap
На этом моменте, я отошел от хранения полученного расписания в массиве классов, формировал структуру данных HashMap. Ключом являлась группа, преподаватель или кабинет, а значением - информация о занятиях. Ключи готовы и открывали дверь в быстрый поиск, теперь не нужно было искать по всем сущностям. Это позволяло применять коллекцию не только к задаче нахождения изменений, но и к поиску дополнительного расписания.
Алгоритм стал быстрее, но все же требовал дополнительные расходы на выгрузку, так как обновление обычно происходило в другом окне.
Окончательная версия, хеш-функция
На идею использования криптографических алгоритмов пришёл в процессе их изучения и сфер применения. Из столь обширной области, нам нужны будут хеш-функции. Они используются для формирования строки, которая может характеризовать какой-то объём данных. Для нас важно, что мы можем сгенерировать хеш-код по информации любой длины, а на выходе получить сообщение фиксированной.
Не каждый алгоритм, выполняющий преобразования, подходит для применения. Для хэш-функций имеются определенные правила, которые обеспечивают её высокую безопасность. Из всех свойств, нас интересует только шанс получить последовательно два одинаковых кода – коллизию.
Коллизией хеш-функции называются
два разных набора сообщений a,b после преобразования которых получается одинаковый хеш-код hash(a) = hash(b).
Они всегда будут существовать, в связи с тем, что пространство сообщений бесконечно, а количество выходов ограничено.
Я использовал MD5. Он имеет низкий шанс коллизии у случайно взятых данных и высокую скорость работы. Хеш-функция ставит в соответствие входной строке 128 битную, разделенную на 32 символа в 16-ричном формате. Производим вычисления и получаем, что вероятность совпадения хеш-кодов у двух последовательных разных сообщений равна или .
Применяя эту идею, мы первым делом создаем хеш-коды всех сохранённых расписаний, предварительно отсортированных по дню и времени проведения занятия. Храним их постоянно в памяти телефона. При запуске обновления, генерируем контрольную сумму полученной с сервера информации и сравниваем её с локальной. При расхождении проводим аналогичные действия спускаясь на уровень ниже и применяем уже, например, к парам группы. Заранее собрав их и отсортировав. Таким образом оптимизируется поиск измененных, требующих корректировки, не затрагивая другие.
Заключение
Добавление структур данных и хеш-функций к изначальному алгоритму сравнений ускорило его. Контрольная сумма расписаний даёт нам заметную, но непостоянную оптимизацию в виде формального определителя изменений. Она сыграла роль предварительной проверки. Применение HashMap позволило ускорить поиск, как для доп. расписаний, так и в условиях нашей задачи.
На этом можно считать задачу решенной, а рассказ завершенным. Благодарю за внимание!
Код функции генерации хеша
public static String generateHash(String data) throws NoSuchAlgorithmException {
//объявляем переменную которая будет хранить хеш-код
String hashCode;
// читаем строку как массив байт
byte[] byteArray = data.getBytes();
// выбираем алгоритм шифрования,
// передаем на вход массив байт исходной строки
byte[] byteHash = MessageDigest.getInstance("SHA-256").digest(byteArray);
// полученный хеш - код в виде массива байт,
// переводим в строку длинной 32 знака в шестнадцатеричном формате.
hashCode = HashMethods.bytesToHexString(byteHash);
// возвращаем
return hashCode;
}
ris58h
Так и не понял как из этого следует необходимость мобильного приложения. Простейшее PWA прекрасно подходит.
kislyannikov Автор
И да и нет. Вуз не хотел менять сайт, но на нём было уже реализована верстка для мобильных, но там не было нужных функций. Он уже содержал только расписание учеников, а вот преподов нет. Да и просмотр кабинетов вообще отсутствовал.
Напротив, приложение можно дополнять, расширять его под нужды. В принципе, я это и сделал.