Pull to refresh

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

Reading time4 min
Views13K

Некоторое время назад передо мной была поставлена задача по определению смены местоположения пользователя на карте. По результатам эксперимента в статье, для этих целей, по точности определения и энергоэффективности, прекрасно подходит 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 все отлично работает!

Ссылки:


Tags:
Hubs:
Total votes 9: ↑9 and ↓0+9
Comments13

Articles