Некоторое время назад передо мной была поставлена задача по определению смены местоположения пользователя на карте. По результатам эксперимента в статье, для этих целей, по точности определения и энергоэффективности, прекрасно подходит 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
Комментарии (13)
furyon
22.04.2016 08:30Недавно была необходимость решать подобную задачу, после различных попыток остановился на pathsense.com, довольно простой API, неплохая точность, батарейку жрет также как geofences. Настроил работу в сервисе, при входе в зону вылетал пуш даже при закрытом приложении.
Vilkaman
22.04.2016 08:43Спасибо, не знал о таком! Если верить табличке сравнения на их сайте, но это решение действительно лучше. Однозначно стоит попробовать!
Но я думаю, если на телефоне будет установлено несколько различных приложений, которые используют pathsense.com то расход батареи будет больше, за счет независимой обработки в каждом приложении. Что думаете?furyon
22.04.2016 09:02Я в конечно счете после долгого тестирования отказался как от pathsense так и geofence из-за расхода батареи. Тратилось до 10% если постоянно включено. Реализовал так, раз в несколько часов подгружается список координат с радиусами, и сервисом раз в минуту проверять кто вошел, сейчас по результатам тестов батарейки есть почти в 10 раз меньше, но разумеется от такого способа свои проблемы, но меня все устраивает.
Vilkaman
22.04.2016 09:2310% за какой период? Для своей задачи, я использовал geofance немного не по назначению, для определения что пользователь просто начал двигаться (планирую написать об этом еще одну статью) и по сравнению с другими подходами (прямую проверку GPS координат, google services activity recognition..) такой расход батарее заказчика устраивал)
furyon
22.04.2016 09:29Поскольку мои geofences работали в фоне всегда, я говорю про 10 процентов от общего заряда батареии. Там на pathsense написано что они пожирают около 0.7% в час.
Harco
22.04.2016 08:30Geofence всё ещё имеет проблемы, так например не всегда срабатывает триггер при выходе из гео-зоны, на реальных устройствах, поэтому надеяться на его работу неблагодарное занятие.
Vilkaman
22.04.2016 08:45Как часто это происходит? Можно ли это как-то повторить? или это рандомный баг, который например, повторяется только где-нибудь в лесу?
Harco
22.04.2016 08:53Да, в официальной документации написано, что если вы находитесь где-то за городом, то триггеры не будут срабатывать. В моём же случае они не срабатывали в городе, причём на разных устройствах в случайном порядке которые находились рядом, закономерности никакой. furyon пишет про pathsense сервис, да он уменьшает количество не срабатываний, но напирмер на nexus 5 он тоже не всегда отрабатывает при выходе из зоны.
rude
на последнем эмуляторе из SDK, geo-fence срабатывает при эмуляции локации GPS
Vilkaman
Если изменить координаты в ручном режиме, не через сторонние приложения?
rude
Да, если менять вручную. Например вот так:
Сначала ставим координаты вне geo-fence локации, а потом меняем на координаты в радиусе нашей geo-fence.