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

Cómo recibir actualizaciones de ubicación periódicas

Si tu app puede hacer un seguimiento continuo de la ubicación, podrá ofrecer información más relevante al usuario. Por ejemplo, si tu app ayuda al usuario a encontrar la ruta mientras camina o conduce, o si realiza el seguimiento de la ubicación de elementos, es necesario obtener la ubicación del dispositivo a intervalos regulares. Además de la ubicación geográfica (latitud y longitud), es posible que quieras brindar al usuario más información, como el rumbo (dirección horizontal de viaje), la altitud o la velocidad del dispositivo. El objeto Location contiene esta información y datos adicionales que la app puede obtener del proveedor de ubicación combinada.

Si bien puedes obtener la ubicación de un dispositivo con getLastLocation(), como se muestra en la lección sobre cómo obtener la última ubicación conocida, un enfoque más directo consiste en solicitar actualizaciones periódicas desde el proveedor de ubicación combinada. En respuesta, la API actualiza la app de forma periódica con la mejor ubicación disponible, según los proveedores disponibles, como Wi-Fi y GPS (sistema de posicionamiento global). La precisión de la ubicación está determinada por los proveedores, los permisos de ubicación solicitados y las opciones que defines en la solicitud de ubicación.

En esta lección, se muestra cómo solicitar actualizaciones regulares de la ubicación de un dispositivo con el método requestLocationUpdates() en el proveedor de ubicación combinada.

Cómo declarar permisos

Las apps que usan los servicios de ubicación deben solicitar los permisos correspondientes. En la mayoría de los casos, puedes solicitar el permiso de ubicación aproximada y aún así obtener información de ubicación precisa de los proveedores disponibles.

En el siguiente fragmento, se muestra cómo solicitar el permiso de ubicación aproximada:

    <manifest ... >
      <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    </manifest>
    
Captura de pantalla del diálogo para el usuario
Figura 1: Este diálogo se muestra cuando la app solicita la ubicación con la opción para usar la ubicación de acceso mientras la app está en uso.

En un dispositivo que ejecuta Android 10 (API nivel 29) o una versión posterior, los usuarios ven el diálogo que se muestra en la Figura 1 para indicar que la app está solicitando permiso de acceso a la ubicación. Si el usuario permite que la app acceda a la ubicación del dispositivo desde este diálogo, tu app podrá acceder a la ubicación solo mientras el usuario interactúa con la app, no mientras se ejecuta en segundo plano. Puedes declarar un servicio en primer plano si permites que la app obtenga los detalles de la ubicación para continuar una acción iniciada por el usuario después de que el usuario haya enviado la app al segundo plano.

Nota: Aunque es posible solicitar acceso a la ubicación en segundo plano, si la app se ejecuta en Android 10 o una versión posterior, no se recomienda hacerlo.

Cómo conocer la ubicación más reciente

La ubicación conocida más reciente del dispositivo proporciona una base práctica de inicio y garantiza que la app tenga una ubicación conocida antes de comenzar con las actualizaciones de ubicación periódicas. La lección sobre cómo obtener la última ubicación conocida te muestra cómo realizar el proceso llamando a getLastLocation(). En los fragmentos de las siguientes secciones se asume que la app ya obtuvo la última ubicación conocida y la almacenó como un objeto Location en la variable global mCurrentLocation.

Cómo solicitar actualizaciones de ubicación

Antes de solicitar actualizaciones de ubicación, la app debe conectar los servicios de ubicación y realizar la solicitud correspondiente. En la lección sobre cómo cambiar la configuración de ubicación se muestra cómo hacerlo. Una vez realizada la solicitud de ubicación, puedes iniciar las actualizaciones regulares llamando a requestLocationUpdates().

Según el formato de la solicitud, el proveedor de ubicación combinada puede invocar el método de devolución de llamada LocationCallback.onLocationResult() y pasarlo a una lista de objetos Location, o bien emitir un PendingIntent que contenga la ubicación en los datos extendidos. La precisión y la frecuencia de las actualizaciones están afectadas por los permisos de ubicación solicitados y por las opciones que defines en el objeto de solicitud de ubicación.

En esta lección, se muestra cómo obtener la actualización con el enfoque de devolución de llamada LocationCallback. Llama a requestLocationUpdates() y pásalo a tu instancia del objeto LocationRequest y a una instancia de LocationCallback. Define un método de startLocationUpdates(), como se muestra en el siguiente código de ejemplo:

Kotlin

    override fun onResume() {
        super.onResume()
        if (requestingLocationUpdates) startLocationUpdates()
    }

    private fun startLocationUpdates() {
        fusedLocationClient.requestLocationUpdates(locationRequest,
                locationCallback,
                null /* Looper */)
    }
    

Java

    @Override
    protected void onResume() {
        super.onResume();
        if (requestingLocationUpdates) {
            startLocationUpdates();
        }
    }

    private void startLocationUpdates() {
        fusedLocationClient.requestLocationUpdates(locationRequest,
                locationCallback,
                null /* Looper */);
    }
    

Ten en cuenta que el fragmento de código anterior hace referencia a una marca booleana, requestingLocationUpdates, que se usa para hacer un seguimiento de si el usuario activó o no las actualizaciones de ubicación. Si los usuarios desactivan las actualizaciones de ubicación, puedes informarles sobre el requisito de ubicación de tu app. Para obtener más información sobre cómo conservar el valor de la marca booleana en diferentes instancias de la actividad, consulta Cómo guardar el estado de la actividad.

Cómo definir la devolución de llamada de las actualizaciones de ubicación

El proveedor de ubicación combinada invoca el método de devolución de llamada LocationCallback.onLocationResult(). El argumento entrante contiene un objeto Location de lista que, a su vez, contiene la latitud y la longitud de la ubicación. En el siguiente fragmento, se muestra cómo implementar la interfaz de LocationCallback y definir el método, luego cómo obtener la marca de tiempo de la actualización de ubicación y mostrar la latitud, la longitud y la marca de tiempo en la interfaz del usuario de la app:

Kotlin

    private lateinit var locationCallback: LocationCallback

    // ...

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult?) {
                locationResult ?: return
                for (location in locationResult.locations){
                    // Update UI with location data
                    // ...
                }
            }
        }
    }
    

Java

    private LocationCallback locationCallback;

    // ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...

        locationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(LocationResult locationResult) {
                if (locationResult == null) {
                    return;
                }
                for (Location location : locationResult.getLocations()) {
                    // Update UI with location data
                    // ...
                }
            };
        };
    }
    

Cómo solicitar acceso a la ubicación en segundo plano

Si la app apunta a Android 10 o una versión posterior, deberás declarar el permiso de ACCESS_BACKGROUND_LOCATION en el archivo de manifiesto de la app, y recibir el permiso del usuario para poder recibir actualizaciones de ubicación regulares mientras se ejecuta la app en segundo plano.

En el siguiente fragmento de código, se muestra cómo solicitar el acceso de ubicación en segundo plano en la app:

    <manifest ... >
      <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
      <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    </manifest>
    
Captura de pantalla del diálogo para el usuario
Figura 2: Diálogo que se muestra cuando la app solicita la ubicación con la opción de acceso en todo momento, incluso cuando la app se ejecuta en segundo plano

En un dispositivo que ejecuta Android 10 (API nivel 29) o una versión posterior, los usuarios ven el diálogo que se muestra en la Figura 2 para indicar que la app está solicitando permiso de ubicación y que, además, solicita acceso a la ubicación en todo momento, incluso cuando se ejecuta en segundo plano. Este diálogo incluye una opción para que los usuarios otorguen a la app acceso a la ubicación solo cuando se usa la app; si los usuarios seleccionan esta opción, la app no tiene acceso a la ubicación mientras se ejecuta en segundo plano. Si el flujo de trabajo de la app requiere acceso en todo momento a la información de ubicación, deberás informar al usuario sobre el requisito de acceso a ubicación mientras tu app está en segundo plano.

Precaución: Incluso si los usuarios inicialmente otorgan a tu app acceso a la información de ubicación mientras esta se ejecuta en segundo plano, podrán revocar este permiso más tarde en la configuración del sistema. Los usuarios pueden optar por otorgarle a tu app acceso a la información de ubicación solo cuando la usan, o bien no otorgarle acceso.

Por eso, cuando tu app inicie un servicio, verifica que el usuario aún le permita acceder a la información de la ubicación en segundo plano.

Recordatorio para el usuario sobre el acceso de ubicación en segundo plano

Captura de pantalla de la notificación del sistema
Figura 3: Notificación que le recuerda al usuario que otorgó a una app permiso de acceso a la ubicación del dispositivo "en todo momento"

El usuario puede optar por permitirle a la app que acceda a la ubicación del dispositivo "en todo momento". Cuando la app accede a la ubicación del dispositivo en segundo plano por primera vez, después de que el usuario elige esta opción, el sistema programa el envío de una notificación al usuario a fin de recordarle que le otorgó a tu app acceso a la ubicación del dispositivo en todo momento. En la Figura 3, se muestra una notificación de ejemplo.

Cómo informar al usuario sobre el requisito de acceso a ubicación en segundo plano

Si el usuario solicitó que tu app acceda a la ubicación solo cuando la usa, recomendamos que la app muestre un diálogo personalizado para alertar al usuario que un flujo de trabajo de la app no puede funcionar correctamente sin acceder a la ubicación en todo momento.

Precaución: Los usuarios tienen la opción de rechazar de manera simultánea el acceso a la ubicación del dispositivo y de impedir que tu app solicite acceso a ella en el futuro. Tu app debe respetar y controlar esta decisión de "rechazar el permiso y no volver a preguntar".

Una vez que el usuario acepte este diálogo, podrás solicitar el uso de la ubicación en segundo plano; entonces, aparecerá el diálogo que se muestra en la Figura 4:

Captura de pantalla del diálogo para el usuario
Figura 4: Diálogo que le solicita al usuario permitir el acceso a la ubicación en todo momento

A continuación, hay un ejemplo de esta lógica de verificación de permiso:

Kotlin

    val permissionAccessCoarseLocationApproved = ActivityCompat
        .checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION) ==
        PackageManager.PERMISSION_GRANTED

    if (permissionAccessCoarseLocationApproved) {
       val backgroundLocationPermissionApproved = ActivityCompat
           .checkSelfPermission(this, permission.ACCESS_BACKGROUND_LOCATION) ==
           PackageManager.PERMISSION_GRANTED

       if (backgroundLocationPermissionApproved) {
           // App can access location both in the foreground and in the background.
           // Start your service that doesn't have a foreground service type
           // defined.
       } else {
           // App can only access location in the foreground. Display a dialog
           // warning the user that your app must have all-the-time access to
           // location in order to function properly. Then, request background
           // location.
           ActivityCompat.requestPermissions(this,
               arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
               your-permission-request-code
           )
       }
    } else {
       // App doesn't have access to the device's location at all. Make full request
       // for permission.
       ActivityCompat.requestPermissions(this,
           arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,
                   Manifest.permission.ACCESS_BACKGROUND_LOCATION),
           your-permission-request-code
       )
    }
    

Java

    boolean permissionAccessCoarseLocationApproved =
        ActivityCompat.checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION)
            == PackageManager.PERMISSION_GRANTED;

    if (permissionAccessCoarseLocationApproved) {
       boolean backgroundLocationPermissionApproved =
               ActivityCompat.checkSelfPermission(this,
                   permission.ACCESS_BACKGROUND_LOCATION)
                   == PackageManager.PERMISSION_GRANTED;

       if (backgroundLocationPermissionApproved) {
           // App can access location both in the foreground and in the background.
           // Start your service that doesn't have a foreground service type
           // defined.
       } else {
           // App can only access location in the foreground. Display a dialog
           // warning the user that your app must have all-the-time access to
           // location in order to function properly. Then, request background
           // location.
           ActivityCompat.requestPermissions(this, new String[] {
               Manifest.permission.ACCESS_BACKGROUND_LOCATION},
               your-permission-request-code);
       }
    } else {
       // App doesn't have access to the device's location at all. Make full request
       // for permission.
       ActivityCompat.requestPermissions(this, new String[] {
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.ACCESS_BACKGROUND_LOCATION
            },
            your-permission-request-code);
    }
    

Cómo continuar una acción que inició el usuario

La app puede ofrecer flujos de trabajo según la ubicación, como el uso de la navegación paso a paso mientras conduces o el trazado de una ruta mientras corres. Cuando los usuarios realizan estos tipos de tareas, la app generalmente necesita acceder a la ubicación del dispositivo una vez que pasa a estar en segundo plano, por ejemplo, cuando el usuario presiona el botón de Inicio o apaga la pantalla del dispositivo.

Para retener el acceso a la ubicación del dispositivo en este caso práctico específico, inicia un servicio en primer plano que hayas declarado que tiene un tipo de servicio en primer plano de "location" en el manifiesto de tu app:

    <service
        android:name="MyNavigationService"
        android:foregroundServiceType="location" ... >
        ...
    </service>
    

Antes de iniciar el servicio en primer plano, asegúrate de que la app aún tenga acceso a la ubicación del dispositivo:

Kotlin

    val permissionAccessCoarseLocationApproved = ActivityCompat
        .checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION) ==
        PackageManager.PERMISSION_GRANTED

    if (permissionAccessCoarseLocationApproved) {
       // App has permission to access location in the foreground. Start your
       // foreground service that has a foreground service type of "location".
    } else {
       // Make a request for foreground-only location access.
       ActivityCompat.requestPermissions(this,
           arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION),
           your-permission-request-code
       )
    }
    

Java

    boolean permissionAccessCoarseLocationApproved =
        ActivityCompat.checkSelfPermission(this,
            permission.ACCESS_COARSE_LOCATION) ==
            PackageManager.PERMISSION_GRANTED;

    if (permissionAccessCoarseLocationApproved) {
        // App has permission to access location in the foreground. Start your
        // foreground service that has a foreground service type of "location".
    } else {
       // Make a request for foreground-only location access.
       ActivityCompat.requestPermissions(this, new String[] {
            Manifest.permission.ACCESS_COARSE_LOCATION},
           your-permission-request-code);
    }
    

Cómo detener las actualizaciones de ubicación

Considera si quieres detener las actualizaciones de ubicación cuando ya no se enfoca la actividad, por ejemplo, cuando el usuario cambia a otra app o a una actividad diferente en la misma app. Esto puede ser útil para reducir el consumo de energía, siempre que la app no necesite recopilar información, incluso cuando se ejecuta en segundo plano. En esta sección, se muestra cómo detener las actualizaciones en el método onPause() de la actividad.

Para detener las actualizaciones de ubicación, llama a removeLocationUpdates() y pásalo a LocationCallback, como se muestra en el siguiente código de ejemplo:

Kotlin

    override fun onPause() {
        super.onPause()
        stopLocationUpdates()
    }

    private fun stopLocationUpdates() {
        fusedLocationClient.removeLocationUpdates(locationCallback)
    }
    

Java

    @Override
    protected void onPause() {
        super.onPause();
        stopLocationUpdates();
    }

    private void stopLocationUpdates() {
        fusedLocationClient.removeLocationUpdates(locationCallback);
    }
    

Usa un valor booleano, mRequestingLocationUpdates, para registrar si las actualizaciones de ubicación están activadas. En el método onResume() de la actividad, verifica si las actualizaciones de ubicación están activadas y, si no lo están, actívalas:

Kotlin

    override fun onResume() {
        super.onResume()
        if (requestingLocationUpdates) startLocationUpdates()
    }
    

Java

    @Override
    protected void onResume() {
        super.onResume();
        if (requestingLocationUpdates) {
            startLocationUpdates();
        }
    }
    

Cómo guardar el estado de la actividad

Un cambio en la configuración del dispositivo, por ejemplo en la orientación de la pantalla, puede causar la destrucción de la actividad actual. Por lo tanto, tu app debe almacenar toda la información necesaria para recrear la actividad. Una manera de hacerlo es mediante un estado de la instancia almacenado en un objeto Bundle.

En el siguiente ejemplo de código, se muestra cómo usar la devolución de llamada onSaveInstanceState() de la actividad para guardar el estado de la instancia:

Kotlin

    override fun onSaveInstanceState(outState: Bundle?) {
        outState?.putBoolean(REQUESTING_LOCATION_UPDATES_KEY, requestingLocationUpdates)
        super.onSaveInstanceState(outState)
    }
    

Java

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putBoolean(REQUESTING_LOCATION_UPDATES_KEY,
                requestingLocationUpdates);
        // ...
        super.onSaveInstanceState(outState);
    }
    

Define un método de updateValuesFromBundle() para restablecer los valores guardados de la instancia anterior de la actividad, si están disponibles. Llama al método desde el método onCreate() de la actividad, como se muestra en el siguiente ejemplo de código:

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        updateValuesFromBundle(savedInstanceState)
    }

    private fun updateValuesFromBundle(savedInstanceState: Bundle?) {
        savedInstanceState ?: return

        // Update the value of requestingLocationUpdates from the Bundle.
        if (savedInstanceState.keySet().contains(REQUESTING_LOCATION_UPDATES_KEY)) {
            requestingLocationUpdates = savedInstanceState.getBoolean(
                    REQUESTING_LOCATION_UPDATES_KEY)
        }

        // ...

        // Update UI to match restored state
        updateUI()
    }
    

Java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // ...
        updateValuesFromBundle(savedInstanceState);
    }

    private void updateValuesFromBundle(Bundle savedInstanceState) {
        if (savedInstanceState == null) {
            return;
        }

        // Update the value of requestingLocationUpdates from the Bundle.
        if (savedInstanceState.keySet().contains(REQUESTING_LOCATION_UPDATES_KEY)) {
            requestingLocationUpdates = savedInstanceState.getBoolean(
                    REQUESTING_LOCATION_UPDATES_KEY);
        }

        // ...

        // Update UI to match restored state
        updateUI();
    }
    

Para obtener más información sobre cómo guardar un estado de instancia, consulta la referencia de clase de actividad de Android.

Nota: Para obtener un almacenamiento más persistente, almacena las preferencias del usuario en SharedPreferences en tu app. Define la preferencia de uso compartido en el método onPause() de tu actividad y recupera la preferencia en onResume(). Para obtener más información sobre cómo guardar las preferencias, consulta Cómo guardar conjuntos de claves y valores.

En la siguiente lección, se indica Cómo mostrar la dirección de una ubicación determinada.

Recursos adicionales

Para obtener más información, utiliza los siguientes recursos:

Ejemplos