Создание и мониторинг геозон

Геозона сочетает в себе информацию о текущем местоположении пользователя и информацию о его близости к интересующим объектам. Чтобы отметить интересующее вас место, укажите его широту и долготу. Чтобы настроить близость к этому месту, добавьте радиус. Широта, долгота и радиус определяют геозону, создавая круговую область или ограждение вокруг интересующего вас места.

Вы можете создать несколько активных геозон, с ограничением в 100 на приложение для каждого пользователя устройства. Для каждой геозоны вы можете настроить службы геолокации на отправку событий входа и выхода или указать период ожидания (или задержку ) в пределах геозоны перед срабатыванием события. Вы можете ограничить длительность любой геозоны, указав время истечения в миллисекундах. После истечения срока действия геозоны службы геолокации автоматически удаляют её.

В этом уроке показано, как добавлять и удалять геозоны, а затем прослушивать переходы геозон с помощью BroadcastReceiver .

Примечание: на устройствах Wear API геозонирования неэффективно расходуют энергию. Мы не рекомендуем использовать эти API на Wear. Подробнее см. в разделе «Экономия энергии и заряда аккумулятора» .

Настройка мониторинга геозоны

Первый шаг при запросе мониторинга геозоны — запрос необходимых разрешений. Для использования геозоны ваше приложение должно запросить следующее:

Более подробную информацию см. в руководстве о том, как запросить разрешение на определение местоположения .

Если вы хотите использовать BroadcastReceiver для прослушивания переходов геозон, добавьте элемент с именем сервиса. Этот элемент должен быть дочерним по отношению к элементу <application> :

<application
   android:allowBackup="true">
   ...
   <receiver android:name=".GeofenceBroadcastReceiver"/>
<application/>

Для доступа к API определения местоположения необходимо создать экземпляр клиента Geofencing. Чтобы узнать, как подключить клиент:

Котлин

lateinit var geofencingClient: GeofencingClient

override fun onCreate(savedInstanceState: Bundle?) {
    // ...
    geofencingClient = LocationServices.getGeofencingClient(this)
}

Ява

private GeofencingClient geofencingClient;

@Override
public void onCreate(Bundle savedInstanceState) {
    // ...
    geofencingClient = LocationServices.getGeofencingClient(this);
}

Создание и добавление геозон

Вашему приложению необходимо создавать и добавлять геозоны, используя класс-конструктор API определения местоположения для создания объектов геозон и удобный класс для их добавления. Кроме того, для обработки намерений, отправляемых службами определения местоположения при переходе через геозоны, вы можете определить PendingIntent , как показано в этом разделе.

Примечание: На однопользовательских устройствах ограничение составляет 100 геозон на приложение. На многопользовательских устройствах ограничение составляет 100 геозон на приложение на одного пользователя устройства.

Создание объектов геозоны

Сначала создайте геозону с помощью Geofence.Builder , задав желаемый радиус, длительность и типы переходов. Например, чтобы заполнить объект списка:

Котлин

geofenceList.add(Geofence.Builder()
        // Set the request ID of the geofence. This is a string to identify this
        // geofence.
        .setRequestId(entry.key)

        // Set the circular region of this geofence.
        .setCircularRegion(
                entry.value.latitude,
                entry.value.longitude,
                Constants.GEOFENCE_RADIUS_IN_METERS
        )

        // Set the expiration duration of the geofence. This geofence gets automatically
        // removed after this period of time.
        .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS)

        // Set the transition types of interest. Alerts are only generated for these
        // transition. We track entry and exit transitions in this sample.
        .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)

        // Create the geofence.
        .build())

Ява

geofenceList.add(new Geofence.Builder()
    // Set the request ID of the geofence. This is a string to identify this
    // geofence.
    .setRequestId(entry.getKey())

    .setCircularRegion(
            entry.getValue().latitude,
            entry.getValue().longitude,
            Constants.GEOFENCE_RADIUS_IN_METERS
    )
    .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS)
    .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
            Geofence.GEOFENCE_TRANSITION_EXIT)
    .build());

В этом примере данные извлекаются из файла констант. На практике приложения могут динамически создавать геозоны на основе местоположения пользователя.

Укажите геозоны и начальные триггеры

В следующем фрагменте кода класс GeofencingRequest и его вложенный класс GeofencingRequestBuilder используются для указания геозон, которые нужно отслеживать, и для настройки способа запуска связанных событий геозон:

Котлин

private fun getGeofencingRequest(): GeofencingRequest {
    return GeofencingRequest.Builder().apply {
        setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
        addGeofences(geofenceList)
    }.build()
}

Ява

private GeofencingRequest getGeofencingRequest() {
    GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
    builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
    builder.addGeofences(geofenceList);
    return builder.build();
}

В этом примере показано использование двух триггеров геозон. Переход GEOFENCE_TRANSITION_ENTER срабатывает, когда устройство входит в геозону, а переход GEOFENCE_TRANSITION_EXIT — когда устройство выходит из геозоны. Указание INITIAL_TRIGGER_ENTER сообщает службам определения местоположения, что GEOFENCE_TRANSITION_ENTER должен срабатывать, если устройство уже находится внутри геозоны.

Во многих случаях предпочтительнее использовать INITIAL_TRIGGER_DWELL , который активирует события только тогда, когда пользователь останавливается на определённое время в пределах геозоны. Такой подход поможет сократить количество «спамовых оповещений», возникающих из-за большого количества уведомлений о кратковременном входе устройства в геозону и выходе из неё. Другой способ добиться наилучших результатов от геозон — установить минимальный радиус 100 метров. Это помогает учесть точность определения местоположения в типичных сетях Wi-Fi, а также снизить энергопотребление устройства.

Определить приемник вещания для переходов через геозоны

Объект Intent , отправленный службами геолокации, может инициировать различные действия в вашем приложении, но не следует запускать его для запуска активности или фрагмента, поскольку компоненты должны становиться видимыми только в ответ на действие пользователя. Во многих случаях BroadcastReceiver — хороший способ обработки перехода через геозону. BroadcastReceiver получает обновления при возникновении события, например, при входе в геозону или выходе из неё, и может запускать длительную фоновую работу.

В следующем фрагменте показано, как определить PendingIntent , который запускает BroadcastReceiver :

Котлин

class MainActivity : AppCompatActivity() {

    // ...

    private val geofencePendingIntent: PendingIntent by lazy {
        val intent = Intent(this, GeofenceBroadcastReceiver::class.java)
        // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
        // addGeofences() and removeGeofences().
        PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
    }
}

Ява

public class MainActivity extends AppCompatActivity {

    // ...

    private PendingIntent getGeofencePendingIntent() {
        // Reuse the PendingIntent if we already have it.
        if (geofencePendingIntent != null) {
            return geofencePendingIntent;
        }
        Intent intent = new Intent(this, GeofenceBroadcastReceiver.class);
        // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when
        // calling addGeofences() and removeGeofences().
        geofencePendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.
                FLAG_UPDATE_CURRENT);
        return geofencePendingIntent;
    }

Добавить геозоны

Для добавления геозон используйте метод GeofencingClient.addGeofences() . Предоставьте объект GeofencingRequest и PendingIntent . В следующем фрагменте кода показана обработка результатов:

Котлин

geofencingClient?.addGeofences(getGeofencingRequest(), geofencePendingIntent)?.run {
    addOnSuccessListener {
        // Geofences added
        // ...
    }
    addOnFailureListener {
        // Failed to add geofences
        // ...
    }
}

Ява

geofencingClient.addGeofences(getGeofencingRequest(), getGeofencePendingIntent())
        .addOnSuccessListener(this, new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                // Geofences added
                // ...
            }
        })
        .addOnFailureListener(this, new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Failed to add geofences
                // ...
            }
        });

Управление переходами геозон

Когда службы определения местоположения обнаруживают, что пользователь вошел в геозону или вышел из нее, они отправляют Intent содержащееся в PendingIntent , который вы включили в запрос на добавление геозон. Приемник широковещательных сообщений, такой как GeofenceBroadcastReceiver обнаруживает вызов Intent и может получить событие геозоны из намерения, определить тип перехода через геозону и определить, какая из заданных геозон была срабатывает. Приемник широковещательных сообщений может дать команду приложению начать выполнение фоновой работы или, при необходимости, отправить уведомление в качестве выходного сигнала.

Примечание: На Android 8.0 (уровень API 26) и выше, если приложение работает в фоновом режиме во время мониторинга геозоны, устройство реагирует на события геозоны каждые пару минут. Чтобы узнать, как адаптировать приложение к этим ограничениям, см. раздел Ограничения фонового определения местоположения .

В следующем фрагменте показано, как определить BroadcastReceiver , который отправляет уведомление при переходе через геозону. Когда пользователь нажимает на уведомление, отображается основная активность приложения:

Котлин

class GeofenceBroadcastReceiver : BroadcastReceiver() {
    // ...
    override fun onReceive(context: Context?, intent: Intent?) {
        val geofencingEvent = GeofencingEvent.fromIntent(intent)
        if (geofencingEvent.hasError()) {
            val errorMessage = GeofenceStatusCodes
                    .getStatusCodeString(geofencingEvent.errorCode)
            Log.e(TAG, errorMessage)
            return
        }

        // Get the transition type.
        val geofenceTransition = geofencingEvent.geofenceTransition

        // Test that the reported transition was of interest.
        if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
                geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {

            // Get the geofences that were triggered. A single event can trigger
            // multiple geofences.
            val triggeringGeofences = geofencingEvent.triggeringGeofences

            // Get the transition details as a String.
            val geofenceTransitionDetails = getGeofenceTransitionDetails(
                    this,
                    geofenceTransition,
                    triggeringGeofences
            )

            // Send notification and log the transition details.
            sendNotification(geofenceTransitionDetails)
            Log.i(TAG, geofenceTransitionDetails)
        } else {
            // Log the error.
            Log.e(TAG, getString(R.string.geofence_transition_invalid_type,
                    geofenceTransition))
        }
    }
}

Ява

public class GeofenceBroadcastReceiver extends BroadcastReceiver {
    // ...
    protected void onReceive(Context context, Intent intent) {
        GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
        if (geofencingEvent.hasError()) {
            String errorMessage = GeofenceStatusCodes
                    .getStatusCodeString(geofencingEvent.getErrorCode());
            Log.e(TAG, errorMessage);
            return;
        }

        // Get the transition type.
        int geofenceTransition = geofencingEvent.getGeofenceTransition();

        // Test that the reported transition was of interest.
        if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
                geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {

            // Get the geofences that were triggered. A single event can trigger
            // multiple geofences.
            List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();

            // Get the transition details as a String.
            String geofenceTransitionDetails = getGeofenceTransitionDetails(
                    this,
                    geofenceTransition,
                    triggeringGeofences
            );

            // Send notification and log the transition details.
            sendNotification(geofenceTransitionDetails);
            Log.i(TAG, geofenceTransitionDetails);
        } else {
            // Log the error.
            Log.e(TAG, getString(R.string.geofence_transition_invalid_type,
                    geofenceTransition));
        }
    }
}

После обнаружения события перехода через PendingIntent , BroadcastReceiver получает тип перехода геозоны и проверяет, является ли это одним из событий, которые приложение использует для активации уведомлений — в данном случае GEOFENCE_TRANSITION_ENTER или GEOFENCE_TRANSITION_EXIT . Затем служба отправляет уведомление и регистрирует сведения о переходе.

Остановить мониторинг геозоны

Остановка мониторинга геозон, когда он больше не нужен или нежелателен, может помочь сэкономить заряд батареи и время процессора устройства. Мониторинг геозон можно остановить в основном действии, используемом для добавления и удаления геозон; удаление геозоны немедленно останавливает его. API предоставляет методы для удаления геозон либо по идентификаторам запросов, либо путём удаления геозон, связанных с заданным PendingIntent .

Следующий фрагмент кода удаляет геозоны с помощью PendingIntent , останавливая все дальнейшие уведомления, когда устройство входит в ранее добавленные геозоны или выходит из них:

Котлин

geofencingClient?.removeGeofences(geofencePendingIntent)?.run {
    addOnSuccessListener {
        // Geofences removed
        // ...
    }
    addOnFailureListener {
        // Failed to remove geofences
        // ...
    }
}

Ява

geofencingClient.removeGeofences(getGeofencePendingIntent())
        .addOnSuccessListener(this, new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                // Geofences removed
                // ...
            }
        })
        .addOnFailureListener(this, new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Failed to remove geofences
                // ...
            }
        });

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

Используйте лучшие практики геозонирования

В этом разделе изложены рекомендации по использованию геозон с API определения местоположения для Android.

Уменьшите потребление энергии

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

  • Set the notification responsiveness to a higher value. Doing so improves power consumption by increasing the latency of geofence alerts. For example, if you set a responsiveness value of five minutes your app only checks for an entrance or exit alert once every five minutes. Setting lower values doesn't necessarily mean that users are notified within that time period (for example, if you set a value of 5 seconds it may take a bit longer than that to receive the alert).

  • Используйте больший радиус геозоны для мест, где пользователь проводит значительное время, например, дома или на работе. Хотя больший радиус не снижает энергопотребление напрямую, он снижает частоту проверок входа или выхода приложением, что эффективно снижает общее энергопотребление.

Выберите оптимальный радиус для вашей геозоны

Для достижения наилучших результатов минимальный радиус геозоны следует установить в диапазоне 100–150 метров. При наличии Wi-Fi точность определения местоположения обычно составляет 20–50 метров. При наличии геозоны в помещении точность может составлять всего 5 метров. Если вы не уверены, что геозона доступна для определения местоположения в помещении, предполагайте, что точность определения местоположения по Wi-Fi составляет около 50 метров.

Когда Wi-Fi недоступен (например, при движении по сельской местности), точность определения местоположения снижается. Диапазон точности может составлять от нескольких сотен метров до нескольких километров. В таких случаях следует создавать геозоны с большим радиусом.

Объясните пользователям, почему ваше приложение использует геозонирование.

Поскольку ваше приложение получает доступ к данным о местоположении в фоновом режиме при использовании геозон, подумайте, какую пользу оно приносит пользователям. Чётко объясните им, зачем вашему приложению нужен этот доступ, чтобы повысить уровень понимания и прозрачности для пользователей.

Дополнительную информацию о передовых практиках в отношении доступа к местоположению, включая геозонирование, можно найти на странице передовых практик в отношении конфиденциальности .

Используйте тип перехода с задержкой, чтобы уменьшить количество спам-оповещений

Если вы получаете большое количество оповещений при кратковременном пересечении геозоны, лучший способ уменьшить их количество — использовать тип перехода GEOFENCE_TRANSITION_DWELL вместо GEOFENCE_TRANSITION_ENTER . В этом случае оповещение о пребывании будет отправляться только тогда, когда пользователь останавливается внутри геозоны на заданный период времени. Вы можете выбрать длительность, установив задержку праздношатания .

Перерегистрируйте геозоны только при необходимости.

Зарегистрированные геозоны хранятся в процессе com.google.process.location , принадлежащем пакету com.google.android.gms . Приложению не требуется выполнять какие-либо действия для обработки следующих событий, поскольку система восстанавливает геозоны после них:

  • Сервисы Google Play обновлены.
  • Службы Google Play останавливаются и перезапускаются системой из-за ограничения ресурсов.
  • Процесс определения местоположения дает сбой.

Приложение должно повторно зарегистрировать геозоны, если они все еще нужны после следующих событий, поскольку система не может восстановить геозоны в следующих случаях:

  • Устройство перезагружено. Приложение должно дождаться завершения загрузки устройства и повторно зарегистрировать необходимые геозоны.
  • Приложение удаляется и переустанавливается.
  • Данные приложения очищены.
  • Данные сервисов Google Play очищены.
  • Приложение получило оповещение GEOFENCE_NOT_AVAILABLE . Обычно это происходит после отключения NLP (поставщика сетевого местоположения Android).

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

Если геозоны не срабатывают, когда устройство входит в геозону (оповещение GEOFENCE_TRANSITION_ENTER не срабатывает), сначала убедитесь, что ваши геозоны зарегистрированы правильно, как описано в этом руководстве.

Вот некоторые возможные причины, по которым оповещения не работают должным образом:

  • Точное местоположение внутри вашей геозоны недоступно или геозона слишком мала. На большинстве устройств служба геозон использует только сетевое местоположение для срабатывания геозоны. Сервис использует этот подход, поскольку сетевое местоположение потребляет гораздо меньше энергии, определение местоположения занимает меньше времени и, что самое важное, доступно внутри помещений.
  • Wi-Fi на устройстве отключен. Включение Wi-Fi может значительно повысить точность определения местоположения. Поэтому при отключении Wi-Fi ваше приложение может не получать оповещения о геозоне в зависимости от ряда настроек, включая радиус геозоны, модель устройства и версию Android. Начиная с Android 4.3 (уровень API 18), мы добавили возможность «Режима сканирования только Wi-Fi», которая позволяет пользователям отключить Wi-Fi, но при этом сохранять корректное местоположение в сети. Рекомендуется предлагать пользователю включить режим сканирования только Wi-Fi или Wi-Fi, если отключены оба режима. Используйте SettingsClient , чтобы убедиться, что системные настройки устройства настроены правильно для оптимального определения местоположения.

    Примечание: Если ваше приложение предназначено для Android 10 (уровень API 29) или выше, вы не сможете вызвать WifiManager.setEnabled() напрямую, если только ваше приложение не является системным приложением или контроллером политики устройства (DPC) . Вместо этого используйте панель настроек .

  • Внутри вашей геозоны отсутствует надёжное сетевое подключение. При отсутствии надёжного подключения к сети оповещения могут не генерироваться. Это связано с тем, что служба геозоны зависит от поставщика услуг определения местоположения, которому, в свою очередь, требуется подключение к сети.
  • Оповещения могут приходить с задержкой. Служба геозоны не запрашивает местоположение постоянно, поэтому при получении оповещений возможна небольшая задержка. Обычно задержка составляет менее 2 минут, а при движении устройства — ещё меньше. Если действуют ограничения на определение местоположения в фоновом режиме , задержка составляет в среднем около 2–3 минут. Если устройство находится в неподвижном состоянии длительное время, задержка может увеличиться (до 6 минут).

Дополнительные ресурсы

Чтобы узнать больше о Geofencing, ознакомьтесь со следующими материалами:

Образцы

Пример приложения для создания и мониторинга геозон.