- PVSM.RU - https://www.pvsm.ru -
-v-Android.png)
Добрый день. Сегодня я хотел бы рассказать о Location APIs [1] в общем и о геозонах (geofences) в частности, которые были представлены на Google I/0 2013 (видео [2] и презентация [3]). Не смотря на то, что событие произошло более полугода назад, на хабре до сих пор нет вменяемой информации об этом (только одно упоминание [4]). Постараюсь немного исправить ситуацию.
Location APIs являются частью Google Play сервисов, которая предназначена для создания приложений работающих с местоположением устройства. В отличие от подобных функций в LocationManager [5], данные API отличаются улучшенным энергосбережением. В данный момент доступна следующая функциональность: определение местоположения устройства, работа с геозонами и распознавание активности пользователя. Определение местоположения позволяет балансировать между точностью определения и потреблением энергии, а также предоставляет доступ к наиболее частым местоположениям. Распознавание активности позволяет узнать, что делает пользователь устройства: едет на машине, едет на велосипеде, идет пешком или находится на одном месте. Ну и, собственно, работа с геозонами позволяет посылать сообщения, когда пользователь устройства входит в конкретную зону, покидает её либо находится в зоне определенный период времени.
На мой взгляд официальный пример [6] довольно сложный и запутанный. Это связано с тем, что в нём:
Исходя из этого в данной статье я сфокусируюсь только на геозонах и опущу некоторые обработки исключений.
Примечание: Google Play сервисы могут быть отключены на устройстве. Это может нарушить работу многих приложений и система честно предупреждает пользователя об этом перед их отключением. Но всё же хорошим тоном будет проверять это в своем приложении с помощью GooglePlayServicesUtil.isGooglePlayServicesAvailable [7] и как-то предупреждать пользователя.
Итак, для примера напишем приложение, в котором можно явно указать координаты и радиус геозоны. При входе/выходе из неё в статус бар будет добавляться уведомление с id геозоны и типом перемещения. После выхода из геозоны мы её удалим.
В общем процесс выглядит следующим образом:
Как мы видим, главным действующим лицом является LocationClient. Он отвечает за доступ к API для определения местоположения и работы с геозонами.
Для начала необходимо подключить Google Play сервисы. Как это сделать описано здесь [9].
Далее в активити инициализируем элементы отображения. Из этой области нас интересует вызов сервиса при обработке нажатия на кнопку:
int transitionType = Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT;
MyGeofence myGeofence = new MyGeofence(mId, latitude, longitude, radius, transitionType);
Intent geofencingService = new Intent(activity, GeofencingService.class);
geofencingService.putExtra(GeofencingService.EXTRA_ACTION, GeofencingService.Action.ADD);
geofencingService.putExtra(GeofencingService.EXTRA_GEOFENCE, myGeofence);
activity.startService(geofencingService);
Тут мы создаем 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:
mAction = (Action) intent.getSerializableExtra(EXTRA_ACTION);
switch (mAction) {
case ADD:
MyGeofence newGeofence = (MyGeofence) intent.getSerializableExtra(EXTRA_GEOFENCE);
mGeofenceListsToAdd.add(newGeofence.toGeofence());
break;
case REMOVE:
mGeofenceListsToRemove = Arrays.asList(intent.getStringArrayExtra(EXTRA_REQUEST_IDS));
break;
}
mLocationClient = new LocationClient(this, this, this);
mLocationClient.connect();
Прежде чем добавить геозону mGeofenceListsToAdd, мы вызвали метод toGeofence() объекта класса MyGeofence. Я уже говорил, что MyGeofence является обёрткой над Geofence.Builder:
public MyGeofence(int id, double latitude, double longitude, float radius, int transitionType) {
this.id = id;
this.latitude = latitude;
this.longitude = longitude;
this.radius = radius;
this.transitionType = transitionType;
}
public Geofence toGeofence() {
return new Geofence.Builder()
.setRequestId(String.valueOf(id))
.setTransitionTypes(transitionType)
.setCircularRegion(latitude, longitude, radius)
.setExpirationDuration(ONE_MINUTE)
.build();
}
Geofence.Builder — это служебный класс для создания Geofence. Мы задаем необходимые параметры, а потом вызываем метод build() для создания объекта. Выше указан необходимый минимум параметров. Тут стоит обратить внимание на setExpirationDuration. Дело в том, что зарегистрированные геозоны могут быть удалены только в двух случаях: по истечении заданного времени или при явном удалении. Поэтому, если вы передаете в качестве параметра NEVER_EXPIRE, то вы обязаны позаботиться об удалении объекта самостоятельно. Для Location APIs есть ограничение: максимум 100 геозон на одно приложение одновременно.
После того как LocationClient подключится, сработает onConnected колбэк интерфейса GooglePlayServicesClient.ConnectionCallbacks. В нем мы выполняем добавление либо удаление в зависимости от текущего типа действия:
@Override
public void onConnected(Bundle bundle) {
Log.d("GEO", "Location client connected");
switch (mAction) {
case ADD:
Log.d("GEO", "Location client adds geofence");
mLocationClient.addGeofences(mGeofenceListsToAdd, getPendingIntent(), this);
break;
case REMOVE:
Log.d("GEO", "Location client removes geofence");
mLocationClient.removeGeofences(mGeofenceListsToRemove, this);
break;
}
}
Как мы видим, addGeofences одним из параметров требует PendingIntent, который сработает при перемещении. В нашем случае PendingIntent будет запускать IntentService:
private PendingIntent getPendingIntent() {
Intent transitionService = new Intent(this, ReceiveTransitionsIntentService.class);
return PendingIntent.getService(this, 0, transitionService, PendingIntent.FLAG_UPDATE_CURRENT);
}
После выполнения действия у нас срабатывают OnAddGeofencesResultListener или onRemoveGeofencesByRequestIdsResult , в которых мы отключаемся от LocationClient и останавливаем сервис:
@Override
public void onAddGeofencesResult(int i, String[] strings) {
if (LocationStatusCodes.SUCCESS == i) {
Log.d("GEO", "Geofences added " + strings);
for (String geofenceId : strings)
Toast.makeText(this, "Geofences added: " + geofenceId, Toast.LENGTH_SHORT).show();
mLocationClient.disconnect();
stopSelf();
} else {
Log.e("GEO", "Error while adding geofence: " + strings);
}
}
@Override
public void onRemoveGeofencesByRequestIdsResult(int i, String[] strings) {
if (LocationStatusCodes.SUCCESS == i) {
Log.d("GEO", "Geofences removed" + strings);
mLocationClient.disconnect();
stopSelf();
} else {
Log.e("GEO", "Error while removing geofence: " + strings);
}
}
Последняя часть приложения – это IntentService, который запускается при пересечении границы геозоны пользователем устройства. Все действия выполняются в onHandleIntent:
@Override
protected void onHandleIntent(Intent intent) {
if (LocationClient.hasError(intent)) {
Log.e(TRANSITION_INTENT_SERVICE, "Location Services error: " + LocationClient.getErrorCode(intent));
return;
}
int transitionType = LocationClient.getGeofenceTransition(intent);
List<Geofence> triggeredGeofences = LocationClient.getTriggeringGeofences(intent);
List<String> triggeredIds = new ArrayList<String>();
for (Geofence geofence : triggeredGeofences) {
Log.d("GEO", "onHandle:" + geofence.getRequestId());
processGeofence(geofence, transitionType);
triggeredIds.add(geofence.getRequestId());
}
if (transitionType == Geofence.GEOFENCE_TRANSITION_EXIT)
removeGeofences(triggeredIds);
}
Здесь у нас фигурируют в основном статические методы LocationClient. Сначала мы делаем проверку на наличие ошибок с помощью hasError. Затем получаем тип перемещения и список сработавших геозон с помощью getGeofenceTransition и getTriggeringGeofences соответственно. Вызываем обработку каждой геозоны и сохраняем её id. Ну и напоследок, удаляем геозоны в случае, если данное перемещение было выходом из геозоны.
Для удаления геозон мы опять создаём сервис, в который передаём тип операции (REMOVE) и список id на удаление:
private void removeGeofences(List<String> requestIds) {
Intent intent = new Intent(getApplicationContext(), GeofencingService.class);
String[] ids = new String[0];
intent.putExtra(GeofencingService.EXTRA_REQUEST_IDS, requestIds.toArray(ids));
intent.putExtra(GeofencingService.EXTRA_ACTION, GeofencingService.Action.REMOVE);
startService(intent);
}
Надеюсь пример получился понятным и интересным. Желаю всем хороших приложений!
Автор: HE4UCTb
Источник [10]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/android-development/53446
Ссылки в тексте:
[1] Location APIs: https://developer.android.com/google/play-services/location.html
[2] видео: http://www.youtube.com/watch?v=Bte_GHuxUGc
[3] презентация: http://commondatastorage.googleapis.com/io-2013/presentations/106 - Beyond the Blue Dot- New features in Android Location (1).pdf
[4] одно упоминание: http://habrahabr.ru/post/191290/
[5] LocationManager: http://developer.android.com/reference/android/location/LocationManager.html
[6] официальный пример: http://developer.android.com/shareables/training/GeofenceDetection.zip
[7] GooglePlayServicesUtil.isGooglePlayServicesAvailable: http://developer.android.com/reference/com/google/android/gms/common/GooglePlayServicesUtil.html#isGooglePlayServicesAvailable(android.content.Context)
[8] github: https://github.com/Ne4istb/AndroidGeofenceTest
[9] здесь: http://developer.android.com/google/play-services/setup.html
[10] Источник: http://habrahabr.ru/post/210162/
Нажмите здесь для печати.