Работа с GPS на Android

Работа с GPS на AndroidМногие современные мобильные устройства оборудованы GPS приемниками. Аппараты на базе Android не стали исключением. В этой статье, мы рассмотрим работу с GPS в устройствах на базе Android с точки зрения разработчика.

Современные GPS приемники для мобильных телефонов имеют ряд ограничений – таких как:

  1. Долгое определение начальных координат при старте GPS;
  2. Ручное задание частоты обновления положения и выбор провайдера;
  3. Высокое энергопотребление GPS приемника при частом обновлении.

Все эти недостатки требуется компенсировать сложной логикой программного обеспечения, работающего с GPS. Предоставленная Android SDK сглаживает лишь часть проблем, и стандартного API для работы с GPS становится недостаточно.

С другой стороны, для каждого приложения нужен собственный набор параметров и настроек GPS, таких как: провайдер (GPS, AGPS, Wireless networks), период обновления, логика приложения (нужна реакция на изменение координат или просто получение текущего местоположения).

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

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

Функциональные требования к библиотеке для работы с GPS

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

  1. Давать возможность разработчику не следить за подписанием на уведомления об изменение GPS координат. Часто бывает нужно, чтобы подписание действовало только в то время, когда форма открыта — при деактивации формы (stop, pause, back, destroy) нужно отписываться, а при активации — создавать нового слушателя и подписывать его на уведомления об изменение. Недостатков в этом подходе много. От простых — можно забыть отписаться при закрытии формы, до нетривиальных — если слушателей много, то они могут быть не синхронизированы и иметь разное значение координат, а также множество одинаковых объектов — это дополнительная нагрузка на память и процессор (и, как следствие, на аккумулятор). Поэтому логически вытекает второй пункт требований;
  2. Использовать всего один объект LocationListener и при этом НЕ singleton. В Android OS экземпляры объектов уничтожаются вместе с Activity, на которой они были созданы поэтому создавать обекты с глобальным доступом следует не в Activity, а в Application. Создавать новый экземпляр LocationListener при каждом обращении к GPS нестоит, сборщик мусора сам убирает отписанных слушателей, но лишние объекты занимают память, процессорное, и увеличивают энергопотребление,- пусть и кратковременно. При объявлении LocationListener в Application, вся работа будет происходить с одним объектом, что упрощает отладку приложения. При работе с GPS на устройстве часто необходимо выставить ложные координаты. Стандартный способ требует изменения манифеста, добавления новых разрешений, написание класса для выставления координат и поэтому является слишком сложным и трудоемким. Гораздо проще выставить фиксированное значение координат для одного объекта LocationListener;
  3. Быстро получать приблизительные координаты. Часто при старте приложения нужно определить примерное положение клиента (например, для загрузки конкретного контента или локализации приложения) — поэтому (при старте) некогда дожидаться пока определятся точные координаты, нужно получить примерные. Примерные координаты могут быть получены разными способами — использовать последние известные телефону координаты, либо, если последние координаты неизвестны, использовать провайдер Wireless networks (который определяется намного быстрее, чем GPS);
  4. Сообщать об изменении координат за пределы контекста текущего приложения. Несмотря на то, что экземпляр LocationListener один, сообщение об изменении координат должны получать множество объектов логики (далее будем называть такие классы “слушатели”), при этом, не используя паттерн “Обозреватель”, потому что не всегда обмен данными происходит внутри единого контекста программы. Прямой обмен невозможен, например, когда модуль работы с GPS — это Service, а получатель (логика) находится в Application, или вовсе является сторонним приложением;
  5. Иметь возможность разом отписать всех “слушателей” от GPS, и главное иметь возможность восстановить всех слушателей обратно. Это может понадобиться при отправке координат на сервер. Пока сервер получает и обрабатывает старые координаты — клиентская часть не должна реагировать на новые изменения, поскольку в противном случае ответ от сервера может быть уже не только неактуальным, но и не верным (для нового положения);
  6. Автоматически переподключаться к наиболее точному провайдеру. Способов получения координат несколько и всегда должен быть использован наиболее точный, но следить за появлением нового провайдера части логики не должны, и уж тем более выполнять переподключение вручную. Это должен выполнять модуль работы с GPS.

Как реализовать модуль работы с GPS

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

Сосредоточимся на реализации вышеизложенных требований:

1-2) Из-за вышеописанной проблемы (затирание объектов вместе с экземпляром Activity) использовать паттерн Singletone нельзя. Поэтому все объекты, которые должны присутствовать в единственном экземпляре следует объявлять не в Activity а в Application. LocationListener — класс отвечающий за изменение GPS координат тоже должен существовать в единственном экземпляре и как следствие должен быть описан в классе Application. При выходе из приложения нужно не забыть удалить все ссылки на LocationListener иначе произойдет ошибка “Memory Leak”. Приложение может быть закрыто принудительно и в этом случае тоже нужно предусмотреть корректное удаление ссылок. Класс Application содержит в себе метод onTerminate() — этот метод срабатывает даже если приложение закрыли принудительно. Поэтому именно в нем следует вызвать метод LocationListener. unregister() — чтобы удалить ссылку на данный объект из системного класса LocationManager. Данных подход гарантирует единственность объекта LocationListener, и свободный доступ к данному объекту через context, который присутствует в любом View и Activity.

3) Для получения начальных (неточных) координат, которые могут понадобится при старте приложения, можно использовать последние известные телефону координаты. Последние известные координаты всегда автоматически запоминаются телефоном и доступны через стандартные системные API .

LocationManager. getLastKnownLocation ( LocationManager. GPS_PROVIDER ) ;

Они не совсем точны (например если пользователь выключил GPS и после этого начал двигаться) — но при старте приложения, как уже было замечено ранее, примерного положения вполне достаточно. Однако если пользователь ни разу не включал GPS с момента старта телефона, то последние известные координаты недоступны. В таком случае в качестве провайдера следует использовать не GPS_PROVIDER, а NETWORK_PROVIDER — координаты по NETWORK_PROVIDER определяются значительно быстрее чем GPS_PROVIDER, хотя тоже являются неточными. Важное замечание: при использовании NETWORK_PROVIDER — радиус погрешности выдаваемый LocationManager является достаточно большим. и гарантировать что пользователь находится в пределах круга с центром в полученных координатах и радиуса равного радиус погрешности — нельзя. Это связано с тем что данных провайдер может определяет ближайший известный ему узел сети а не местоположение самого пользователя Это происходит если в устройств выходит в интернет с помощью Wi-Fi и не имеет sim карты.

4) Реализация передачи данных между различными Context приложениями. например, между двумя несвязными приложениями или приложением и сервисом. Для реализации передачи данных (или уведомлений) в контекст другого приложения можно использовать внутренние системные сообщения OS Android. Системные сообщения могут содержать в себе данные либо просто код события которое произошло. Код произошедшего события передается во все активные приложения, но обрабатывается только теми у которых соответствующим образом настроен фильтр системных сообщений IntentFilter. Для того чтобы наше приложение получало уведомления о изменении координат — расширим IntentFilter добавив в него наш тип сообщения — MessageManager. LOCATION_CHANGED. LocationListener в свою очередь — должен отправлять соответствующие системные сообщения, поэтому при изменении координат создается Intent с сообщением, что координаты изменились, а дальше каждый “слушатель” сам решает, что делать — обработка логики. Благодаря такому подходу в логике остается только поведение (реакция на изменение координат), а вся работа с подписанием, провайдерами и точностью уходит в модуль работы с GPS .

Способ отправки системного сообщения:

Intent intent = new Intent ( MessageManager. PLAYER_LOCATION_CHANGED ) ;

context. sendBroadcast ( intent ) ;

5)Возможность разом отписать всех “слушателей” от GPS, и главное иметь возможность восстановить всех слушателей обратно. При выбранной нами архитектуре данное требование реализуется автоматически. Поскольку объект LocationListener один то при unregister() — он перестанет рассылать сообщения об изменении координат, то есть “слушатели” перестанут получать уведомления, но останутся подписанными на сам LocationListener, то есть если мы вызовим метод register(), то все “слушатели” опять начнут получать системные сообщения с уведомлениями. С другой стороны отписание “слушателя” никак не влияет на работу LocationListener и других “слушателей”, а восстановить подписание “слушателей” можно, поскольку LocationListener всего один и доступен через context.

6) Автоматическое переподключени к наиболее точному провайдеру. При перемещении пользователя точность текущего GPS провайдера может меняться — например есть пользователь зашел в металлический гараж то все спутники, по которым определяется GPS, исчезнут и точность его будет минимальна, а точность интернет провайдера может остаться прежней. Так и наоборот если пользователь использовал интернет провайдер, а затем включил GPS, то логичнее использовать GPS. Определять наиболее точного провайдера LocationListener может самостоятельно. Для этого необходимо периодически опрашивать все провайдеры на доступность и точность определения координат. Обычно время для обновления провайдера выставляется в зависимости от задачи приложения. Данный подход более детально описан в документации Android.

Все что было описано выше умещается в один класс, который приведен ниже

import android. app. Application ;

import android. content. Context ;

public class LocationGpsOnlyService implements LocationListener,

LocationService <

private static final int BEST_PROVIDER_SHCEDULE = 5000;

private LocationManager locationManager;

private Location gpsLocation;

private Location firstLocation;

private Context context;

private TimerTask locationCheckingTask;