Некоторое время назад передо мной была поставлена задача по определению смены местоположения пользователя на карте. По результатам эксперимента в статье, для этих целей, по точности определения и энергоэффективности, прекрасно подходит Google Services Geofences.
Как работать с Geofences подробно рассмотрено в единственном русскоязычном примере по использованию Location APIs в статье на хабре, но с тех пор прошло уже 2 года, и информация сильно устарела.
Пример автора на github, к сожалению, даже не компилировался, поэтому я решил его завести под свежие версии библиотек. На мое удивление, изменений в API между com.google.android.gms:play-services:4.0.30
и com.google.android.gms:play-services:8.4.0
оказалось много! Собственно, о них дальше и пойдет речь в статье.
Так в чем же отличия?
Для начала желательно ознакомиться с оригиналом.
В самой концепции ничего не поменялось, изменились только ответственные классы.
Так, вместо 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 все отлично работает!
Ссылки:
- Код на github
- Оригинал статьи
- Официальная документация по Geofences 1 и 2
- ReactiveLocation
- lockito