95936

Разработка Android-приложения Velo-city

Дипломная

Информатика, кибернетика и программирование

Изучение способов интеграции с существующими сервисами. Составление технического задания. Выбор библиотек для создания универсальных графических элементов для разных типов мобильных устройств. Создание слоя работы с картами. Перемещение камеры. Отрисовка маршрутов. Удаление маршрутов. Центрирование карты на определенной области.

Русский

2015-10-01

1.73 MB

7 чел.

Федеральное агентство по образованию

Федеральное государственное автономное образовательное учреждение высшего профессионального образования
«Казанский (Приволжский) федеральный университет»

ИНСТИТУТ ВЫЧИСЛИТЕЛЬНОЙ МАТЕМАТИКИ И
ИНФОРМАЦИОННЫХ ТЕХНОЛОГИЙ

КАФЕДРА ТЕОРЕТИЧЕСКОЙ КИБЕРНЕТИКИ

Специальность (направление): 010501.65 Прикладная математика и информатика

Специализация: 01.02.08 Математическая кибернетика

ВЫПУСКНАЯ КВАЛИФИКАЦИОННАЯ РАБОТА

(Дипломная работа)

Разработка Android-приложения Velo-city

Работа завершена:

«__» __________ 2014г.   _______________________ (Д.Ф. Минбаев)

Работа допущена к защите:

Научный руководитель

Старший преподаватель кафедры теоретической кибернетики

«__» __________ 2014г.   _______________________ (Р.М. Хадиев)

Заведующий кафедрой

Доктор физико-математических наук, профессор

«__» __________ 2014г.   _______________________ (Ф. М. Аблаев)

Казань — 2014

Оглавление

Введение. 4

Постановка задачи 6

Ожидаемые результаты 6

Выбор инструментов для выполнения задания 7

Реализация 9

Изучение возможностей мобильных устройств 9

Изучение способов интеграции с существующими сервисами 20

Составление технического задания 23

Выбор библиотек для создания универсальных графических элементов для разных типов мобильных устройств 25

Создание слоя работы с картами 26

Перемещение камеры 26

Отрисовка маршрутов 27

Удаление маршрутов: 28

Центрирование карты на определенной области 29

Отложенная отрисовка маршрутов 29

Реализация расчетов статистики 31

Расчет статистики по группе маршрутов 31

Расчет распределений параметров маршрутов 34

Реализация компонентов приложения 38

Главный экран 38

Список маршрутов 39

Отображение информации по маршруту 40

Статистика 41

Календарь 42

Планировщик 43

Реализация поддержки приложением специфического жизненного цикла графических элементов 44

Хранение состояний 44

Восстановление состояний 46

Тестирование приложения 49

Функциональное тестирование 50

Тестирование производительности 50

Ручное тестирование 52

Полуавтоматизированное тестирование 53

Альфа-тестирование 54

Бета-тестирование 55

Пример работы 56

Активити трекера 56

Активити списка маршрутов 58

Активити маршрута 59

Активити статистики 62

Активити календаря 64

Активити планировщика 65

Литература и источники 66

Заключение 67


Введение

В современном мире велосипед является популярным средством передвижения в городской среде за счет удобства, скорости, возможности совмещения передвижения до пункта назначения с тренировкой. В некоторых странах(например, Нидерландах) велосипед является основным транспортом передвижения в городах. Дороги оборудуются специальными велосипедными дорожками, создаются пункты прокатов, клубы велосипедистов. В России использование велосипедов набирает популярность: в некоторых городах уже выделены полосы для движения велосипедистов, созданы пункты проката.

Перед человеком, решившим кататься на велосипеде, вне зависимости от цели поездок (замена автомобилю или общественному транспорту при перемещении по городу, туристические поездки, тренировки), встает вопрос выбора маршрута.

Альтернативы только две:

1) двигаться по велосипедным дорожкам,

2) использовать какое-либо средство навигации и выбора маршрута, которому предполагается следовать.

Первая является подходящим решением только в случае, если дорожки для движения велосипедистов покрывают значительную часть дорог, а велосипедист хорошо ориентируется в географии региона.

Вторая позволяет отправляться кататься в ранее неизвестные районы, не боясь заблудиться и попасть на неудобную для передвижения дорогу.

Средств осуществления навигации не так уж и много: карты, специальные приборы-навигаторы, мобильные телефоны и планшетные компьютеры с встроенным локатором и специальным программным обеспечением. Идеальным решением для возможности навигации является приложение для мобильного телефона: оно позволит в процессе движения иметь необходимую информацию о текущем положении и о маршруте движения. Кроме того, позволит получать и хранить информацию о ранее пройденных маршрутах, их характеристиках, получать статистическую информацию, что будет полезно людям, следящим за своими результатами тренировок, изучать посещенные районы на карте.

Абстрагирование от конкретного средства передвижения позволит легко расширить функциональные возможности приложения, легко приспособив его для использования для прогулок, пробежек, движении на автомобиле и других видах транспорта. Связав мобильные устройства с общим сервером, можно получить систему, предоставляющую актуальную информацию по возможным маршрутам в разных городах, а также получить доступ к информации через веб-сайт.


Постановка задачи

Постановка задачи: Разработать приложение под ОС Android-систему учета передвижения, классификации маршрутов, сбора статистики, планирования передвижения.

План работы:

  1.  Изучение возможностей мобильных устройств.
  2.  Изучение способов интеграции с существующими сервисами.
  3.  Составление технического задания
  4.  Выбор библиотек для создания универсальных графических компонентов для разных типов мобильных устройств.
  5.  Создание слоя работы с картами.
  6.  Реализация подсчета статистики.
  7.  Реализация компонентов приложения.
  8.  Реализация поддержки приложением специфического жизненного цикла графических элементов.
  9.  Тестирование приложения.

Ожидаемые результаты

Результатом является часть информационной системы, работающая на мобильном устройстве под управлением ОС Android, по учету, планированию, расчету числовых характеристик и формированию статистики движения велосипедистов.

В настоящее время существуют аналоги приложения: Runtastic, Endomondo.


Выбор инструментов для выполнения задания

Android-приложения пишутся на языке Java. В качестве среды разработки была выбрана IntelliJ IDEA со специальным плагином для разработки под Android. IDEA предоставляет богатую среду для разработки на языке Java: контекстно-зависимую справку, подсказки к коду, взаимодействие со средством сборки, визуальный редактор баз данных.

В качестве системы сборки был выбран Gradle. Gradle-система сборки, обладающая возможностью ведения контроля зависимостей, как в Maven, и возможностью описания сценариев как в Ant. Описание правил сборке ведется на диалекте Groovy.

Для удобства разработки было принято решение использовать распределенную систему контроля версий Git. Особенностью Git является возможность создавать локальные репозитории для учета изменений в коде программного продукта, а также удобная система веток, позволяющая изолировать разработку новых возможностей приложения, как от стабильной версии, так и друг от друга.

Для взаимодействия с базой данных был выбран фреймворк ORMLite, позволяющий использовать аннотации для создания сущностей, соответствующих таблицам базы данных. Удобным качеством является то, что он умеет сам создавать таблицы при первом запуске приложения на устройстве, вести версии базы. Специальные классы-дао(data access object) позволяют работать с таблицами как при помощи запросов, так и при помощи методов, реализующих общие операции при работе с сущностями.

Для создания элементов графического пользовательского интерфейса был использован фреймворк HoloEverywhere. Особенность его заключается в том, что он позволяет создавать виджеты, визуально похожие на стандартные виджеты android последних версий, на устройствах версии старше 1.6.

Для формирования и отображения графиков был использован реймворк androidplot, позволяющий отображать различную информацию в виде графиков, настраиваемых с помощью xml файлов. Данные для отображения передаются в упорядоченном виде, добавляются специальные конвертеры данных для отображения в подписях к графикам, остальную работу делает фреймворк.

Использованные библиотеки:

  1.  com.android.support:support-v4:19
  2.  com.google.android.gms:play-services:4.3
  3.  com.j256.ormlite:ormlite-core:4.47
  4.  com.j256.ormlite:ormlite-android:4.47
  5.  org.holoeverywhere:library:2.1
  6.  org.holoeverywhere:addon-preferences:2.1
  7.  com.nineoldandroids:library:2.4
  8.  com.androidplot-core-0.6.0


Реализация

Реализация проекта выполнялась согласно плану, расписанному в пункте «Постановка задачи. План. Ожидаемые результаты».

Изучение возможностей мобильных устройств

Операционная система Android имеет три уровня:

  1.  Основной-урезанная и модифицированная версия Linux
  2.  Уровень инфраструктуры приложения, содержащий виртуальную машину Dalvik, веб-браузер, базу данных SQLite, некоторые специализированные фреймворки и Java API.
  3.  Уровень написанных в Google Android-приложений, являющийся логическим продолжением уровня архитектуры, большинство из них могут использоваться в приложениях сторонних разработчиков.

Рассмотрим уровни подробнее:

  1.  Уровень Linux

Исторически так сложилось, что разрабатывать какой-то новый продукт «с нуля» гораздо более дорогой и трудоемкий процесс, чем использование уже работающих и отлаженных механизмов . Android Inc приняла решение использовать Linux как основу для Android. Исходники Linux, находятся в свободном доступе и предоставляют собой хорошую основу для любых разработок, даже такие гиганты мира высоких технологий как Google и Apple часто используют в качестве основы своих разработок open-source проекты. Во времена создания первой версии устройства имели слишком мало оперативной и энергонезависимой памяти. Процессоры были медленнее по сравнению с процессорами компьютеров, где обычно используется Linux, из-за этого разработчикам Android потребовалось минимизировать системные требования Linux.

Linux на высоком уровне- это комбинация ядра операционной системы и множества других, необязательных частей. Так, разрабочткики Google вынуждены в любом случае использовать ядро Linux как часть ОС Android. Кроме того, были рассмотрены необязательные части и из них выбрано самое необходимое: сетевой фаервол IPTables и оболочка Ash и д.р.

Разработчики Android модифицировали ядро Linux, была добавлена поддержка специфичного аппаратного обеспечения, используемого на мобильных устройствах. Выбор Linux в качестве основы оказал огромное влияние на все аспекты ОС Android. Сборка Android, по сути, есть вариация процесса сборки Linux. Google настоятельно не рекомендует разрабатывать нативные приложения. Технически, конечно, это возможно, но в дальнейшем у вас не будет возможности распространять это приложение нормальным способом.

2)Уровень инфраструктуры

Android-приложения написаны на Java. По мнению некоторых аналитиков, основная причина выбора языка Java состоит в необходимости одному и тому же приложению работать на различном аппаратном обеспечении. Эта проблема имеет место для ОС Android: Google не контролирует производителей аппаратных средств. Например, ОС Android работает на процессорах с архитектурой x86, ARM, PPC и MIPS). На бинарном уровне эти архитектуры несовместимы.

Перед архитекторами ОС Android стоял выбор: распространять несколько версий одного и того же приложения одновременно либо реализовать независимость приложений от архитектуры устройства.

Для того, чтобы одно и то же приложение могло работать на разном аппаратном обеспечении, компания Google использовала контейнер-ориентированную архитектуру (container-based architecture). В такой архитектуре двоичный код выполняется программным контейнером и изолируется от деталей конкретного аппаратного обеспечения. Примеры всем знакомы — Java и C#. В обоих языках двоичный код не зависит от специфики аппаратного обеспечения и выполняется виртуальной машиной.

Существует и другой способ достигнуть независимости от аппаратного обеспечения на уровне двоичного кода. Как один из вариантов, можно использовать эмулятор аппаратного обеспечения, так же известный как QEMU. Он позволяет эмулировать, например, устройство с процессором ARM на платформе x86 и так далее. Google могла бы использовать C++ как язык для разработки приложений внутри эмуляторов. Действительно, Google использует такой подход в своих эмуляторах Android, которые построены на основе QEMU.

То, что они не пошли по такому пути, привело к тому, что удалось обойтись без эмуляторов ОС, где это возможно. Эмуляторы требуют значительных вычислительных ресурсов. Для достижения наилучшего быстродействия эмуляция была оставлена только там, где этого нельзя было избежать, в нашем случае — в Android-приложениях.

Как бы то ни было, компания Google пришла к решению использовать Java как основной язык разработки приложений и среды их выполнения.

Выбрав Java, нужно было решить, какую виртуальную машину (JVM) использовать. Ввиду ограниченности ресурсов использование стандартной JVM было затруднено. Единственным возможным выбором было использование Java ME JVM, разработанной для мобильных устройств. Однако инженерами  Google было принято решение разработать собственную виртуальную машину-Dalvik VM.

Dalvik VM отличается от других виртуальных Java-машин следующим:

Она использует специальный формат DEX для хранения двоичных кодов, в противовес форматам JAR и Pack200, которые являются стандартом для других виртуальных Java-машинах. Компания Google заявила, что бинарники DEX меньше, чем JAR.

  1.  Dalvik VM оптимизирована для выполнения нескольких процессов одновременно.
  2.  Dalvik VM использует архитектуру, основанную на регистрах против стековой архитектуры в других JVM, что приводит к увеличению скорости выполнения и уменьшению размеров бинарников.
  3.  Она использует собственный набор инструкций (а не стандартный байткод JVM)
  4.  Возможен запуск (если необходимо) нескольких независимых Android-приложений в одном процессе
  5.  Выполнение приложения может охватывать несколько процессов Dalvik VM «естественным образом» (позже мы обсудим, что это значит). Для поддержи этого добавлено:
  6.  Специальный механизм сериализации объектов, основанный на классах Parcel и Parcelable. Функционально преследуются те же цели, что и Java Serializable, но в результате данные имеют меньший объём и потенциально более терпимы к версионным изменениям классов.
  7.  Особый способ для выполнения вызовов между процессами (inter process calls, IPC), основный на Android Interface Definition Language (AIDL).
  8.  До Android 2.2 Dalvik VM не поддерживала JIT-компиляцию, что было серьёзным ударом по производительности. Начиная с версии 2.2, скорость выполнения часто используемых приложений значительно возрасла.

Были пересмотрены стандартные пакеты Java JDK API: удалены некоторые из них (например всё, что касалось Swing) и добавили некоторое количество собственных — их имя начинается с «android».

3) Уровень приложений

Является расширением уровня инфраструктуры, редставляет собой фреймворки, инструменты, приложения, которые могут быть использованы сторонними разработчиками. На данный момент многие сторонние приложения, не говоря уже о продуктах, выпущенных компанией Google, предоставляют разработчикам возможности для взаимодействия и использование некоторых элементов приложений в других приложениях: Многие сервисы обмена сообщениями умеют обращаться к контактам с помощью адресной книги телефона, приложения социальных сетей дополняют информацию на устройстве о контактах, работают с картой памяти и другими физическими устройствами.

Возможность организовывать взаимодействие между приложениями, предоставляя для этого определенные механизмы, сыграла важную роль в развитии разработки под ОС Android. Появление встраивания и переноса информации между приложениями стали возможны благодаря этому.

Архитектура Android:

Архитектура Android является фреймворк-ориентированной (framework-based), в противовес вольной (free-style) архитектуре. Free-style приложения, написанные на Java, начинаются с класса, имеющего метод public static void main(String args[]), и разрабатываются на основании предпочтений разработчиков и требований задачи. Фреймворк-ориентированные приложения основываются на существующем фреймворке. Их разработка сводится к расширению неких классов или реализации интерфейсов, предоставленных фрейморком. Такое приложение не может быть запущено вне фреймворка или без него. Фреймворк-ориентированная архитектура ограничивает свободу разработчиков, предписывая им что и как следует делать. Но, в итоге, исчезают повторяющиеся участки кода (boilerplate code), разработчики реализуют лишь часть компонентов и встраивают их в платформу.

Для Java существует множество фреймворков. Тем не менее, команда Android решила создать свой собственный. Возможно, одной из причин, по которой они так поступили, была необходимость поддерживать уникальную систему управления памятью.

В «обычной» Java объекты находятся в памяти до тех пор, пока сборщик мусора не доберётся до них. Происходит это только тогда, когда на объект нет ни одной ссылки от «живых» объектов. В Android это не так. Если некий интерфейс пользователя скрывается (то есть он более не видим на экране), то нет никакой гарантии, что он находится в памяти, даже если приложение в дальнейшем собирается использовать его. Если у ОС Android есть достаточно свободной памяти, эти объекты могут храниться в ней, но сборщик мусора может уничтожить их в любой момент, когда ОС решит, что памяти осталось слишком мало. То же верно и для процессов. Процесс, который в данный момент времени не показывает пользователю никакого графического интерфейса, может быть уничтожен ОС Android.

Фреймворк Android решает возникающую при этом проблему хранения состояний у «спрятанных» компонентов: для каждого состояния жизненного цикла существуют методы, каждый из которых может быть переопределён разработчиком. Эти методы вызываются фреймворком в заранее определённые ключевые моменты, например, когда пользовательский интерфейс показывается на экране, прячется и т.п. В этих методах разработчик может реализовать логику для хранения и восстановления состояния объектов. Подобная обработка скрытия пользовательских интерфейсов и существование кнопки «назад» на Android-устройствах приводит к необходимости наличия стека пользовательских интерфейсов, в котором текущий видимый интерфейс помещается на вершину, а все остальные сдвигаются вниз (стековая операция «push»). Нажатие «назад» удаляет интерфейс с вершины стека и показывает элемент, который был за ним (стековая операция «pop»). Подобный стек существует в Android. В документации он называется «activity stack». В плане обработки взаимодействия между пользовательским интерфейсом и его логикой Android следует архитектурному MVVM, являющемуся лучшим решением для клиентских приложению.

Архитектура MVVM была создана с целью разделения труда дизайнера и программиста:

  1.  Разработка пользовательского интерфейса совершается дизайнером интерфейсов с помощью технологии, более или менее естественной для такой работы (XML)
  2.  Логика пользовательского интерфейса реализуется разработчиком как компонент ViewModel
  3.  Функциональные связи между пользовательским интерфейсом и ViewModel реализуются через биндинги (bindings), которые, по сути, являются правилами типа «если кнопка A была нажата, должен быть вызван метод onButtonAClick() из ViewModel». Биндинги могут быть написаны в коде или определены декларативным путём (Android использует оба типа).

Структура приложения:

В общем случае, Android-приложение состоит из:

  1.  Java-классов, являющихся подклассами основных классов из Android SDK и Java-классов, у которых нет родителей в Android SDK.
  2.  Манифеста
  3.  Ресурсов наподобие строк, изображений и т.п.
  4.  Файлов

Java-классы

View— базовый класс для всех виджетов пользовательского интерфейса. Интерфейс Android-приложения представляет собой дерево экземпляров наследников этого класса. Можно создать это дерево программно, но это неправильно. Пользовательский интерфейс определяется с помощью XML (файлы слоёв, layout files), а во время исполнения автоматически превращается (inflate, термин Android) в дерево соответствующих объектов.

КлассActivity и его подклассы содержат логику, лежащую за пользовательским интерфейсом. При ближайшем рассмотрении этот класс соответствует ViewModel в архитектурном шаблоне Model-View-ViewModel(MVVM). Отношение между подклассом Activity и пользовательским интерфейсом — это отношение один к одному; обычно каждый подкласс Activity имеет только один связанный с ним слой пользовательского интерфейса, и наоборот. Activity имеет жизненный цикл.

В течении жизненного цикла Activity может находиться в одном из трёх состояний:

  1.  Активно и выполняется — этот пользовательский интерфейс находится на переднем плане (говоря технически — на вершине стека активити)
  2.  Приостановлено — если данный интерфейс пользователя потерял фокус, но всё ещё видим. В таком состоянии никакой код не выполняется.
  3.  Завершено — если интерфейс пользователя невидим. В таком состоянии код не выполняется.

Код активити выполняется только когда соответствующий интерфейс пользователя видим и имеет фокус. Нет гарантии, что объект Activity и связанные с ним объекты находятся в памяти, когда активити приостановлено или завершено.

Класс Service и его подклассы реализуют механизм долгого непрерывного выполнения какого-либо действия. Особенностью является то, что сервисы не привязаны к активити, следовательно их работа не будет приостановлена, когда активити сменится или будет скрыто. Service может быть инстанцирован и запущен Android-приложением по некому условию.

Класс BroadcastReceiver и его подклассы представляют собой «подписчика» в механизме взаимодейтсвия издатель/подписчик, реализованном в архитектуре Android.

Разработчик под Android не ограничен одним только расширением классов из Android SDK. Он может писать собственные классы так, как захочет. Но все они будут только хелперами («helper classes») для классов из Andoird SDK.

Android Manifest

Манифест Android—важная часть Android-приложения. Вот как их описывает Google:

  1.  Определяет имя Java-пакета приложения. Имя пакета представляет собой уникальный идентификатор для приложения.
  2.  Описывает компоненты приложения — активити, сервисы, броадкаст-ресиверы и контент-провайдеры. Определяет имена классов, реализующие каждый из компонентов и оглашает их возможности (например, какие Intent-сообщения они могут обрабатывать). Эти объявления позволяют системе Android знать, какие компоненты и при каких условиях могут быть запущены.
  3.  Предопределяет процессы, которые будут содержать компоненты приложения.
  4.  Объявляет разрешения, которые приложение должно иметь для доступа к защищённым частям API и взаимодействия с другими приложениями.
  5.  Также объявляет разрешения, которые требуются для доступа к компонентам приложения.
  6.  Перечисляет классы Instrumentation, которые предоставляют профайлинг и другую информацию во время работы приложения. Эти объявления присутствуют в манифесте только пока приложение разрабатывается и тестируется; они удаляются перед публикацией приложения.
  7.  Объявляет минимальный уровень Android API, который требует приложение.
  8.  Перечисляет библиотеки, с которыми приложение должно быть связано.

Ресурсы

Android-приложения используют следующие типы ресурсов:

  1.  Изображения
  2.  Слои GUI (XML файлы)
  3.  Объявления меню (XML файлы)
  4.  Текстовые строки

Как правило, в Java ресурсы идентифицируются строками. Такие строки могут содержать, например, путь и имя файла, содержащего изображение, или ID данной строки и т.п. Проблема в том, что ошибки в таких ссылках не могут быть обнаружены в процессе трансляции кода.

При сборке Android-приложения генерируется специальный Java-класс с именем R. Этот класс содержит несколько static final наборов данных. Каждый такой набор данных — ссылка на отдельный ресурс. Эти ссылки используются в коде приложения для связи с ресурсами. Теперь каждая ошибка в ссылке на ресурсы проявляет себя в процессе компиляции.

Файлы

Android-приложение использует несколько разных типов файлов:

  1.  Файлы «общего назначения»
  2.  Файлы БД
  3.  Файлы Opaque Binary Blob (OBB) (они представляют собой зашифрованную файловую систему, которая может быть монтирована для приложения)
  4.  Закешированные файлы

Хотя, в конечно итоге, все они только файлы Linux, имеет смылсл рассматривать их как отдельные типы файлов только в разрезе обработки их различным API и отдельного хранения. Также они отделены от файлов, хранящихся во внутреннем или внешнем хранилище (последнее может отсутствовать или исчезнуть/появиться в любой момент).

API для работы с файлами реализован классом Context, от которого порождены классы Activity и Service.

Необходимые возможности

Рассматривались устройства с операционной системой Android версии 2.3 и выше. Необходимыми условиями для разработки  приложения для выбранного типа устройств являются наличие на них GPS, возможности отображения карт и взаимодействия с ними приложения.

GPS присутствует на большинстве современных телефонов и на многих планшетных компьютерах, Google предоставляет api для получения показаний датчика, настройки отклика датчика, классы для удобной работы с получаемыми данными: android.location.*.

Существуют несколько видов карт, используемых на мобильных устройствах под управлением ОС Android: google maps, yandex карты. и д.р. Современные карты- это не просто картинки с названиями локаций и обозначением важных объектов. Они представляют собой сложную многослойную структуру, самостоятельно определяющую необходимость отображения тех или иных элементов в зависимости от приближения. Данные хранятся на внешних серверах, при отображении локации она синхронизируется с данными, хранящимися в системе, кешируется, если пользователь воспользовался приложением google maps на своем телефоне, а затем соединение с интернетом было потеряно, карта на ранее заргуженном участке сохранится со всеми подробностями, близлежащие локации также будут подгружены, правда в худшем качестве. Такая система приоритетов в значительной мере оптимизирует процесс передачи и отображения данных. Карта содержит разные слои, каждый из которых отображает локации в каком-то особом виде: спутниковые снимки, схемы и т.д. С каждым годом карты становятся все совершеннее, сейчас присутствует возможность перемещаться по «объемной» карте в некоторых регионах. Помимо отображения географических объектов карты предоставляют устройству возможность прокладывать маршруты, использовать показания датчиков(gps, акселерометра) для более удобного взаимодействия, облегченного поиска. API карт развивается стремительными темпами, многие возможности карт доступны сторонним разработчикам в их приложениях. Google maps позволяют подключать их к сторонним приложениям, отображать на них как стандартизированные объекты(зависят от карты), так и создавать собственные объекты для отображения.


Изучение способов интеграции с существующими сервисами

Для использования приложением какого-либо из существующих сервисов, необходимо в файле настроек приписать AndroidManifest.xml прописать разрешение на их использование, этот файл используется при запросе разрешения на использование приложением сервисов при установке через сервис google.play.

   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

   <uses-permission android:name="ru.velocity.permission.MAPS_RECEIVE"/>

   <uses-permission android:name="android.permission.INTERNET"/>

   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

   <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>

   <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

   <uses-permission android:name="android.permission.GET_ACCOUNTS"/>

   <uses-permission android:name="android.permission.USE_CREDENTIALS"/>

   <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>

Для использования карты необходимо отнаследовать класс от от класса com.google.android.gms.maps.SupportMapFragment. Метод getMap() возвратит карту, с которой можно работать.

Для ее настройки необходимо обратиться к графическим настройкам карты и передать необходимые параметры для компонентов карты. Ниже приведен один из возможных вариантов настройки:

       map.getUiSettings().setCompassEnabled(true);

       map.getUiSettings().setMyLocationButtonEnabled(true);

       map.getUiSettings().setRotateGesturesEnabled(true);

       map.setMyLocationEnabled(true);

Для нанесения на карту простой линии необходимо создать экземпляр класса PolylineOptions-«настроек линии», установить ширину линии и цвет. Затем можно добавить линию на карту:

       PolylineOptions options = new PolylineOptions().width(5). color(aColor).geodesic(true);

       Polyline polyline = getMap().addPolyline(options);

Для добавления точек к линии можно воспользоваться методом public void setPoints(java.util.List<com.google.android.gms.maps.model.LatLng> points).

Для работы с базой данных необходимо создать класс-сущность, экземпляры которого будут храниться в одной или нескольких таблицах. Класс должен имплементить интерфейс java.io.Serializable, а поля должны быть помечены аннотациями. Ниже приведен пример класса-сущности: public class AchievementInfo implements Serializable {

   @DatabaseField(generatedId = true)

   private int id;

   @DatabaseField(canBeNull = false)

   private int level;

   public AchievementInfo() {

   }

   public int getId() {

       return id;

   }

   public void setId(int id) {

       this.id = id;

   }

   public int getLevel() {

       return level;

   }

   public void setLevel(int level) {

       this.level = level;

   }

}

Для взаимодействия с базой данных необходимо создать класс-помощник, отнаследованный от com.j256.ormlite.android.apptools. OrmLiteSqliteOpenHelper, переопределив методы  public void onCreate(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource) и public void onUpgrade(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource, int oldVersion, int newVersion) получим возможность создать базу с нуля и обновлять ее структуру и содержание соответственно. Также необходимо создать объекты предоставления доступа к данным, они делятся на два вида: обычный, кидающий при вызове его методов java.sql. SQLException, и кидающий java.lang.RuntimeException, что иногда бывает удобно и позволяет сократить код. Оба эти объекта будут полями класса-помощника и будут получаться через его методы,как показано ниже:

private Dao<AchievementInfo, Integer> achievementInfoDa;

private RuntimeExceptionDao<AchievementInfo, Integer> simpleAchievementInfoDao;

   public Dao<AchievementInfo, Integer> getAchievementInfoDao() throws SQLException {

       if (achievementInfoDao == null) {

           achievementInfoDao = getDao(AchievementInfo.class);

       }

       return achievementInfoDao;

   }

   public RuntimeExceptionDao<AchievementInfo, Integer> getSimpleAchievementInfoDao() {

       if (simpleAchievementInfoDao == null) {

           simpleAchievementInfoDao = getRuntimeExceptionDao(AchievementInfo.class);

       }

       return simpleAchievementInfoDao;

   }


Составление технического задания

  1.  Поддержка обратной совместимости с ранними версиями.
  2.  Разработка механизма поддержки широкого спектра версий ОС Android
  3.  Единый стиль элементов пользовательского интерфейса
  4.  Перенос возможностей последних версий на всю линейку поддерживаемых устройств(с версии 2.3)
  5.  Разработка универсального механизма хранения состояний и данных.
  6.  Поддержка специфического для ОС жизненного цикла компонентов.
  7.  Реализация возможности восстановления состояний устройства
  8.  Унификация работы с картой в рамках приложения.
  9.  Выделение возможностей карты, позволяющих отображать положение пользователя, положение планируемых маршрутов, записываемого маршрута, информации о пройденных маршрутах.
  10.  Создание слоя взаимодействия с картами, позволяющего отрисовывать на карте данные в формате приложения.
  11.  Компонент ведения(записи) маршрута.
  12.  Компонент должен обеспечить отображение и своевременное обновление информации по маршруту на экране записи.(пройденного времени, расстояния, мгновенной скорости)
  13.  Компонент должен обеспечить отображение на карте линии движения, дорисовывать ее при изменении положения объекта в пространстве, дополнять при активации приложения из «спящего» режима мобильного устройства или при восстановлении приложения из свернутого состояния.
  14.  Работа с пройденными маршрутами.
  15.  Компонент должен обеспечить хранение данных о пройденных маршрутах, отображение списка пройденных маршрутов, взаимодействие списка с формами отображения маршрута и его характеристик.
  16.  Работа с системой классификации маршрутов. Компонент должен обеспечить возможность группировать маршруты по типу(основанному на средстве передвижения).
  17.  Реализация отображения на карте дополнительной информации о маршрутах. Компонент должен обеспечить отображение на карте информацию по скорости, либо по высоте. Переключение режима должно осуществляться через меню.
  18.  Динамика изменения высоты и скорости движения должна отображаться на графиках, выводимых при просмотре подробной информации о маршруте.
  19.  Статистика.
  20.  Разработка гибкой системы подсчета статистики. Формирование результата, в формате, удобном для отображения как на графиках, так и в диаграммах.
  21.  Реализация отдельных графических компонентов отображения статистических данных.
  22.  Применение диаграмм для отображения количественных значений характеристик по временным периодам.
  23.  Календарь.
  24.  Реализация возможности отображения данных о маршрутах на календаре.
  25.  Создание механизма связующего календарь с статистикой прошедших маршрутов и настройкой будущих
  26.  Планирование маршрутов.
  27.  Создание универсального компонента, позволяющего выбирать маршруты как для конкретного дня, так и для наперед заданной даты.
  28.  Включение возможности вызова планировщика с активити трекера, с помощью пункта меню.
  29.  Система достижений.
  30.   Создание счетчиков достижений, отслеживающих и обновляющих результаты пользователя.
  31.  Отображение информации о достижениях в отдельном компоненте, разделение достижений на «достигнутые» и «предстоящие».

Выбор библиотек для создания универсальных графических элементов для разных типов мобильных устройств

Современные устройства, под управление ОС Android, чаще всего используют цветовую схему Android Holo, однако эта тема не столько иное цветовое решение, сколько новый подход к управлению мобильным устройством, использующий появившиеся со времен Android 3.0 технологии. Использовать эту тему в ее оригинальном виде удалось бы далеко не на всех устройствах( в полном объеме лишь с 4.0), пришлось выбирать между альтернативами: реализовывать с помощью имеющихся стандартных библиотек, либо найти способ портировать Holo на более ранние версии. Удалось найти библиотеку HoloEverywhere, которая позволяет использовать большинство возможностей тему на устройствах, начиная с версии 1.6. Решено было применить именно ее.

В HoloEverywhere реализованы следующие возможности:

  1.  Стилизованы многие виджеты, такие как Button, ToggleButton, RadioButton, CheckBox, Spinner, (Multi)AutoCompleteTextView, RatingBar, ProgressBar, SeekBar и другие производные. Так-же есть и портированные: Switch, NumberPicker, CalendarView, DatePicker, TimePicker
  2.  Портирован AlertDialog, ProgressDialog, стилизован обычный Dialog (к сожалению, внутренности AlertDialog слишком закрыты для простого портирования темы, пришлось тянуть весь его код и создавать несовместимую со стандартным AlertDialog версию)
  3.  Портирован весь Preference Framework, создан SupportSharedPreferences, который умеет писать различные Set и JSON объекты/массивы
  4.  Помощник для переключения тем для Activity
  5.  Везде используется свой LayoutInflater, который может инфлейтить ваши вью по короткому имени. если на старте приложения вызвать правильный метод с правильными параметрами. Допускается писать просто MySuperView, и это будет работать везде, где используется HE (в рамках текущего приложения, естественно).
    Из этого так-же следует, что возможно подменить системные виджеты на свои, абсолютно не меняя разметок, в том числе и системных. Но если виджеты создаются из кода — тут уж ничего не поделаешь.
  6.  Дополненная реализация ListView, позволяет стартовать ActionMode из ABS

Создание слоя работы с картами

Основные операции с картой:

  1.  Перемещение камеры.
  2.  Отрисовка маршрутов.
  3.  Удаление маршрутов на карте.
  4.  Центрирование карты на определенной области.

В приложении было реализовано несколько фрагментов, содержащих карту. Они были использованы в разных компонентах и различались лишь логикой отрисовки маршрута(-ов), что позволило не переполнять классы логикой отрисовки и излишними проверками, параметрами, сделав их максимально простыми, но при этом сократить их общее количество.

Перемещение камеры

Для перемещения камеры достаточно сформировать позицию , создать на ее основе «обновление камеры» и вызвать метод карты,перемещающий цент карты в нужное положение:

CameraPosition cameraPosition  CameraPosition.fromLatLngZoom(new LatLng(savedLatitude, savedLongitude), savedZoom);

 CameraUpdate cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition);

       map.moveCamera(cameraUpdate);

Для «запоминания» положения камеры для дальнейшего восстановления необходимо добавить обработку смены положения камеры :

      map.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {

           @Override

           public void onCameraChange(CameraPosition cameraPosition) {

               final double latitude = cameraPosition.target.latitude;

               final double longitude = cameraPosition.target.longitude;

               final float zoom = cameraPosition.zoom;

               final FragmentActivity activity = getActivity();

               if (activity != null) {

                   activity.getPreferences(Context.MODE_PRIVATE)

                           .edit()

                           .putFloat("savedLatitude", (float) latitude)

                           .putFloat("savedLongitude", (float) longitude)

                           .putFloat("savedZoom", zoom)

                           .apply();}

           }

       });

Отрисовка маршрутов

Маршрут представляет из себя одну ломаную линию, либо несколько ломаных(когда необходимо с помощью раскраски линии передать какую-либо информацию). Принцип формирования параметров при этом не различается(необходимо знать цвет, ширину линии, координаты точек), но способ хранения будет немного различаться, коллекция разноцветных «кусков» против карты, содержащей независимые маршруты и ключ обращения к ним(идентификатор маршрута из базы). Разделение логики отрисовки с данными задачи, позволило реализовать внешние формы так, что они работают только с классами модели. Это позволит, при надобности, легко перейти на другой тип карт, не формировать маршруты карты во множестве компонентов программы, оставив эту задачу фрагменту с картой.

Пример отрисовки маршрута:

   private void addRouteToMap(RouteEntity aRoute, int aColor) {

       removeRoute(aRoute.getId());

       PolylineOptions options = new PolylineOptions().width(5).color(aColor).geodesic(true);

       Polyline polyline = getMap().addPolyline(options);

       final List<LatLng> coordinates = getRoutePoints(aRoute);

       polyline.setPoints(coordinates);

       routesMap.put(aRoute.getId(), polyline);

   }

   private List<LatLng> getRoutePoints(RouteEntity aRoute) {

       final List<LatLng> coordinates = new ArrayList<LatLng>();

       final List<PointEntity> points = new ArrayList<PointEntity>(aRoute.getPoints());

       for (PointEntity pointEntity : points) {

           coordinates.add(new LatLng(pointEntity.getLatitude(), pointEntity.getLongitude()));

       }

       return coordinates;

   }

Удаление маршрутов:

Удаление используется в случае, когда данные по маршруту меняются, при этом переход в другой компонент не происходит. Такая ситуация встречается в планировщике, когда меняется раскраска маршрута или их состав. Так как фрагмент с картой работает с представлениями, используемыми на карте, для удаления достаточно найти в карте линий нужный маршрут по его идентификатору:

    Polyline polyline = routesMap.get(aId);

       if (polyline != null) {

           polyline.remove();

       }

Центрирование карты на определенной области

Центрирование на области(используются границы маршрута). Для этого необходимо получить точки области, LatLngBounds.Builder позволяет сформировать необходимую область по точкам, передав в него соответствующие выбранному маршруту точки, получаем область и переводим камеру в нужное положение.

  public void focusOnRoute(int aRouteId) {

       Polyline polyline = routesMap.get(aRouteId);

       if (polyline != null) {

           LatLngBounds.Builder boundsBuilder = new LatLngBounds.Builder();

           for (LatLng point :

                   polyline.getPoints()) {

               boundsBuilder.include(point);

           }

           LatLngBounds bounds = boundsBuilder.build();

           try {

               getMap().animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 25));

           } catch (IllegalStateException ise) {

           }

       }

   }

Отложенная отрисовка маршрутов

При связи фрагмента с картой с таблицей маршрутов возникает необходимость «откладывать» открисовку маршрутов, при восстановлении выбранных элементов таблицы(маршруты пытаются рисоваться, а фрагмент с картой еще не готов). Для разрешения этой ситуации, маршруты запоминаются в фрагменте с картой, если вызывается метод отрисовки, а карте еще не готова, затем в момент, когда карта сформирована, на основе списка запомненных маршрутов формируются маршруты на карте, затем список очищается:

   public void addSelectedPolyline(RouteEntity aRoute) {

       if (getMap() != null) {

           addRouteToMap(aRoute, Color.RED);

       } else {

           selectedRoutesCache.add(aRoute);

       }

   }

   public void addUnSelectedPolyline(RouteEntity aRoute) {

       if (getMap() != null) {

           addRouteToMap(aRoute, Color.BLUE);

       } else {

           unselectedRoutesCache.add(aRoute);

       }

   }

   private void drawFromCache() {

       for (RouteEntity selected : selectedRoutesCache) {

           addSelectedPolyline(selected);

       }

       for (RouteEntity unselected : unselectedRoutesCache) {

           addUnSelectedPolyline(unselected);

       }

       if (getMap() != null) {

           selectedRoutesCache.clear();

           unselectedRoutesCache.clear();

       }

   }

   @Override

   public View onCreateView(android.view.LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

  final View view = super.onCreateView(inflater, container, savedInstanceState);

     

// создание

       drawFromCache();

       return view;

   }


Реализация расчетов статистики

Расчет статистики по группе маршрутов

Основной логической единицей представления данных является маршрут. Характеристики маршрута формируются в процессе записи(расстояние, время, географическое положение), некоторые могут задаваться позже или редактироваться(название, тип). Маршруты состоят из точек, представляя из себя набор данных о положении в пространстве( широта, долгота, высота над уровнем моря), времени, скорости и ускорении, точка дает полезную информацию только в совокупности с другими такими же точками. Их упорядоченное множество и составляет маршрут.

При выборке данных за разные маршруты(это может быть группа маршрутов за определенный период или выбранных в зависимости от их типа) необходимо произвести пересчет данных(общее время расстояние, средние и максимальные значения скорости, продолжительности и пройденного расстояния за один маршрут) и перевести в формат, удобный для отображения. Задача калькулятора - считать, формирование данных для расчета задача обращающегося к расчету компонента. Сделав максимально простой метод расчета, удалось удачно разделить непосредственный расчет и формирование данных(в калькулятор передаются сами маршруты, никаких обращений к базе или внешним данным):

    public static StatisticsCalculationResult calculateStatistics(List<RouteEntity> routes) {

       StatisticsCalculationResult calculationResult = new StatisticsCalculationResult();

       if (routes.size() > 0) {

           double distance = 0d;

           long time = 0L;

           long maxTime = 0L;

           double maxSpeed = 0d;

           double maxDistance = 0d;

           for (RouteEntity entity : routes) {

               final double routeDistance = entity.getDistance();

               final long routeTime = entity.getDateFinish().getTime() - entity.getDateStart().getTime();

               final double routeSpeed = entity.getMaxSpeed();

               distance += routeDistance;

               time += routeTime;

               if (routeDistance > maxDistance) {

                   maxDistance = routeDistance;

               }

               if (routeTime > maxTime) {

                   maxTime = routeTime;

               }

               if (routeSpeed > maxSpeed) {

                   maxSpeed = routeSpeed;

               }

           }

           calculationResult.setRoutesAmount(routes.size());

           calculationResult.setTotalDistance(distance);

           calculationResult.setAverageDistance(distance / routes.size());

           calculationResult.setTotalTime(time);

           calculationResult.setAverageTime(time / routes.size());

           //m/sec

           calculationResult.setAverageSpeed(distance * MILLIS_IN_SECOND / time);

           calculationResult.setMaxDistance(maxDistance);

           calculationResult.setMaxTime(maxTime);

           calculationResult.setMaxSpeed(maxSpeed);

       }

       return calculationResult;

   }

Пример подготовки входных данных для расчета, передачи их в калькулятор, на примере класса RoutesStatisticFragment:

   private StatisticsCalculationResult prepareResult(List<RouteEntity> routes) {

       StatisticsCalculationResult result = StatisticsCalculator.calculateStatistics(routes);

       return result;

   }

       List<RouteEntity> routes = loadRoutes(position);

       StatisticsCalculationResult result = prepareResult(routes);

private List<RouteEntity> loadRoutes(int position) {

       List<RouteEntity> routes;

       final Bundle arguments = getArguments();

       final ArrayList<Date> dates = arguments == null ? null : (ArrayList<Date>) arguments.getSerializable("dates");

       try {

           if (dates != null) {

               //грузим по датам иначе берем все

               routes = new ArrayList<>();

               for (Date date : dates) {

                   Calendar begin = Calendar.getInstance();

                   begin.setTime(date);

                   begin.set(Calendar.HOUR, 0);

                   begin.set(Calendar.MINUTE, 0);

                   begin.set(Calendar.SECOND, 0);

                   //

                   Calendar end = Calendar.getInstance();

                   end.setTime(date);

                   end.set(Calendar.HOUR, 23);

                   end.set(Calendar.MINUTE, 59);

                   end.set(Calendar.SECOND, 59);

                   Where<RouteEntity, Integer> where = velocityDatabaseHelper.getSimpleRouteEntityDao().queryBuilder().where()

                           .ge("dateStart", begin.getTime()).and().le("dateStart", end.getTime())

                           .and()

                           .ge("dateFinish", begin.getTime()).and().le("dateFinish", end.getTime());

                   if (position != 0) {

                       where = where

                               .and()

                               .eq("routeType", RouteType.values()[position - 1]);

                   }

                   final List<RouteEntity> routesByDay = where.query();

                   routes.addAll(routesByDay);

               }

           } else {

               Where<RouteEntity, Integer> where = velocityDatabaseHelper

                       .getSimpleRouteEntityDao()

                       .queryBuilder()

                       .where();

               where = where

                       .eq("template", false);

               if (position != 0) {

                   where = where

                           .and()

                           .eq("routeType", RouteType.values()[position - 1]);

               }

               routes = where

                       .query();

           }

       } catch (SQLException sqlEx) {

           throw new RuntimeException(sqlEx.getMessage(), sqlEx);

       }

       return routes;

   }

Как видно в примере, громоздкий запрос с кучей условий остался в классе, в котором он необходим(не только для статистики), а калькулятор сделал свою работу, не зная лишних подробностей.

Расчет распределений параметров маршрутов

Для отображения суммарных значений показателей по группам маршрутов использовался GraphsDataCalculator, он имеет более сложную структуру, чем калькулятор статистики, за счет того, что работает с базой. Расчет суммы показателя(продолжительности, расстояния) по группе простая задача, но формирование множества маршрутов самой группы представляет сложность, а учтя, что показатели имеют значение только в привязке с группой, калькулятор был настроен на работу с временной группой. Выдаваемый результат представляет объект, созданный для дальнейшего удобного представления в графиках:

public class GraphsDataResult {

   private List<Long> dates = new ArrayList<>();

   private List<Double> distance = new ArrayList<>();

   private List<Long> duration = new ArrayList<>();

   public List<Long> getDates() {

       return dates;

   }

   public List<Double> getDistance() {

       return distance;

   }

   public List<Long> getDuration() {

       return duration;

   }

}

Формирование множества маршрутов группы представляет собой формирование специфического запроса. Даты необходимо формировать в правильном формате- если временной промежуток не будет расширен(включение границ), могут быть потеряны некоторые маршруты:

       GraphsDataResult result = new GraphsDataResult();

       final VelocityDatabaseHelper helper = OpenHelperManager.getHelper(context, VelocityDatabaseHelper.class);

       try {

           List<RouteEntity> filteredRoutes = new ArrayList<>();

           Calendar begin = Calendar.getInstance();

           begin.setTime(aStart);

           begin.set(Calendar.HOUR, 0);

           begin.set(Calendar.MINUTE, 0);

           begin.set(Calendar.SECOND, 0);

           //

           Calendar end = Calendar.getInstance();

           end.setTime(aFinish);

           end.set(Calendar.HOUR, 23);

           end.set(Calendar.MINUTE, 59);

           end.set(Calendar.SECOND, 59);

           try {

               final List<RouteEntity> routes = helper.getSimpleRouteEntityDao()

                       .queryBuilder()

                           .where()

                               .ge("dateStart", begin.getTime())

                           .and()

                               .le("dateStart", end.getTime())

                           .and()

                               .ge("dateFinish", begin.getTime())

                           .and()

                               .le("dateFinish", end.getTime())

                       .query();

               filteredRoutes.addAll(routes);

           } catch (SQLException sqlEx) {

               Log.e(StatisticsCalculator.class.getName(), "Error has occurred while querying for dateRange", sqlEx);

           }

Распределение по группам, основанным на временных периодах, происходит по следующей схеме:

1)Формирование карт для хранение результатов ключи-даты, значения- суммарные показатели параметров.

2)Обходя множество маршрутов извлекаем необходимые значения параметров, формируем ключ для карт результатов, получаем сохраненное значение параметра, либо создаем пустое, добавляем извлеченные из маршрута значения в карты значений.

3) Переводим карты значений в результирующий объект.

Код выполняющий эти операции на примере распределения по дням:

      Map<Date, Double> distanceMap = new TreeMap<>();

                   Map<Date, Long> timeMap = new TreeMap<>();

                   Calendar calendar = Calendar.getInstance();

                   for (RouteEntity route : filteredRoutes) {

                       calendar.clear();

                       calendar.setTime(route.getDateStart());

                       calendar.set(Calendar.HOUR, 0);

                       calendar.set(Calendar.MINUTE, 0);

                       calendar.set(Calendar.SECOND, 0);

                       calendar.set(Calendar.MILLISECOND, 0);

                       Date key = calendar.getTime();

                       Double distance = distanceMap.get(key);

                       Long time = timeMap.get(key);

                       if (distance == null) {

                           distance = 0d;

                       }

                       if (time == null) {

                           time = 0L;

                       }

                       distance = distance + route.getDistance();

                       distanceMap.put(key, distance);

                       time = time + route.getDateFinish().getTime() - route.getDateStart().getTime();

                       timeMap.put(key, time);

                   }

                   for (Date key : distanceMap.keySet()) {

                       result.getDistance().add(distanceMap.get(key));

                       result.getDuration().add(timeMap.get(key));

                       result.getDates().add(key.getTime());

                   }


Реализация компонентов приложения

Главный экран

Основной задачей, реализуемой программным продуктом, является трекинг- запись и отображение информации по движению объекта. Исходя из этого, было решено предоставить при запуске приложения доступ сразу к активити трекера. Важной информацией для отображения при записи маршрута являются затраченное время, пройденное расстояние и мгновенная скорость, линия маршрута на карте. Для отображения этой информации на главном экране реализован фрагмент карты, отображающий пройденный маршрут, и линии запланированных маршрутов. Для отображения числовой информации о маршруте на экран записи маршрута добавлен фрагмент, содержащий списковое представление, элементами которого являются наборы данных вида <Название параметра, Значение; Единица измерения(может отсутствовать)>, отображающие данные о записываемом маршруте.

Управление состоянием трекера осуществляется с помощью элементов меню, расположенных в верхней части экрана. Активити трекера может находиться в двух состояниях: ожидание записи маршрута и запись маршрута. В состоянии ожидания записи в меню верхней вкладки присутствуют кнопка перехода в планировщик и кнопка запуска маршрута, переводящее активити в режим записи. В состоянии записи кнопки планировщика и начала записи скрываются, отображается кнопка остановки записи. В режиме записи на карту наносятся точки маршрута, получаемые с датчика gps.

Навигация по приложению осуществляется через выплывающее меню, содержащее доступные для перехода экраны приложения. Реализуется такой механизм с помощью функционала фреймворка holoeverythere, позволяющего регистрировать активити, отображаемые на выплывающем фрагменте, в адаптере, устанавливаемом на выплывающий список.

Структура приложения является древовидной(отсутствуют зацикливания при перемещении между различными активити). Сделано это по нескольким причинам, основной является упрощение пользования программным продуктом. Ключевым элементом(корнем дерева) сделан главный экран(активити трекера), пользователь может переходить в интересные ему разделы, воспользовавшись выплывающим меню, работать в них, если есть возможность, продвигаться последовательно уровень за уровнем вглубь, возможность перескока с ветки на ветку запрещена, для перехода вглубь другого раздела необходимо вернуться на экран отображения трекера и перейти в интересующий раздел. Вторая причина- это присутствие кнопки «назад», выполняющей переход на предыдущую активити в стеке использованных активити. Такое поведение не всегда является удобным, но его нельзя избежать- это особенность устройств, кроме того, переопределение поведения стандартных элементов нежелательно с точки зрения удобства пользовательского интерфейса. Программный продукт не является проектом, делаемым под заказ для узкоспециализированных задач, к исполнению которых привлекаются специалисты, прошедшие специальное обучение, поэтому, если переопределить «очевидное» поведение, пользователи будут путаться, приложение окажется неудобным.

Список маршрутов

Основной задачей ативити маршрутов является отображение маршрутов и выполнение с ними основных действий. Для выполнения данной задачи идеально подходит списковое представление. Оно позволяет отображать списки однотипных элементов, обрабатывать нажатие на одиночный элемент, при определенной настройке позволяет проводить множественный выбор.

Необходимой информацией для отображения является название маршрута, дистанция, дата и тип. При настройке элемента спискового представления можно сконфигурировать его содержание( через файл настроек) и заполнить его в адаптере списка. В данном случае элементе списка находятся картинка( для отображения типа), и текстовые поля для вывода остальных значений.

Меню активити маршрутов имеет три режима:

  1.  Нет выбранных элементов. Доступны действия обновления маршрутов, поиск по названию.
  2.  Выбран один маршрут. Доступны действия редактирования  маршрута, синхронизации маршрута, удаления.
  3.  Выбрано несколько маршрутов. Доступные действия синхронизации, удаления, отображения множества маршрутов на карте.

Переход в активити отображения информации по маршруту осуществляется через нажатие на элемент списка. Активити отображения информации по маршруту открывается с параметром идентификатор выбранного маршрута(получается из элемента списка).

Выбор типа маршрутов осуществляется с помощью спиннера, содержащего все типы маршрутов и значение «все», при котором отображаются все маршруты.

Отображение информации по маршруту

Назначение этого активити отобразить информацию по пройденному маршруту в более полном объеме, нежели на предыдущем активити. При отображении информации по одному маршруту, данные можно получить непосредственно из полей RouteEntity. Расчет времени, расстояния по точкам является довольно дорогой операцией, нет необходимости пересчитывать каждый раз, так как точки привязаны к маршруту и их характеристики не меняются, достаточно рассчитать при записи и сохранить значения. Для отображения динамики изменения таких параметров как скорость, высота в зависимости от времени, информации в маршруте не достаточно, нужно получить данные из списка точек, хранящихся в маршруте и перевести их в формат доступный для отображения.

Активити отображения информации по маршруту содержит три вкладки:

  1.  Карта и общая информация.
  2.  График скорости.
  3.  График высоты.

Вкладка карты содержит карту, с отображенным на ней маршрутом, который раскрашен в соответствии со значением выбранного параметра на участке. Выбор этого параметра осуществляется через элемент меню верхней панели. Имеется разноцветная шкала, отображающая какому значению соответствует определенный цвет, представляет собой отдельный компонент, реализованный на основе представления, на котором отображаются цветовая гамма и диапазон принимаемых значений. Кроме того, имеется область, отображающая основные параметры маршрута: тип, продолжительность, расстояние, среднюю скорость.

Фреймворк androidplot, реализующий отображение данных на графиках, строит график, но для этого кроме визуальных параметров графика необходимо передать значения параметров функции и соответствующих им значений. В данном случае на вкладке скорости необходимо сформировать значения времени и соответствующих им показателей скорости, для вкладки высоты сформировать значения времен и соответствующих высот. Эти операции осуществляются специальными конвертерами данных. Для отображения значений на осях графиков были сформированы особого рода форматеры, переводящие данные модели, соответствующей графикам, в  подписи осей графиков. Например, работа со временем в задаче, согласно общепринятому стандарту, осуществляется в миллисекундах, а отображаются вполне привычные обычному человеку минуты, секунды.

Статистика

Задача активити статистики отображать суммарную информацию по некоторой выборке маршрутов. Изначально для отображения данных по характеристикам группы маршрутов был создан собственный статический компонент, содержащий поля отображения картинок и поля отображения текстовой информации, такой как название характеристики, ее значение и единицы измерения.

С точки зрения архитектуры приложения такой подход оказался простым, но не слишком удобным: при появлении новой характеристики приходилось менять форму отображения, проводить расчет новой характеристики, делать заполнение формы рассчитанным значением непосредственно в методе самой формы. Как оказалось, такое неудобное поведение было не основной проблемой. При разработке формы дизайн подгонялся под маленькие экраны, на устройствах с большей диагональю экрана форма отображалась некорректно.

Учтя все проблемы, возникшие с формой, было принято решение перейти на отображение информации с помощью спискового представления, элементами которого являлись единообразные объекты, содержащие иконку, соответствующую отображаемой характеристике, название, значение и единицы измерения. Такой переход улучшил внешний вид, сделал формат отображения данных одинаковым для всех устройств, и позволил облегчить работу с данными. Теперь при расчетах формируются объекты для отображения, поэтому при добавлении новой характеристики достаточно рассчитать ее значение и добавить его в список, остальную работу сделает адаптер спискового представления.

Тип маршрута выбирается с помощью спиннера, аналогичном описанному ранее для списка маршрутов.

Вкладка отображения графиков активити статистики получает данные для отображения из калькулятора, передавая туда маршруты, попавшие в выборку. На данном фрагменте также используется график, формируемый фреймворком androidplot, потому калькулятор выдает данные сразу в необходимом формате(списки значений аргументов отображаемой функции и соответствующих им значений), форма отображения отвечает за формирование конвертеров данных модели графика в отображаемые подписи.

Календарь

Задачей календаря является отображение пройденных маршрутов и предоставление доступа пользователю к маршрутам выбранного дня. Поддерживается множественный выбор.

Для реализации календаря было создано представление, отображающее дни месяца с возможностью переключаться между месяцами. При заполнении дней осуществляется выборка маршрутов и распределение их по дням.

При выборе определенного дня или группы открывается активити, содержащая статистическую информацию о маршрутах, соответствующих выбранным дням, и карту отображающую маршруты.

Планировщик

Задачей планировщика является распределение готовых шаблонов маршрутов по дням, с целью дальнейшего их использования как ориентиров на карте трекера.

Планировщик может открываться в двух режимах: дата для привязки заранее известна и после выбора маршрутов для определенного дня необходимо вернуться к форме, вызвавшей планировщик; дата не известна и после выбора маршрутов необходимо связать выбранные маршруты с определенным днем используя календарик.

Планировщик содержит два основных компонента: карту с множеством маршрутов и списковое представление, содержащее основную информацию о маршрутах, и позволяющее цетровать карту на интересующем маршруте.

Для отображения данных в списке доступных маршрутов и формирования списка выбранных разработаны специальные фильтры. В сущность маршрута добавлен дополнительный тип, позволяющий определить, может ли маршрут использоваться как шаблон. Создана связующая сущность, обеспечивающая привязку шаблона к определенному дню, она используется как при открытии трекера и отображения выбранных маршрутов за определенный день, так и в трекере, для определения маршрутов, являющихся запланированными на текущую дату.


Реализация поддержки приложением специфического жизненного цикла графических элементов

В отличие от веб и десктопных приложений, приложения, работающие на ОС Android имеют специфический жизненный цикл элементов пользовательского интерфейса: объекты могут создаваться не один раз, а каждый раз при отключении экрана, сворачивании приложения, нехватке ресурсов. Для простых элементов такое поведение реализуется автоматически, но для представлений с динамически изменяемым содержанием возникает проблема корректного восстановления состояния. Такими прежде всего являются спиковые представления, таблицы, сторонние представления(в данном случае карты). Проблема в том, что представления не могут хранить свои состояния, а из-за пересозданий объектов, данные приходиться отображать заново. Стандартные механизмы виджетов позволяют хранить данные, но восстановление состояний ложится на разработчика.

Хранение состояний

Как было описано выше ОС Android позволяет приложениям хранить данные, соответствующие активити в специальном классе Bundle. Вызывается метод public void onSaveInstanceState(Bundle outState), когда устройство меняет состояние приложения с «выполняется» на «приостановлено» или «завершено». В его реализации разработчик должен сохранить данные, необходимые для дальнейшего восстановления состояния. Для этого Bundle обладает множеством методов, позволяющих отправить на хранение объект определенного типа: putBoolean(String key, boolean value), putByte(String key, byte value), putChar(String key, char value), putLong(String key, long value), putLongArray(String key, long[] value).Такая структура методов эффективна и проста для понимания: легко представляется в виде таблиц, в которой первый столбец является ключом, второй хранимым значением.

В нашем приложении приходится обращаться со сложными объектами, данные методы не подходили. Был использован метод putSerializable(String key, Serializable value), позволяющий отправить на хранение любой сериализуемый объект. Сериализация( обратная операция- десереализация) – процесс, переводящий объект в битовую последовательность, обратная- восстановление объекта из массива бит. Для того, чтобы объект некоторого класса можно было сериализовать, класс должен быть помечен специальным интерфейсом-маркером java.io.Serializable, дающим понять виртуальной машине, что объект данного класса может быть сериализован. Сущности задачи являются сериализуемыми. В приложении придется сохранять в Bundle коллекции сущностей или объектов карты, возникает неприятный момент: интерфейсы java.util.List, java.util.Map, java.util.Set не являются сериализуемыми. Классы, которые их реализуют и используются в задаче(java.util.ArrayList, java.util.HashSet, java.util.HashMap) являются сериализуемыми, поэтому пришлось при объявлении переменных указывать класс, а не интерфейс, либо проводить операцию приведения по типу при вызове метода.

Согласно спецификации, любой из методов отправления объекта в хранилище перетрет предыдущее значение, которое имеет этот же ключ, при опечатке в ключе ошибку до исполнения и получения некорректного значения отловить будет невозможно, поэтому к выбору ключей нужно подходить ответственно. Разумным способом хранения ключей будет их вынесение в константы в классе, в котором они будут использоваться, например так записан ключ в одном из фрагментов, работающих с картой:  private static final String SELECTED_ROUTES_KEY = "selected_routes".

Важным является выбор тех объектов, которые нужно хранить. Не смотря на то, что основными объектами, с которыми работают формы являются сущности из локальной базы или некоторые транспортные объекты, не всегда оправдана лишняя конвертация из объектов модели в объекты, рисуемые на формах. Если везде заниматься хранением именно объектов модели, то возникнут проблемы с производительностью. В таком случае разница между созданием формы «с нуля» будет отличаться от восстановления отсутствием обращения к базе данных. Это даст существенную разницу в производительности, но остается возможность оптимизировать алгоритм работы с состояниями форм.

Показательным примером в этом плане служит работа с маршрутами. В сущности маршрута имеется список точек, в которых содержится информация не только о географическом положении, необходимом для отображения на карте, но и о временных, скоростных характеристиках. Для того чтобы нарисовать на карте линию (объект класса com.google.android.gms.maps.model.Polyline) необходимы точки карты (объекты класса com.google.android.gms.maps.model.LatLng). Их и можно хранить. Это исключает лишнюю конвертацию сущностей в объекты для работы с картой.

Важным параметром при восстановлении фрагментов, содержащих карту, является положение камеры. Как было описано выше, за положением камеры следит специальный слушатель, записывающий значения приближения, широты и долготы в SharedPreferences открытого  активити.

При сохранении состояния спискового представления можно пойти двумя путями: 1) хранить объекты отображаемые в графическом элементе 2)хранить объекты модели, которые отображаются в списковом представлении. Первый способ кажется менее затратным, так и есть, но из-за специфики некоторых компонентов( таких, как планировщик маршрутов), объекты модели все равно необходимо хранить и передавать в контейнер, т.к. объекты списка не содержат многой информации и не предназначены для работы вне своей таблицы, на их основе нельзя будет сформировать данные по маршруту на карте. Поэтому был выбран второй вариант: хранятся объекты модели и идентификаторы выбранных элементов, на основе которых будет сформирован выбор элементов спискового представления после пересоздания.

Восстановление состояний

Согласно жизненному циклу активити и фрагментов, восстановление состояния происходит в методе onCreate(Bundle savedInstanceState), затем в методе onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) происходит создание элементов пользовательского интерфейса, здесь же происходит их заполнение данными. Подобная архитектура вызовов позволяет легко заполнять данные в фрагментах и активити(если элемент не восстанавливается, а создается, поля класса можно заполнить обычными методами установки значений) при условии, что разработчик не имеет доступа ко всему жизненному циклу элемента.

Вернемся к примеру с сохранением маршрута в виде точек. Для восстановления будет достаточно в onCreate(Bundle savedInstanceState) получить из контейнера необходимые данные по известным ключам и заполнить ими(либо значениями по умолчанию) поля класса, на основе которых во втором методе будут заполнены информацией графические компоненты. При восстановлении списков(ListView) заполнение списков происходит обычным образом в класс, управляющий содержимым, передается список объектов, которые необходимо отобразить в представлении. Затем восстанавливается состояние «выбранности» определенных элементов(они так же были получены из контейнера и хранятся в поле класса). Сложность возникает из-за того, что на событие выбора элемента списка назначен слушатель, который работает с другим фрагментом( в данном случае фрагмент с картой находится внутри фрагмента со списком маршрутов). Слушатель, получив событие изменения состояния на «выбрано» срабатывает и вызывает другой фрагмент, жизненный цикл которого находится в стадии, когда не все визуальные элементы готовы к работе( к примеру, не проинициализировалась карта).

Для решения этой проблемы потребовалось модифицировать методы добавления маршрутов на карту, добавив в них дополнительные проверки на возможность отображения маршрутов. В случае, если отображение невозможно, сконвертиованные для отображения маршруты запоминаются и хранятся до тех пор, пока карта не будет готова к работе. Затем они рисуются и удаляются из списка ожидающих отрисовки. Карта становится готовой в собственном методе onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState).

Положение камеры восстанавливается на основе SharedPreferences активити, из которого получаются данные о приближении, широте и долготе. Если положение не было сохранено, имеется возможность выставить положение по-умолчаню.


Тестирование приложения

Тестирование является важным этапом в жизненном цикле любого программного продукта, преследующим две основные цели:

  1.  Удостовериться, что программный продукт работает в соответствии с требованиями.
  2.  Выявить сценарии использования, при которых поведение является некорректным.

Виды тестирования примененные в ходе тестирования программного продукта:

  1.  По объекту тестирования.
  2.  Функциональное тестирование. Представляет собой проверку в целях выявления реализуемости функциональных требований
  3.  Тестирование производительности. Представляет собой проверку быстродействия программного продукта, выполнение определенных функций продукта и оценку потребляемых ресурсов, состояния ключевых элементов.
  4.  По степени автоматизации.
  5.  Ручное тестирование. Проверка функционала продукта человеком, посредством использования доступных возможностей продукта.
  6.  Полуавтоматизированное тестирование. Проверка функционала продукта человеком с частичной автоматизацией некоторых процессов.
  7.  По времени проведения тестирования.
  8.  Альфа-тестирование. Первый этап тестирования, в котором задействованы разработчики программного продукта(иногда вместе с заказчиками). Используется отладочный режим.
  9.  Бета-тестирование. Тестирование продукта людьми, являющимися потенциальными пользователями. Позволяет найти ошибки, не найденные разработчиками, получить отклик, оценку пользователей. Распространяемая для этого версия программного продукта обычно имеет ограниченный срок действия или несколько урезанный функционал.

Функциональное тестирование

Архитектура программного продукта, представляющего из себя множество взаимодействующих друг с другом особым образом компонентов, приводит к тому, что значительная часть проверок правильности работы компонента происходит в момент разработки зависимого компонента. Значит, эта обязанность ложится на разработчиков. Важным является не только факт правильности работы компонента в отдельности, но и правильная реализация возможностей будущего взаимодействия. Большинство данных не передается непосредственно между основными компонентами, передаются некие идентификаторы, позволяющие компоненту самостоятельно перейти в необходимое состояние. Передача идентификаторов в значительной мере упрощает взаимодействие, но иногда данные бывают записаны не в том формате, с которым работает зависимый элемент.

Разница в формате данных не является очевидной ошибкой, поэтому ее сложно отловить. В процессе разработки планировщика была сделана следующая ошибка: формат даты отличался от формата даты в компоненте, в котором запрашивались планируемые маршруты. Это сделало невозможным получение назначенных на определенный день маршрутов(запрос в базу пытался получить маршруты по дате, но в дате запроса присутствовали часы, минуты, секунды и проверка эквивалентности не проходила). Для исправления данной ошибки форматы дат были приведены в соответствие.

Тестирование производительности

Призвано выявить узкие места приложения, проверить стабильность работы, оценить время отклика компонентов. Для этого приложение тестируется в реальных условиях.

Самой долгой операцией является запись маршрута. На данный момент этот процесс проходит так: данные о точках маршрута, получаемые из сервиса gps преобразуются для внутреннего хранения и отображения на карте, затем, если активити карты в сотоянии работы, точка наносится на карту, иначе запоминается и наносится вместе с другими запомненными точками при переходе активити в рабочий режим. В первых версиях приложения трекер работал исправно на коротких по времени маршрутах(важным показателем является не расстояние между точками, а их количество), но на маршруте продолжительностью более двух часов ui-поток начинал тормозить, время обновления таймера подскакивало в пике до 14 секунд, вместе с этим время отклика элементов пользовательского интерфейса возрастало до 10 секунд в пике. Как оказалось, вызвано это было загрузкой ui-потока неоптимальной перерисовкой точек маршрута на карте. Переход к описанному выше алгоритму позволил значительно повысить производительность и время отклика приложения в режиме записи маршрута.

Потенциально долгой операцией является отрисовка уже готового маршрута на карте. При разработке отображения на карте пройденного маршрута в виде раскрашенной линии, отображающий цвет, соответствующий какой-либо группе значений отображаемого параметра, был проведен анализ производительности компонента отображения маршрута. В первоначальном виде цветов было 15, линия рисовалась как множество линий маленькой длины. В ходе развития компонента, был осуществлен переход на работу с градиентом цветов, их количество значительно возросло, как следствие- упала производительность за счет более сложных расчетов. Как оказалось, основной причиной низкой производительности стало создание множества линий(каждые две точки помещались в одну отдельную линию с рассчитанным для нее цветом). Алгоритм был изменен: количество групп значений было снижено, как следствие снизилось количество цветов,  отрезки начали формироваться из нескольких точек, если все они входили в одну группу. Алгоритм проверки возможности включения новой точки в уже имеющуюся линию позволил эффективно группировать точки по значению отображаемого параметра. В лучшем случае отрисовка на карте раскрашенного маршрута займет чуть больше времени на отрисовку(если в маршруте наблюдался резкий скачок значения параметра, но в целом значения попадали в одну группу, количество линий разного цвета будет небольшим), плюс дополнительное время уйдет на определение цветовой группы значения параметра, но это время значительно меньше, чем время, которое было бы затрачено на создание лишних объектов карты.

Ручное тестирование

Альтернативой ручному тестированию служат автоматизированное или полуавтоматизированное тестирование. Почему как основное было выбрано именно ручное? Причин несколько. Это и сложность автоматизации тестов, и отсутствие заранее известных результатов по многим аспектам работы программного продукта(таких как построение маршрутов, получение показаний датчиков), и максимальная приближенность к реальным условиям работы.

Написать при отсутствии заранее известных результатов тест, который бы оценивал состояние в интересующий момент и решал выполнились ли необходимые и достаточные условия просто невозможно. Некоторые же проверки может выполнить только человек(нельзя забывать специфику продукта, важной частью которого являются элементы графического пользовательского интерфейса, не всегда отображаемые приемлемо, хотя работающие без ошибок). В данном случае, при тестировании обработкой продуктом показаний датчиков нельзя эмулировать события датчиков с приемлемой приближенностью к реальной работе. К примеру, для точной эмуляции показаний датчика gps необходимо точно знать как поведет себя устройство в тот или иной момент в определенном положении в пространстве, да, генерация событий, содержащих какие-то данные не является сложной задачей, но такой набор данных не покажет самого главного- как поведет себя продукт в реальных условиях.

Именно в ходе проверки в реальных условиях были выявлены неточности показаний скорости датчиком gps. Как любой сторонний сервис, датчики лишь предоставляют некоторую информацию, решения верить им или нет, как распорядиться данными остаются за программой. Выявленные неточности показаний скорости были устранены с помощью оценок показателей положения, точности датчика.

При расчете показателя скорости на основе положения в пространстве( оно определяется показателем широты и долготы) двух точек и разницы во времени могут возникать значительные «скачки» скорости. В ходе анализа координат точек и значения точности, было выявлено, что такие «скачки» являются следствием неточности измерения координат. Допустим, что объект движется по прямой со скоростью 1 м/с, точность определения положения 0.5 метра( что является недостижимо хорошим показателем точности для реальных датчиков gps), измерение показаний датчиков происходит раз в секунду. Точность говорит в круге какого радиуса вокруг точки полученного положения может реально находиться объект. Пусть первая точка оказалась точно в центре круга, а вторая на расстоянии 0.5 метра от первой( в реальности объект переместился на метр) расчет скорости покажет, что скорость была 0.5 м/с. Второй случай- первая точка оказалась на отдаленной от второй границе области, вторая на максимально удаленной от первой точки. Разница в расстоянии в таком случае покажет 2 метра в секунду. Такое положение дел наглядно демонстрирует, что расчет скорости только на основе положений в пространстве и разницы во времени в моменты, когда точность сопоставима с пройденным расстоянием, не обеспечивает допустимых показателей точности расчетов. Разработанный алгоритм, ограничивающий максимальное значение скорости при определенных показаниях точности измерения у точки пространства и пересчитывающий скорость на основе положения позволил получить максимально правдоподобное значение скорости.

Полуавтоматизированное тестирование

Полуавтоматизированное тестирование отличается от ручного тестирования тем, что генерация данных, либо оценка результатов производятся автоматически. Может применяться не только для верификации работы компонентов, но и для изучения работы и поведения определенных сторонних компонентов.

Интереснейшим сторонним компонентом в продукте является google map. Значительно раньше начала основного этапа разработки, еще на этапе изучения возможностей и выбора инструментов, был проделан простой, но показательный тест. Заключался он в том, что был создан отдельный элемент, содержащий карту, и самостоятельно рисующий на ней маршрут движения абстрактного объекта. Реализуется это очень просто: достаточно генерировать через какой-либо определенный отрезок времени(хотя будет лучше, если время будет случайной величиной из определенного диапазона) точку и добавлять ее в линию на карте. Доступа к времени, за которое прорисовался объект на карте, нет, но можно оценить визуально насколько медленнее начинает работать карта при большом количестве точек. Такие тесты показали, что добавление новой точки работает достаточно быстро. Аналогичный тест проводился для набора точек(перерисовывался значительный участок маршрута), в этом случае наблюдалось падение производительности.

Легкое в реализации и недолгое тестирование отображения объектов на карте позволило еще на этапе проектирования выявить тонкий момент- добавление множества новых объектов на карту занимает очень много времени. Это повлияло на архитектуру компонента трекера.

Альфа-тестирование

Тестирование проходило в несколько этапов, с развитием функционала программного продукта к тестированию привлекалось все больше людей. Начальное тестирование началось в момент, когда был реализован прототип трекера, в тестировании участвовали только разработчики. В это время проходила активная разработка и отладка механизма записи маршрута, кроме сервиса записи имелась лишь форма отображения параметров маршрута. Основной целью тестирования являлась оценка работоспособности разработанного механизма записи, сравнение показателей скорости и расстояния, рассчитанных программой, с реальными. Большое количество тестировщиков не было необходимо из-за отсуствия разнообразного функционала.

С развитием функционала появилась необходимость проверять, не привело ли внедрение нового функционала к поломке старого, в этот момент начали привлекаться тестировщики из числа знакомых людей. Подобное тестирование не требовало особых знаний о продукте, он уже был пригоден для использования широким кругом людей. Однако незначительное увеличение числа людей, тестирующих продукт, не только значительно ускорило поиск ошибок, но привело к появлению новых идей по дальнейшей разработке.

Для реализации альфа-тестирования в рамках google play существует специальный механизм, позволяющий открывать доступ к приложению только некоторым людям, рассылая приглашения на их аккаунты.

Бета-тестирование

Бета-тестирование подразумевает участие в тестировании людей, не связанных с компанией-разработчиком. Для привлечения людей в бета-тестирование обычно используются социальные сети или тематические сайты.

Особенностью продукта является его работоспособность на разных устройствах, проблемы, сопутствующие поддержке на разных устройствах проявляются в первую очередь в компонентах, встроенных в платформу android. Такими являются элементы графического пользовательского интерфейса, некоторые сервисы. Привлечение к тестированию большего количества людей позволило провести проверку работоспособности на многих типах устройств( мобильных устройствах и планшетных компьютерах разных моделей).

Бета-тестирование реализуется в рамках google play способом, аналогичным способом, с той лишь разницей, что участие в тестировании может быть открытым, что подразумевает свободный доступ к приложению всем пользователям сервиса google play.


Пример работы

Активити трекера

При запуске приложения пользователь оказывается на активити трекера в режиме ожидания:

Рис. 1 Трекер в режиме ожидания

После активации режима записи активити трекера примет  следующий вид и будет отображать информацию по записываемому маршруту:

Рис. 2 Трекер в режиме записи


Активити списка маршрутов

При переходе в список маршрутов пользователь увидит активити списка маршрутов:

Рис. 3 Активити списка маршрутов


Активити маршрута

При открытии одного из маршрутов из списка откроется активити просмотра информации по маршруту на вкладке карты:

Рис. 4 Вкладка отображения выбранного маршрута на карте

При переходе на вкладку скорости пользователю будет представлен график:

Рис. 5 Вкладка отображения графика скаорости

При переходе на вкладку высоты пользователю будет представлен график:

Рис. 6 График отображения высоты маршрута

Активити статистики

При переходе на активити статистики пользователь окажется на вкладке отображения графиков числовых значений дистанции и времени поездок:

Рис. 7 Вкладка отображения графиков активити статистики

При переходе на вторую вкладку пользователь увидит фрагмент отображения информации по периоду:

Рис. 8 Фрагмент отображения статистических данных

Активити календаря

Активити календаря имеет следующий вид:

Рис. 9 Активити календаря

Активити планировщика

При вызове планировщика пользователь увидит следующее:

Рис. 10 Активити планировщика

Литература и источники

В процессе выполнения задания были использованы следующие источники:

  1.  web-сайт http://android-developers.blogspot.ru/
  2.  web-сайт http://habrahabr.ru/
  3.  web-сайт http://www.vogella.com/
  4.  web-сайт http://startandroid.ru/
  5.  web-сайт http://www.holoeverywhere.com/
  6.  web-сайт http://androidplot.com/
  7.  web-сайт http://ormlite.com/
  8.  web-сайт http://stackoverflow.com/


Заключение

В результате выполнения дипломной работы поставленные цели и задачи были выполнены. Был разработано Android-приложение, позволяющее пользователям отслеживать собственные перемещения, вести статистику пройденных маршрутов, планировать будущие поездки.

В дальнейшем, в приложение могут быть внесены следующие доработки:

  1.  Интеграция с социальными сетями
  2.  Улучшение интерфейса, дизайна.
  3.  Расширение возможностей планирования маршрутов, тренировок.
  4.  Реализация возможности отображать маршрут в режиме он-лайн.

Полученное приложение было размещено в магазине приложений Google Play. В Google play существуют аналоги приложения, самые популярные runtastic и endomondo.

Преимущества разработанного приложения в том, что присутствует ведение статистики высоты(в runtastic реализрвано только в платной версии), реализована статистика по временным периодам, присутствует возможность группировки данных, при расчетах скорости использован разработанный механизм сглаживания неточностей показаний скорости.


 

А также другие работы, которые могут Вас заинтересовать

57935. Опрацювання табличних даних за допомогою будованих функцій 19.78 MB
  Учбова: Навчити дітей на практиці застосовувати набуті знання та навички з використання вбудованих функцій та формул в електронних таблицях. Виховна: Виховати у дітей естетичне оформлення файлу, створеному у середовищі табличного процесора...
57936. Виконання обчислень в середовищі табличного процесора 105.09 KB
  Мета уроку: Учбова: Навчити дітей виконувати обчислення в середовищі табличного процесора. Обладнання до уроку: проектор підставка під проектор ноутбук проекційний екран лазерний вказівник затемненн...
57937. Розвиток Архітектури Харкова у XVIII столітті 112.5 KB
  Дидактична мета: В ході уроку забезпечити засвоєння та усвідомлення учнями знань щодо розвитку архітектури Харкова у XVIII столітті історичних пам’яток нашого міста сучасного стану споруд того часу.
57938. Подорож у світ пірамід 28.5 KB
  Мета уроку: Сформувати поняття піраміди правильної та зрізаної піраміди та їх елементів. Засвоїти властивості правильних пірамід формули для обчислення площі повної та бічної поверхні піраміди.
57939. Поняття презентації та комп’ютерної презентації, їх призначення 75.5 KB
  Мета: навчальна: ознайомити учнів з поняттям презентації та комп’ютерної презентації їх призначенням зі слайдовими та потоковими презентаціями; оглянути програмні та технічні засоби призначених для створення і демонстрації презентацій...
57940. Повторення та закріплення вивченого матеріалу з теорії бухгалтерського обліку 201.5 KB
  Мета уроку: навчальна: повторити, закріпити та підсумувати навчальний матеріал з теорії бухгалтерського обліку, навчити учнів застосовувати здобуті знання на практиці та в житті...
57941. Створення звітів та макросів у Microsoft Access 2007 60.5 KB
  Мета: навчальна: ввести визначення понять звіт заголовок звіту область даних макрос; пояснити учням основні принципи створення звітів та макросів; розвивальна: формувати навички аналітикосинтетичного мислення просторову уяву науковий світогляд щодо вирішення різних задач прикладного спрямування...
57942. Природные зоны Африки 62 KB
  Цель: закрепить и обобщить знания по теме «Природные зоны Африки»; создать условия для понимания взаимосвязей природных компонентов в составе природной зоны; закреплять навыки работы с контурной картой; развивать умение работать в группах.
57943. Автомат Калашникова АК–74 63 KB
  Образовательные: познакомить обучающихся с назначением боевыми свойствами АК74 и устройством его частей и механизмов; сформировать представления об автоматическом действии автомата АК74...