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

от автора

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

image

Как работать с Geofences подробно рассмотрено в единственном русскоязычном примере по использованию Location APIs в статье на хабре, но с тех пор прошло уже 2 года, и информация сильно устарела.

Пример автора на github, к сожалению, даже не компилировался, поэтому я решил его завести под свежие версии библиотек. На мое удивление, изменений в API между com.google.android.gms:play-services:4.0.30 и com.google.android.gms:play-services:8.4.0 оказалось много! Собственно, о них дальше и пойдет речь в статье.

Код

Обновленный пример на github (на момент написания статьи автор оригинального примера не принял pull request).

Так в чем же отличия?

Для начала желательно ознакомиться с оригиналом.

В самой концепции ничего не поменялось, изменились только ответственные классы.
Так, вместо LocationClient имеем api.GoogleApiClient, callback’и из GooglePlayServicesClient также переехали в api.GoogleApiClient, вместо new LocationClient(this, this, this) появился удобный билдер:

    new GoogleApiClient.Builder(this)         .addApi(LocationServices.API)         .addConnectionCallbacks(this)         .addOnConnectionFailedListener(this)         .build();

Немаловажным отличием является то, что добавлением и удалением геозон mGoogleApiClient занимается не напрямую, а через LocationServices.GeofencingApi.

    GeofencingRequest build = ...     LocationServices.GeofencingApi.addGeofences(mGoogleApiClient, builder.build(), getPendingIntent())             .setResultCallback(new ResultCallback<Status>() {                 @Override                 public void onResult(@NonNull Status status) {                     if (status.isSuccess()) {                         String msg = "Geofences added: " + status.getStatusMessage();                         Log.e("GEO", msg);                         Toast.makeText(GeofencingService.this, msg, Toast.LENGTH_SHORT)                                 .show();                     }                     GeofencingService.this.onResult(status);                 }             });

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

    GeofencingRequest.Builder builder = new GeofencingRequest.Builder();     builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);     builder.addGeofences(mGeofenceListsToAdd);     GeofencingRequest build = builder.build();

Одна из возможностей нового билдера — управление поведением геозон в момент добавления. Например, в комментариях к оригинальной статье спрашивали про возможность срабатывания триггера Exit geofence для случая, когда девайс находится снаружи зоны в момент ее установки. Теперь это можно сделать передав флаг GeofencingRequest.INITIAL_TRIGGER_EXIT через метод setInitialTrigger (int initialTrigger), по умолчанию флаги GeofencingRequest.INITIAL_TRIGGER_ENTER и GeofencingRequest.INITIAL_TRIGGER_DWELL. Флаги можно комбинировать между собой.

Кроме этого был убран последний параметр-callback, теперь вместо него LocationServices.GeofencingApi.addGeofences возвращает PendingResult, с помощью которого можно, блокируя поток, ожидать результат или получить ответ асинхронно, с помощью callback метода setResultCallback(..). В любом случае результатом будет статус операции добавления\удаления геозоны. Данный callback заменяет собой OnAddGeofencesResultListener или onRemoveGeofencesByRequestIdsResult из оригинальной статьи.

Удалить не нужные геозоны можно через методы:

    LocationServices.GeofencingApi.removeGeofences(mGoogleApiClient, /*PendingIntent или список id геозон*/)

Изменения в ReceiveTransitionsIntentService

Если раньше для обработки результатов срабатывания триггеров на геозоне использовались статические методы из класса LocationClient, которые требовали в качестве параметра пришедший Intent, то сейчас этим занимается GeofencingEvent, который имеет одноименные методы для выполнения той же работы. Получить его можно следующим образом:

GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);

Создания самих геозон осталось без изменений и происходит через Geofence.Builder.

Еще одним из новшеств является то, что после срабатывания триггера геозона удаляется автоматически, таким образом нам не нужно больше убирать их самостоятельно!

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

Несколько советов по работе с геозонами

  • Для тестирования я выбрал эмулятор Genymotion, но при попытке установить геозону LocationServices выдавал ошибку status code = 1000 (GEOFENCE_NOT_AVAILABLE). Решение этой проблемы нашлось на stackoverflow

  • Если выставить в качестве условия срабатывания триггеры GeofencingRequest.INITIAL_TRIGGER_EXIT и GeofencingRequest.INITIAL_TRIGGER_ENTER или Geofence.GEOFENCE_TRANSITION_ENTER и Geofence.GEOFENCE_TRANSITION_EXIT, то сработает только одно условие из каждой пары, после чего зона будет удалена

  • Для работы с Rx можно использовать эту библиотеку, тогда весь процесс по добавлению\удалению сводится к коду:

    GeofencingRequest.Builder builder = new GeofencingRequest.Builder() ... new ReactiveLocationProvider(context). locationProvider.addGeofences(getPendingGeoIntent(),builder.build()) .subscribe(status -> {     if (status.isSuccess()) {} };

  • К сожалению, если просто изменять координаты через встроенную в Genymotion карту, то триггеры не срабатывают. Но через lockito все отлично работает!

Ссылки:

ссылка на оригинал статьи https://habrahabr.ru/post/282129/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *