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

Receber atualizações periódicas de localização

Se seu app puder rastrear a localização continuamente, ele poderá oferecer informações mais relevantes para o usuário. Por exemplo, se o app ajuda usuários a chegarem a algum lugar a pé ou dirigindo ou rastreia a localização de recursos, ele precisa ter acesso à localização do dispositivo em intervalos regulares. Além da localização geográfica (latitude e longitude), você pode oferecer mais informações ao usuário, como rumo (direção horizontal da viagem), altitude ou velocidade do dispositivo. Essas e outras informações estão disponíveis no objeto Location que seu app pode recuperar do provedor de localização combinada.

Embora você possa saber a localização de um dispositivo com getLastLocation(), como ilustrado nas instruções sobre Ver a última localização conhecida, uma abordagem mais direta é solicitar atualizações periódicas ao provedor de localização combinada. Em resposta, a API atualiza seu app periodicamente com o melhor local disponível com base nos provedores de localização ativos no momento, como Wi-Fi e Sistema de Posicionamento Global (GPS). A precisão do local é determinada pelos provedores, pelas permissões de localização solicitadas e pelas opções definidas na solicitação de localização.

Esta lição mostra como solicitar atualizações regulares da localização de um dispositivo usando o método requestLocationUpdates() no provedor de localização combinada.

Declarar permissões

Apps de serviços de localização precisam solicitar permissão de localização. Na maioria dos casos, você pode solicitar a permissão de local aproximado e ainda assim receber informações razoavelmente precisas dos provedores disponíveis.

O snippet a seguir mostra como solicitar a permissão de local aproximado.

    <manifest ... >
      <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    </manifest>
    
Captura de tela da caixa de diálogo voltada ao usuário
Figura 1. Caixa de diálogo exibida quando o app solicita a localização com a opção de acesso somente quando o app está em uso.

Em um dispositivo com o Android 10 (API nível 29) ou posterior, os usuários veem a caixa de diálogo exibida na Figura 1, o que indica que seu app está solicitando a permissão de localização. Se o usuário permitir que seu app acesse a localização do dispositivo nessa caixa de diálogo, o app poderá acessar essas informações somente quando o usuário estiver interagindo com ele, não durante a execução em segundo plano. Você pode declarar um serviço em primeiro plano, permitindo que o app receba detalhes de localização para continuar uma ação iniciada pelo usuário depois de o usuário enviar o app para o segundo plano.

Observação: embora seja possível solicitar acesso à localização em segundo plano se o app for executado no Android 10 ou posterior, é altamente recomendado que isso não seja feito.

Ver a última localização conhecida

A última localização conhecida do dispositivo oferece uma base útil como ponto de partida, garantindo que o app tenha uma localização conhecida antes de iniciar as atualizações periódicas. Esta lição sobre como Ver a última localização conhecida mostra como ter acesso à última localização conhecida chamando getLastLocation(). Os snippets nas seções a seguir presumem que seu app já tenha recuperado e armazenado a última localização conhecida como um objeto Location na variável global mCurrentLocation.

Solicitar atualizações de localização

Antes de solicitar atualizações de localização, o app precisa estar conectado a serviços de localização e fazer uma solicitação de localização. A lição sobre como Alterar configurações de localização ensina a fazer isso. Quando a solicitação de localização for realizada, você poderá iniciar atualizações regulares chamando requestLocationUpdates().

Dependendo do formulário da solicitação, a localização combinada invoca o método de callback LocationCallback.onLocationResult() e transmite a ele uma lista de objetos Location ou emite um PendingIntent que contém a localização nos próprios dados estendidos. A precisão e a frequência das atualizações são afetadas pelas permissões de localização solicitadas e pelas opções definidas no objeto da solicitação.

Esta lição mostra como receber a atualização usando a abordagem do callback LocationCallback. Chame requestLocationUpdates(), transmitindo a ele sua instância do objeto LocationRequest e um LocationCallback. Defina um método startLocationUpdates(), conforme mostrado no seguinte exemplo de código:

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

Observe que o snippet de código acima se refere a requestingLocationUpdates, uma sinalização booleana que é usada para rastrear se o usuário ativou ou desativou as atualizações de localização. Se o usuário desativou as atualizações de localização, é possível informá-lo sobre o requisito de localização do seu app. Para saber mais sobre a retenção do valor da sinalização booleana em instâncias da atividade, consulte Salvar o estado da atividade.

Definir o callback de atualizações de localização

O provedor de localização combinada invoca o método de callback LocationCallback.onLocationResult(). O argumento recebido contém uma lista de objetos Location com a latitude e a longitude da localização. O snippet a seguir mostra como implementar a interface LocationCallback, definir o método e, em seguida, ver a data e a hora da atualização de localização e exibir a latitude, a longitude e o carimbo de data/hora na interface de usuário do 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
                    // ...
                }
            };
        };
    }
    

Solicitar acesso à localização em segundo plano

Se o app é destinado ao Android 10 ou posterior, é necessário declarar a permissão ACCESS_BACKGROUND_LOCATION no arquivo de manifesto dele e receber permissão do usuário para receber atualizações regulares de localização enquanto o app estiver em segundo plano.

O snippet de código a seguir mostra como solicitar acesso à localização em segundo plano no seu app:

    <manifest ... >
      <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
      <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    </manifest>
    
Captura de tela da caixa de diálogo voltada ao usuário
Figura 2. Caixa de diálogo exibida quando o app solicita a localização com a opção de ter acesso a ela o tempo todo, inclusive em segundo plano.

Em um dispositivo com o Android 10 (API nível 29) ou posterior, os usuários veem a caixa de diálogo exibida na Figura 2, o que indica que seu app está solicitando uma permissão de localização e também acesso à localização o tempo todo, inclusive em segundo plano. Essa caixa de diálogo inclui uma opção para os usuários concederem permissão de acesso à localização somente quando o app estiver em uso. Se eles escolherem essa opção, o app não terá acesso à localização em segundo plano. Se o fluxo de trabalho do app precisar de acesso às informações de localização em tempo integral, informe o usuário sobre o requisito de localização em segundo plano do seu app.

Cuidado: mesmo que os usuários permitam que o app acesse a localização em segundo plano, eles podem revogar essa permissão nas configurações do sistema. Os usuários podem permitir que o app acesse a localização somente enquanto ele estiver em uso ou optar por não conceder nenhuma permissão de acesso à localização.

Por esse motivo, sempre que o app iniciar um serviço, verifique se o usuário ainda permite que ele tenha acesso a informações de localização em segundo plano.

Lembrete de acesso à localização em segundo plano para o usuário

Captura de tela da notificação do sistema
Figura 3. Notificação que lembra o usuário de que ele concedeu a um app acesso à localização do dispositivo o tempo todo.

O usuário pode permitir que seu app tenha acesso à localização do dispositivo o tempo todo. Quando seu app acessar a localização do dispositivo em segundo plano pela primeira vez depois que o usuário fizer essa escolha, o sistema programará uma notificação a ser enviada para o usuário. Essa notificação lembra o usuário que ele permitiu que o app acessasse a localização do dispositivo o tempo todo. Um exemplo dessa notificação é mostrado na Figura 3.

Informar o usuário sobre o requisito de localização em segundo plano

Se o usuário solicitou que seu app acesse a localização somente enquanto estiver em uso, você pode exibir uma caixa de diálogo personalizada para alertar o usuário de que um fluxo de trabalho no app não funciona corretamente sem o acesso à localização em tempo integral.

Cuidado: os usuários podem negar o acesso à localização do dispositivo e, ao mesmo tempo, impedir que o app solicite esse acesso no futuro. Seu app deve respeitar e processar essa decisão de "Negar e não perguntar novamente".

Depois que o usuário confirmar essa caixa de diálogo, você poderá solicitar a localização em segundo plano. Assim, a caixa de diálogo do sistema mostrada na Figura 4 será exibida:

Captura de tela da caixa de diálogo voltada ao usuário
Figura 4. Caixa de diálogo solicitando o consentimento do usuário para acesso à localização o tempo todo.

Um exemplo dessa lógica de verificação de permissão aparece no snippet de código a seguir:

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

Continuar uma ação iniciada pelo usuário

Seu app pode oferecer fluxos de trabalho dependentes da localização, como usar a navegação passo a passo ao dirigir ou definir um caminho durante a execução. Enquanto os usuários realizam esses tipos de tarefas, seu app geralmente precisa acessar a localização do dispositivo depois de ter entrado em segundo plano, por exemplo, quando o usuário pressiona o botão Home no dispositivo ou bloqueia a tela.

Para manter o acesso à localização do dispositivo nesse caso de uso específico, inicie um serviço em primeiro plano que tenha sido declarado como um tipo de serviço em primeiro plano de "location" no manifesto do app:

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

Antes de iniciar o serviço em primeiro plano, verifique se seu app ainda tem acesso à localização do 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);
    }
    

Interromper atualizações de localização

Considere se você quer interromper as atualizações de localização quando a atividade não estiver mais em foco, por exemplo, quando o usuário mudar para outro app ou para outra atividade no mesmo app. Isso pode ajudar a reduzir o consumo da bateria, contanto que o app não precise coletar informações mesmo quando estiver em execução em segundo plano. Esta seção mostra como interromper as atualizações no método onPause() da atividade.

Para interromper as atualizações de localização, chame removeLocationUpdates() e transmita a ele um LocationCallback, conforme mostrado no exemplo de código a seguir:

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

Use um booleano, mRequestingLocationUpdates, para rastrear se as atualizações de localização estão ativadas no momento. No método onResume() da atividade, verifique se as atualizações de localização estão ativadas no momento. Caso contrário, ative-as:

Kotlin

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

Java

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

Salvar o estado da atividade

Alterações na configuração do dispositivo, como uma mudança na orientação da tela ou no idioma, podem fazer com que a atividade atual seja destruída. Por isso, seu app precisa armazenar todas as informações necessárias para recriar a atividade. Uma forma de fazer isso é por meio de um estado de instância armazenado em um objeto Bundle.

O exemplo de código a seguir mostra como usar o callback onSaveInstanceState() da atividade para salvar o estado da instância:

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

Defina um método updateValuesFromBundle() para restaurar os valores salvos a partir da instância anterior da atividade, se eles estiverem disponíveis. Chame o método onCreate() da atividade, conforme mostrado no exemplo de código a seguir:

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 saber mais sobre como salvar um estado de instância, veja a referência da classe Activity do Android.

Observação: para ter um armazenamento mais persistente, salve as preferências do usuário nas SharedPreferences do app. Defina a preferência compartilhada no método onPause() da atividade e recupere a preferência em onResume(). Para ver mais informações sobre como salvar preferências, leia Salvar conjuntos de chave-valor.

A próxima lição, Exibir endereços de localização, mostra como exibir o endereço de um determinado local.

Outros recursos

Para saber mais, use os seguintes recursos:

Amostras