Criar e monitorar fronteiras geográficas virtuais

A fronteira geográfica virtual combina o conhecimento da localização atual do usuário com o da proximidade do usuário em relação a locais que podem ser interessantes para ele. Para marcar um local de interesse, especifique a latitude e longitude. Para ajustar a proximidade do local, adicione um raio. A latitude, a longitude e o raio definem uma fronteira geográfica virtual, criando uma área circular, ou fronteira, ao redor do local de interesse.

Você pode ter várias fronteiras geográficas virtuais ativas, com um limite de 100 por usuário do dispositivo em todos os apps. Para cada uma delas, você pode solicitar que os Serviços de localização enviem eventos de entrada e saída, ou especificar uma duração na área da fronteira geográfica virtual para aguardar ou permanecer antes de acionar um evento. Você pode limitar a duração de qualquer fronteira geográfica virtual especificando uma duração de expiração em milissegundos. Depois que a fronteira geográfica expira, os Serviços de localização a removem automaticamente.

Esta lição mostra como adicionar e remover fronteiras geográficas virtuais e, em seguida, detectar transições entre fronteiras usando um BroadcastReceiver.

Configurar o monitoramento de fronteiras geográficas virtuais

A primeira etapa para solicitar o monitoramento da fronteira geográfica virtual é solicitar a permissão necessária. Para usar a fronteira geográfica virtual, seu app precisa solicitar ACCESS_FINE_LOCATION. Se seu app for voltado ao Android 10 (API de nível 29) ou versões posteriores, ele também precisará solicitar ACCESS_BACKGROUND_LOCATION.

Para solicitar as permissões necessárias, adicione-as como elementos filhos do elemento <manifest> no manifesto do seu app:

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

    <!-- Required if your app targets Android 10 (API level 29) or higher -->
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
    

Caso queira usar um BroadcastReceiver para detectar transições de fronteira geográfica virtual, adicione um elemento que especifique o nome do serviço. Esse elemento precisa ser filho do elemento <application>:

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

Para acessar as APIs Location, crie uma instância do cliente de fronteira geográfica virtual. Para saber como conectar seu cliente:

Kotlin

    lateinit var geofencingClient: GeofencingClient

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

Java

    private GeofencingClient geofencingClient;

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

Criar e adicionar fronteiras geográficas virtuais

Seu app precisa criar e adicionar fronteiras geográficas virtuais usando a classe de construção da API Location para criar objetos de fronteira geográfica e a classe de conveniência para adicioná-los. Além disso, para gerenciar os intents enviados a partir dos Serviços de localização quando as transições de fronteira geográfica virtual ocorrerem, defina um PendingIntent conforme mostrado nesta seção.

Observação: em dispositivos de usuário único, há um limite de cem fronteiras por app. Para dispositivos multiusuários, o limite é de cem fronteiras por app por usuário.

Criar objetos de fronteira geográfica virtual

Primeiro, use Geofence.Builder para criar uma fronteira geográfica virtual, configurando o raio, a duração e os tipos de transição desejados para ela. Por exemplo, para preencher um objeto de lista:

Kotlin

    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())
    

Java

    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());
    

Esse exemplo extrai dados de um arquivo de constantes. Na prática, os apps podem criar fronteiras geográficas virtuais dinamicamente com base na localização do usuário.

Especificar fronteiras geográficas virtuais e acionadores iniciais

O snippet a seguir usa a classe GeofencingRequest e a classe aninhada GeofencingRequestBuilder para especificar as fronteiras geográficas virtuais a serem monitoradas e definir como eventos de fronteira geográfica relacionados são acionados:

Kotlin

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

Java

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

Esse exemplo mostra o uso de dois acionadores de fronteiras geográficas virtuais. A transição GEOFENCE_TRANSITION_ENTER é acionada quando um dispositivo entra em uma fronteira, e a transição GEOFENCE_TRANSITION_EXIT é acionada quando ele sai de uma. A especificação de INITIAL_TRIGGER_ENTER informa aos Serviços de localização que GEOFENCE_TRANSITION_ENTER precisa ser acionado se o dispositivo já estiver dentro da fronteira geográfica virtual.

Em muitos casos, pode ser preferível usar INITIAL_TRIGGER_DWELL, que aciona eventos somente quando o usuário permanece por um período definido dentro de uma fronteira geográfica virtual. Essa abordagem pode ajudar a reduzir o "spam de alertas" resultante de um grande número de notificações quando um dispositivo entra e sai brevemente das fronteira geográficas virtuais. Outra estratégia para ter os melhores resultados com suas fronteiras é definir um raio mínimo de 100 metros. Isso ajuda na precisão da localização das redes Wi-Fi típicas e também reduz o consumo de energia do dispositivo.

Definir um broadcast receiver para transições de fronteira geográfica virtual

Um Intent enviado a partir dos Serviços de localização pode acionar várias ações no seu app, mas é importante que você não faça com que ele inicie uma atividade ou fragmento, já que componentes só ficarão visíveis em resposta a uma ação do usuário. Em muitos casos, um BroadcastReceiver é uma boa forma de gerenciar uma transição de fronteira geográfica virtual. Um BroadcastReceiver recebe atualizações quando um evento ocorre, como em uma transição de entrada ou saída de uma fronteira geográfica virtual, e pode iniciar um trabalho em segundo plano de longa execução.

O snippet a seguir mostra como definir um PendingIntent que inicia um BroadcastReceiver:

Kotlin

    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)
        }
    }
    

Java

    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;
        }
    

Adicionar fronteiras geográficas virtuais

Para adicionar fronteiras geográficas virtuais, use o método GeofencingClient.addGeofences(). Forneça o objeto GeofencingRequest e o PendingIntent. O snippet a seguir demonstra o processamento dos resultados:

Kotlin

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

Java

    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
                    // ...
                }
            });
    

Gerenciar transições de fronteira geográfica virtual

Quando os Serviços de localização detectam que o usuário entrou ou saiu de uma fronteira geográfica virtual, eles enviam o Intent contido no PendingIntent que você incluiu na solicitação para adicionar fronteiras. Um broadcast receiver como GeofenceBroadcastReceiver detecta que o Intent foi invocado e pode então receber o evento de fronteira geográfica virtual do intent, determinar o tipo de transição de fronteira e determinar qual das fronteiras definidas foi acionada. O broadcast receiver pode direcionar um app para começar a executar o trabalho em segundo plano ou, se desejado, enviar uma notificação como saída.

Observação: no Android 8.0 (API de nível 26) e versões posteriores, se um app estiver sendo executado em segundo plano durante o monitoramento de uma fronteira geográfica virtual, o dispositivo responderá a eventos de fronteira a cada dois minutos. Para saber como adaptar seu app a esses limites de resposta, consulte Limites da localização em segundo plano.

O snippet a seguir mostra como definir um BroadcastReceiver que publica uma notificação quando ocorre uma transição de fronteira geográfica virtual. Quando o usuário clica na notificação, a atividade principal do app é exibida:

Kotlin

    class GeofenceBroadcastReceiver : BroadcastReceiver() {
        // ...
        override fun onReceive(context: Context?, intent: Intent?) {
            val geofencingEvent = GeofencingEvent.fromIntent(intent)
            if (geofencingEvent.hasError()) {
                val errorMessage = GeofenceErrorMessages.getErrorString(this,
                        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))
            }
        }
    }
    

Java

    public class GeofenceBroadcastReceiver extends BroadcastReceiver {
        // ...
        protected void onReceive(Context context, Intent intent) {
            GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
            if (geofencingEvent.hasError()) {
                String errorMessage = GeofenceErrorMessages.getErrorString(this,
                        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));
            }
        }
    

Depois de detectar o evento de transição pelo PendingIntent, o BroadcastReceiver recebe o tipo de transição de fronteira geográfica virtual e testa se é um dos eventos que o app usa para acionar notificações, neste caso, tanto GEOFENCE_TRANSITION_ENTER quanto GEOFENCE_TRANSITION_EXIT. Então, o serviço envia uma notificação e registra os detalhes da transição.

Parar o monitoramento da fronteira geográfica virtual

A interrupção do monitoramento de fronteira geográfica virtual quando ele não é mais necessário pode ajudar a economizar bateria e os ciclos de CPU do dispositivo. É possível parar o monitoramento de fronteira geográfica virtual na atividade principal usada para adicionar e remover fronteiras. A remoção de uma fronteira interrompe o monitoramento imediatamente. A API oferece métodos para remover fronteiras geográficas virtuais com IDs de solicitação ou por meio da remoção de fronteiras associadas com um determinado PendingIntent.

O snippet a seguir remove fronteiras geográficas virtuais por PendingIntent, interrompendo todas as outras notificações quando o dispositivo entrar ou sair de fronteiras adicionadas anteriormente:

Kotlin

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

Java

    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
                    // ...
                }
            });
    

Combine a fronteira geográfica virtual com outros recursos de reconhecimento de local, como atualizações periódicas de localização. Para mais informações, consulte as outras lições desta aula.

Usar as práticas recomendadas para fronteiras geográficas virtuais

Esta seção descreve recomendações para o uso de fronteiras geográficas virtuais com as APIs Location para Android.

Reduzir o consumo de energia

Use as técnicas a seguir para otimizar o consumo de energia nos seus apps que usam fronteira geográfica virtual:

  • Defina a capacidade de resposta a notificações como um valor mais alto. Isso melhora o consumo de energia aumentando a latência dos alertas de fronteira geográfica virtual. Por exemplo, se você definir um valor de capacidade de resposta de cinco minutos, seu app só verificará alertas de entrada e saída a cada cinco minutos. Definir valores mais baixos não significa necessariamente que os usuários serão notificados naquele período (por exemplo, se você definir um valor de cinco segundos, poderá levar mais tempo que isso para receber um alerta).

  • Use um raio de fronteira geográfica virtual maior para locais onde o usuário passa uma quantidade significativa de tempo, como em casa ou no trabalho. Embora um raio maior não reduza diretamente o consumo de energia, ele reduz a frequência com que o app verifica a entrada ou a saída, reduzindo o consumo geral de forma eficaz.

Escolher o raio ideal para sua fronteira geográfica virtual

Para melhores resultados, é importante que o raio mínimo da fronteira geográfica virtual seja definido entre 100 e 150 metros. Quando o Wi-Fi está disponível, a precisão da localização geralmente fica entre 20 e 50 metros. Quando a localização interna está disponível, o alcance da precisão pode ser de até 5 metros. A menos que você saiba que a localização interna está disponível dentro da fronteira geográfica, presuma que a precisão da localização por Wi-Fi é de cerca de 50 metros.

Quando a localização por Wi-Fi não está disponível (por exemplo, quando você dirige em áreas rurais), a precisão da localização diminui. O alcance da precisão pode ser de centenas de metros a vários quilômetros. Em casos como esse, crie fronteiras geográficas virtuais usando um raio maior.

Usar o tipo de transição de permanência para reduzir o spam de alertas

Se você recebe um número grande de alertas ao passar rapidamente por uma fronteira geográfica virtual, a melhor maneira de reduzir os alertas é usar um tipo de transição GEOFENCE_TRANSITION_DWELL em vez de GEOFENCE_TRANSITION_ENTER. Dessa forma, o alerta de permanência é enviado apenas quando o usuário para dentro de uma fronteira geográfica virtual por um determinado período. Escolha a duração definindo um atraso de espera.

Registrar fronteiras geográficas virtuais novamente apenas quando solicitado

As fronteiras geográficas virtuais registradas são mantidas no processo com.google.process.location do pacote com.google.android.gms. O app não precisa fazer nada para lidar com os seguintes eventos, porque o sistema restaura as fronteiras geográficas virtuais depois deles:

  • Google Play Services passou por um upgrade.
  • Google Play Services foi desativado e reiniciado pelo sistema devido a restrições de recursos.
  • O processo de localização falhou.

O app precisa registrar novamente as fronteiras geográficas virtuais se elas ainda forem necessárias depois dos eventos a seguir, já que o sistema não pode recuperar as fronteiras nos seguintes casos:

  • Se o dispositivo for reinicializado. O app detectará a ação completa de inicialização do dispositivo e registrará novamente as fronteiras necessárias.
  • Se o app for desinstalado e reinstalado.
  • Se os dados do app forem apagados.
  • Se os dados do Google Play Services forem apagados.
  • Se o app receber um alerta GEOFENCE_NOT_AVAILABLE. Isso normalmente acontece depois que o Provedor de localização de rede (NLP, na sigla em inglês) do Android é desativado.

Solucionar problemas do evento de entrada da fronteira geográfica virtual

Se as fronteiras geográficas virtuais não estiverem sendo acionadas quando o dispositivo entrar em uma fronteira (o alerta GEOFENCE_TRANSITION_ENTER não é acionado), primeiro verifique se suas fronteiras estão registradas corretamente, conforme descrito neste guia.

Veja alguns possíveis motivos para que os alertas não funcionem conforme o esperado:

  • A localização exata não está disponível dentro da sua fronteira geográfica virtual ou sua fronteira é muito pequena. Na maioria dos dispositivos, o serviço de fronteira geográfica virtual usa apenas a localização da rede para acionar a fronteira. O serviço usa essa abordagem porque a localização da rede consome muito menos energia, leva menos tempo para ter localizações discretas e, o mais importante, está disponível em ambientes fechados.
  • O Wi-Fi está desativado no dispositivo. Ativar o Wi-Fi pode melhorar significativamente a precisão da localização. Portanto, se o Wi-Fi estiver desativado, seu aplicativo poderá nunca receber alertas de fronteira geográfica virtual, dependendo de várias configurações, incluindo o raio da fronteira, o modelo do dispositivo ou a versão do Android. A partir do Android 4.3 (API de nível 18), adicionamos o recurso “modo de somente busca por Wi-Fi”, que permite que os usuários desativem o Wi-Fi, mas continuem com uma localização de rede boa. É uma boa prática solicitar ao usuário que ele ative o Wi-Fi ou o "modo de somente busca por Wi-Fi" se os dois estiverem desativados, assim como fornecer um atalho para isso. Use SettingsClient para garantir que as configurações do sistema do dispositivo sejam configuradas adequadamente para ter uma detecção de local ideal.

    Observação: se seu app é voltado ao Android 10 (API de nível 29) ou versões posteriores, não chame WifiManager.setEnabled() diretamente, a não ser que o esse seja um app do sistema ou um controlador de política de dispositivo (DPC, na sigla em inglês). Em vez disso, use um painel de configurações.

  • Não há uma conectividade de rede confiável dentro da sua fronteira geográfica virtual. Se não houver uma conexão de dados confiável, os alertas poderão não ser gerados. Isso ocorre porque o serviço de fronteira geográfica virtual depende do provedor de localização de rede que, por sua vez, requer uma conexão de dados.
  • Os alertas podem chegar atrasados. O serviço de fronteira geográfica virtual não consulta continuamente a localização, então é possível que haja uma certa latência ao receber alertas. Geralmente, a latência é inferior a dois minutos, ou até menos quando o dispositivo está em movimento. Se os Limites da localização em segundo plano estiverem em vigor, a latência será de, em média, dois a três minutos. Se o dispositivo ficar parado por um período significativo, a latência poderá aumentar (até seis minutos).