The Android Developer Challenge is back! Submit your idea before December 2.

Cómo crear y supervisar el geovallado

El geovallado combina el conocimiento acerca de la ubicación actual del usuario con el conocimiento sobre la proximidad del usuario a ubicaciones que podrían interesarle. Para marcar una ubicación de interés, especifica su latitud y longitud. Para ajustar la proximidad de la ubicación, agrega un radio. La latitud, la longitud y el radio definen un geovallado, que crea un área circular, o valla, alrededor de la ubicación de interés.

Puedes tener varios geovallados activos (el límite es de 100 por usuario del dispositivo en todas las apps). Para cada geovallado, puedes pedirles a los Servicios de ubicación que te envíen los eventos de entrada y salida, o bien puedes especificar una duración en el área de geovallado para esperar, o permanecer, antes de activar un evento. Para limitar la duración de un geovallado, puedes especificar un vencimiento en milésimas de segundos. Una vez que vence el geovallado, los Servicios de ubicación lo quitan automáticamente.

En esta lección, te mostraremos cómo agregar y quitar geovallados y, luego, detectar transiciones de geovallado con un BroadcastReceiver.

Cómo configurar la supervisión de geovallado

El primer paso para solicitar la supervisión de geovallado consiste en obtener los permisos necesarios. Para usar el geovallado, tu app debe solicitar ACCESS_FINE_LOCATION. Si se orienta a Android 10 (API nivel 29) o versiones posteriores, también debe solicitar ACCESS_BACKGROUND_LOCATION.

Para pedir los permisos necesarios, agrégalos como elementos secundarios del elemento <manifest> en el manifiesto de tu 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"/>
    

Si deseas usar un BroadcastReceiver para detectar las transiciones de geovallado, agrega un elemento que especifique el nombre del servicio. Este debe ser un elemento secundario del elemento <application>:

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

Para acceder a las API de ubicación, debes crear una instancia del cliente de geovallado. Si deseas obtener información sobre como conectar tu cliente, consulta el código que aparece a continuación:

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

Cómo crear y agregar geovallados

Tu app necesita crear y agregar geovallados mediante la clase del constructor de la API de ubicación a fin de crear objetos de geovallado, y la clase de conveniencia para agregarlos. Además, a fin de administrar los intents enviados desde los Servicios de ubicación cuando se produce una transición de geovallado, puedes definir un PendingIntent como se muestra en esta sección.

Nota: En los dispositivos para un solo usuario, hay un límite de 100 geovallados por app. En el caso de los dispositivos para varios usuarios, el límite es de 100 geovallados por app por usuario del dispositivo.

Cómo crear objetos de geovallado

En primer lugar, usa Geofence.Builder para crear un geovallado, y configura el radio, la duración y los tipos de transición deseados. Por ejemplo, para completar un 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());
    

Este ejemplo extrae datos de un archivo de constantes. En la práctica, las apps podrían crear geovallados de manera dinámica en función de la ubicación del usuario.

Cómo especificar geovallados y activadores iniciales

El siguiente fragmento usa la clase GeofencingRequest y su clase GeofencingRequestBuilder anidada para especificar los geovallados que se deben supervisar a fin de configurar cómo se activan los eventos de geovallado relacionados.

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

En este ejemplo, se muestra el uso de dos activadores de geovallado. La transición GEOFENCE_TRANSITION_ENTER se activa cuando un dispositivo ingresa a un geovallado, mientras que GEOFENCE_TRANSITION_EXIT lo hace cuando un dispositivo sale de un geovallado. Si especificas INITIAL_TRIGGER_ENTER, los Servicios de ubicación entienden que deben activar GEOFENCE_TRANSITION_ENTER cuando el dispositivo ya está adentro del geovallado.

En muchos casos, conviene usar INITIAL_TRIGGER_DWELL, que solo activa eventos cuando el usuario se detiene en un geovallado durante un tiempo definido. Este enfoque puede ayudar a reducir el "spam de alertas" que se genera a partir de las grandes cantidades de notificaciones recibidas cuando un dispositivo entra a un geovallado y sale de él rápidamente. Otra estrategia para obtener mejores resultados consiste en definir un radio mínimo de 100 metros. De esta manera, se tiene en cuenta la precisión de la ubicación de las redes Wi-Fi típicas y también ayuda a reducir el consumo de batería del dispositivo.

Cómo definir un receptor de transmisión para las transiciones de geovallado

Un Intent enviado desde los Servicios de ubicación puede activar varias acciones en tu app, pero no debería iniciar una actividad ni un fragmento, ya que los componentes solo deberían quedar visibles como respuesta a la acción de un usuario. En muchos casos, un BroadcastReceiver es una manera efectiva de administrar una transición de geovallado. Un BroadcastReceiver recibe actualizaciones cuando ocurre un evento, como una transición de entrada a un geovallado o salida de él, y puede iniciar trabajo en segundo plano de ejecución prolongada.

En el siguiente fragmento se muestra cómo definir un PendingIntent que inicie un 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;
        }
    

Cómo agregar geovallados

Para agregar geovallados, usa el método GeofencingClient.addGeofences(). Proporciona el objeto GeofencingRequest y el PendingIntent. En el siguiente fragmento, se demuestra el procesamiento de los 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
                    // ...
                }
            });
    

Cómo administrar las transiciones de geovallado

Cuando los Servicios de ubicación detectan que el usuario entró a un geovallado o salió de él, envían el Intent en el PendingIntent que incluiste en la solicitud para agregar geovallados. Un receptor de transmisión como GeofenceBroadcastReceiver nota que se invocó el Intent y, luego, puede obtener el evento de geovallado, determinar el tipo de transiciones de geovallado y determinar cuál de los geovallados definidos se activó. El receptor de transmisión puede indicarle a una app que comience a realizar trabajo en segundo plano o, si lo desea, que envíe una notificación como resultado.

Nota: En Android 8.0 (API nivel 26) y versiones posteriores, si se ejecuta una app en segundo plano mientras supervisa un geovallado, entonces el dispositivo responde a los eventos de geovallado cada algunos minutos. Si deseas obtener información sobre cómo adaptar tu app a estos límites de respuesta, consulta Límites de ubicación en segundo plano.

En el siguiente fragmento, se muestra cómo definir un BroadcastReceiver que publique una notificación cuando se produzca una transición de geovallado. Cuando el usuario hace clic en la notificación, aparece la actividad principal de la app:

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

Después de detectar el evento de transición mediante el PendingIntent, el BroadcastReceiver obtiene el tipo de transición de geovallado y prueba si es uno de los eventos que la app usa para activar notificaciones, ya sea GEOFENCE_TRANSITION_ENTER o GEOFENCE_TRANSITION_EXIT en este caso. Luego, el servicio envía una notificación y registra los detalles de la transición.

Cómo detener la supervisión de geovallado

Detener la supervisión de geovallado cuando ya no sea necesaria puede ayudar a ahorrar batería y ciclos de CPU en el dispositivo. Puedes detener la supervisión de geovallado en la actividad principal utilizada para agregar y quitar geovallados. Cuando lo haces, estos se detienen inmediatamente. La API proporciona métodos para quitar geovallados, ya sea mediante ID de solicitud o borrando geovallados asociados con un PendingIntent determinado.

El siguiente fragmento quita los geovallados mediante PendingIntent, ya que detiene todas las notificaciones que se reciben cuando el dispositivo entra a geovallados agregados anteriormente o sale de ellos:

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

Puedes combinar el geovallado con otras funciones que reconocen la ubicación, como las actualizaciones de ubicación periódicas. Para obtener más información, consulta las otras lecciones de esta clase.

Cómo usar prácticas recomendadas para el geovallado

En esta sección, se describen recomendaciones que te permitirán usar el geovallado con las API de ubicación de Android.

Cómo reducir el consumo de energía

Puedes usar las siguientes técnicas a fin de optimizar el consumo de energía en las apps que usan el geovallado:

  • Define la capacidad de respuesta de las notificaciones en un valor superior. De esta manera, podrás reducir el consumo de energía si aumentas la latencia de las alertas de geovallado. Por ejemplo, si defines un valor de capacidad de respuesta de cinco minutos, tu app solo verificará una alerta de entrada o salida cada cinco minutos. Definir valores inferiores no necesariamente quiere decir que los usuarios recibirán notificaciones en ese período de tiempo (por ejemplo, si defines un valor de 5 segundos, podría ser necesario un poco más de tiempo para recibir la alerta).

  • Usa un radio de geovallado más amplio para las ubicaciones en las que un usuario pasa una cantidad significativa de tiempo, como su casa o el trabajo. Aunque un radio más amplio no reduce el consumo de energía directamente, sí reduce la frecuencia con la que una app verifica la entrada o la salida, lo que reduce el consumo de energía general.

Cómo elegir el radio óptimo para tu geovallado

Para obtener mejores resultados, debes definir el radio mínimo del geovallado entre 100 y 150 metros. Cuando hay una conexión Wi-Fi disponible, por lo general, la precisión de la ubicación es de 20 a 50 metros. Cuando está disponible la ubicación en interiores, el rango de precisión puede ser de apenas 5 metros. A menos que sepas que la ubicación en interiores está disponible adentro del geovallado, debes suponer que la precisión de la ubicación de Wi-Fi es de alrededor de 50 metros.

Cuando no está disponible la ubicación de Wi-Fi (por ejemplo, cuando conduces en áreas rurales), disminuye la precisión. El rango de precisión puede ser de varios cientos de metros a varios kilómetros. En casos como este, debes crear geovallados con un radio más amplio.

Cómo usar el tipo de transición de permanencia para reducir el spam de alertas

Si recibes una cantidad considerable de alertas mientras conduces durante algunos instantes por un geovallado, la manera más efectiva de reducirlas consiste en usar un tipo de transición de GEOFENCE_TRANSITION_DWELL en lugar de GEOFENCE_TRANSITION_ENTER. De esta manera, solo se envía la alerta de permanencia cuando el usuario se detiene en un geovallado durante un período de tiempo determinado. Para elegir la duración, define un retraso de merodeo.

Cómo volver a registrar geovallados solo cuando sea necesario

Los geovallados registrados se guardan en el proceso com.google.process.location, que le pertenece al paquete com.google.android.gms. Como el sistema restaura los geovallados después de los siguientes eventos, la app no necesita ninguna acción de tu parte para administrarlos:

  • Se actualizan los Servicios de Google Play.
  • El sistema finaliza los Servicios de Google Play y los reinicia debido a una restricción de recursos.
  • Falla el proceso de ubicación.

La app debe volver a registrar geovallados si todavía son necesarios después de los siguientes eventos, ya que el sistema no puede recuperar los geovallados en los siguientes casos:

  • Se reinicia el dispositivo. La app debería detectar que se completó el inicio del dispositivo y, luego, volver a registrar los geovallados requeridos.
  • Se desinstala y se vuelve a instalar la app.
  • Se borran los datos de la app.
  • Se borran los datos de los Servicios de Google Play.
  • La app recibió una alerta de GEOFENCE_NOT_AVAILABLE. Por lo general, esto sucede cuando se inhabilita el NLP (proveedor de ubicación de red) de Android.

Cómo solucionar el evento de entrada al geovallado

Si no se activan los geovallados cuando el dispositivo entra a uno (no se activa la alerta GEOFENCE_TRANSITION_ENTER), primero asegúrate de que estén registrados de manera correcta como se describe en esta guía.

Estos son algunos de los motivos por los que posiblemente no funcionen las alertas según lo esperado:

  • No está disponible la ubicación precisa dentro de tu geovallado o bien es demasiado pequeño. En la mayoría de los dispositivos, el servicio de geovallado solo usa la ubicación de la red para activar el geovallado. El servicio utiliza este enfoque debido a que la ubicación de la red consume mucha menos energía, se necesita menos tiempo para obtener ubicaciones discretas y, lo que es más importante, está disponible en interiores.
  • La conexión Wi-Fi está desactivado en el dispositivo. Tener la red Wi-Fi activada puede mejorar significativamente la precisión de la ubicación. Por lo tanto, si la red está desactivada, es posible que tu aplicación nunca reciba alertas de geovallado en función de varias opciones de configuración, como el radio del geovallado, el modelo del dispositivo o la versión de Android. A partir de Android 4.3 (API nivel 18), agregamos la capacidad de "modo de solo búsqueda de Wi-Fi", que les permite a los usuarios inhabilitar la conexión Wi-Fi sin dejar de obtener una buena ubicación de la red. Se recomienda proporcionarle al usuario una combinación de teclas para habilitar la red Wi-Fi o el modo de solo búsqueda de Wi-Fi si ambos están inhabilitados. Usa SettingsClient para asegurarte de que la configuración del sistema del dispositivo sea la correcta para una detección óptima de la ubicación.

    Nota: Si tu app se orienta a Android 10 (API nivel 29) o versiones posteriores, no podrás llamar a WifiManager.setEnabled() de forma directa, a menos que tu app sea una app del sistema o un controlador de política de dispositivo (DPC). Usa el panel de configuración en su lugar.

  • No hay conectividad de red confiable dentro del geovallado. Si no hay una conexión de datos confiable, es posible que no se generen alertas. Esto se debe a que el servicio de geovallado depende del proveedor de ubicación de red que, a su vez, requiere una conexión de datos.
  • Las alertas pueden llegar tarde. El servicio de geovallado no consulta la ubicación de manera continua. Por lo tanto, puedes experimentar latencia cuando recibes las alertas. Por lo general, la latencia es de menos de 2 minutos, o incluso menos cuando el dispositivo estuvo en movimiento. Si los límites de la ubicación en segundo plano están activados, la latencia es de 2 a 3 minutos en promedio. Si el dispositivo estuvo inmóvil durante un período de tiempo considerable, la latencia podría aumentar (hasta 6 minutos).