Работа с геозонами (geofences) в Android

Работа с геозонами (geofences) в Android

Примечание: Google Play сервисы могут быть отключены на устройстве. Это может нарушить работу многих приложений и система честно предупреждает пользователя об этом перед их отключением. Но всё же хорошим тоном будет проверять это в своем приложении с помощью GooglePlayServicesUtil.isGooglePlayServicesAvailable и как-то предупреждать пользователя.

Задача

Итак, для примера напишем приложение, в котором можно явно указать координаты и радиус геозоны. При входе/выходе из неё в статус бар будет добавляться уведомление с id геозоны и типом перемещения. После выхода из геозоны мы её удалим.

Алгоритм
  1. Из активити создаем сервис, в который передаем данные о геозоне.
  2. Сервис инициализирует LocationClient.
  3. Когда LocationClient инициализировался, добавляем в него геозоны (Geofence) и соответствующие им PendingIntent.
  4. Когда геозоны добавлены, отключаемся от LocationClient и останавливаем сервис.
  5. Далее вся надежда на PendingIntent, который запустит IntentService при входе в зону или выходе из зоны. Сервис добавляет уведомления в статус бар и создает сервис для удаления отработанных геозон.
  6. Созданный сервис снова инициализирует LocationClient.
  7. Когда LocationClient инициализировался, удаляем отработанные геозоны.
  8. Когда геозоны удалены, отключаемся от LocationClient и останавливаем сервис.
  9. Profit!
К делу!

Для начала необходимо подключить Google Play сервисы. Как это сделать описано здесь. Далее в активити инициализируем элементы отображения. Из этой области нас интересует вызов сервиса при обработке нажатия на кнопку:

Тут мы создаем Intent для нашего сервиса (GeofencingService) и передаем в него необходимые данные. Так как GeofencingService отвечает за добавление и удаление геозон (в примере я решил не разделять эти действия на разные сервисы), то нам надо передать тип операции, которая должна быть выполнена сервисом. В данном случае это добавление (GeofencingService.Action.ADD). Также сервису нужны данные о геозоне. Их мы передаем в виде объекта класса MyGeofence, который по сути является оберткой над Geofence.Builder (о нём мы поговорим позже). Итак, мы передаем координаты центра и радиус зоны, а также тип перемещения. Последний может быть трех видов: GEOFENCE_TRANSITION_ENTER, GEOFENCE_TRANSITION_EXIT и GEOFENCE_TRANSITION_DWELL. Если с первыми двумя все понятно, то к третьему необходимы разъяснения. GEOFENCE_TRANSITION_DWELL указывает на то, что пользователь вошел в зону и пробыл в ней некоторое время. Чтобы использовать этот сигнал, вы должны установить setLoiteringDelay при построении геозоны. В данном примере GEOFENCE_TRANSITION_DWELL не используется.

Перейдем к сервису. Сервис имплементирует GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener, LocationClient.OnAddGeofencesResultListener, LocationClient.OnRemoveGeofencesResultListener интерфейсы. Это позволяет ему полностью отвечать за работу с LocationClient. В onStartCommand мы получаем тип операции (ADD или REMOVE) и вытягиваем необходимые для выполнения этого действия данные. После этого инициализируем и запускаем LocationClient:

Прежде чем добавить геозону mGeofenceListsToAdd, мы вызвали метод toGeofence() объекта класса MyGeofence. Я уже говорил, что MyGeofence является обёрткой над Geofence.Builder:

Geofence.Builder — это служебный класс для создания Geofence. Мы задаем необходимые параметры, а потом вызываем метод build() для создания объекта. Выше указан необходимый минимум параметров. Тут стоит обратить внимание на setExpirationDuration. Дело в том, что зарегистрированные геозоны могут быть удалены только в двух случаях: по истечении заданного времени или при явном удалении. Поэтому, если вы передаете в качестве параметра NEVER_EXPIRE, то вы обязаны позаботиться об удалении объекта самостоятельно. Для Location APIs есть ограничение: максимум 100 геозон на одно приложение одновременно.

После того как LocationClient подключится, сработает onConnected колбэк интерфейса GooglePlayServicesClient.ConnectionCallbacks. В нем мы выполняем добавление либо удаление в зависимости от текущего типа действия:

Как мы видим, addGeofences одним из параметров требует PendingIntent, который сработает при перемещении. В нашем случае PendingIntent будет запускать IntentService:

После выполнения действия у нас срабатывают OnAddGeofencesResultListener или onRemoveGeofencesByRequestIdsResult , в которых мы отключаемся от LocationClient и останавливаем сервис:

Последняя часть приложения – это IntentService, который запускается при пересечении границы геозоны пользователем устройства. Все действия выполняются в onHandleIntent:

Здесь у нас фигурируют в основном статические методы LocationClient. Сначала мы делаем проверку на наличие ошибок с помощью hasError. Затем получаем тип перемещения и список сработавших геозон с помощью getGeofenceTransition и getTriggeringGeofences соответственно. Вызываем обработку каждой геозоны и сохраняем её id. Ну и напоследок, удаляем геозоны в случае, если данное перемещение было выходом из геозоны. Для удаления геозон мы опять создаём сервис, в который передаём тип операции (REMOVE) и список id на удаление:

На этом всё!

Надеюсь пример получился понятным и интересным. Желаю всем хороших приложений!

UPDATE: Статья и код сильно устарели за 2 года. Спасибо Vilkaman за обновления кода в репозитории. Подробнее о работе с обновленным Location API можно прочитать в его статье

📎📎📎📎📎📎📎📎📎📎