Crea e monitora le recinzioni

Il geofencing combina la consapevolezza della posizione attuale dell'utente con la consapevolezza della sua vicinanza a località che potrebbero essere di suo interesse. Per contrassegnare una località di interesse, specifica la latitudine e la longitudine. Per regolare la prossimità della posizione, aggiungi un raggio. La latitudine, la longitudine e il raggio definiscono un recinto geografico, creando un'area circolare, o recinto, intorno alla posizione di interesse.

Puoi avere più recinti virtuali attivi, con un limite di 100 per app, per utente del dispositivo. Per ogni recinto virtuale, puoi chiedere ai servizi di localizzazione di inviarti eventi di entrata e uscita oppure puoi specificare una durata all'interno dell'area del recinto virtuale da attendere, o permanenza, prima di attivare un evento. Puoi limitare la durata di qualsiasi recinzione virtuale specificando una durata di scadenza in millisecondi. Una volta scaduto il geofence, i servizi di localizzazione lo rimuovono automaticamente.

Questa lezione mostra come aggiungere e rimuovere recinti virtuali e poi ascoltare le transizioni dei recinti virtuali utilizzando un BroadcastReceiver.

Nota:sui dispositivi Wear, le API Geofencing non utilizzano in modo efficiente l'energia. Non consigliamo queste API su Wear. Per saperne di più, leggi Risparmiare energia e batteria.

Configurazione del monitoraggio del geofence

Il primo passaggio per richiedere il monitoraggio del geofence consiste nel richiedere le autorizzazioni necessarie. Per utilizzare il geofencing, la tua app deve richiedere quanto segue:

Per saperne di più, consulta la guida su come richiedere le autorizzazioni di accesso alla posizione.

Se vuoi utilizzare un BroadcastReceiver per rilevare le transizioni di geofence, aggiungi un elemento che specifichi il nome del servizio. Questo elemento deve essere un elemento secondario di <application>:

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

Per accedere alle API di localizzazione, devi creare un'istanza del client Geofencing. Per scoprire come connettere il client:

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

Creare e aggiungere recinti virtuali

La tua app deve creare e aggiungere recinti virtuali utilizzando la classe di creazione dell'API Location per creare oggetti Geofence e la classe di utilità per aggiungerli. Inoltre, per gestire gli intent inviati da Servizi di localizzazione quando si verificano transizioni di recinzione geografica, puoi definire un PendingIntent come mostrato in questa sezione.

Nota:sui dispositivi monoutente, il limite è di 100 recinti virtuali per app. Per i dispositivi multiutente, il limite è di 100 recinti virtuali per app per utente del dispositivo.

Creare oggetti recinti virtuali

Innanzitutto, utilizza Geofence.Builder per creare un recinto virtuale, impostando il raggio, la durata e i tipi di transizione desiderati per il recinto virtuale. Ad esempio, per compilare un oggetto elenco:

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

Questo esempio estrae i dati da un file di costanti. In pratica, le app potrebbero creare dinamicamente recinti virtuali in base alla posizione dell'utente.

Specifica i recinti virtuali e i trigger iniziali

Il seguente snippet utilizza la classe GeofencingRequest e la relativa classe nidificata GeofencingRequestBuilder per specificare i recinti virtuali da monitorare e impostare la modalità di attivazione degli eventi di recinti virtuali correlati:

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

Questo esempio mostra l'utilizzo di due attivatori di geofence. La transizione GEOFENCE_TRANSITION_ENTER si attiva quando un dispositivo entra in un recinto virtuale, mentre la transizione GEOFENCE_TRANSITION_EXIT si attiva quando un dispositivo esce da un recinto virtuale. Se specifichi INITIAL_TRIGGER_ENTER, i servizi di localizzazione GEOFENCE_TRANSITION_ENTER devono essere attivati se il dispositivo si trova già all'interno del recinto virtuale.

In molti casi, potrebbe essere preferibile utilizzare INITIAL_TRIGGER_DWELL, che attiva gli eventi solo quando l'utente si ferma per una durata definita all'interno di un recinto virtuale. Questo approccio può contribuire a ridurre lo "spam di avvisi" derivante da un numero elevato di notifiche quando un dispositivo entra ed esce brevemente dai recinti virtuali. Un'altra strategia per ottenere risultati ottimali dalle recinzioni geografiche è impostare un raggio minimo di 100 metri. In questo modo si tiene conto della precisione della posizione delle tipiche reti Wi-Fi e si riduce il consumo energetico del dispositivo.

Definisci un broadcast receiver per le transizioni di geofence

Un Intent inviato da Servizi di localizzazione può attivare varie azioni nella tua app, ma non deve avviare un'attività o un frammento, perché i componenti devono diventare visibili solo in risposta a un'azione dell'utente. In molti casi, un BroadcastReceiver è un buon modo per gestire una transizione di geofence. Un BroadcastReceiver riceve aggiornamenti quando si verifica un evento, ad esempio una transizione all'interno o all'esterno di un recinto virtuale, e può avviare un'attività in background a lunga esecuzione.

Lo snippet seguente mostra come definire un PendingIntent che avvia 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;
    }

Aggiungere recinti virtuali

Per aggiungere recinti virtuali, utilizza il metodo GeofencingClient.addGeofences(). Fornisci l'oggetto GeofencingRequest e PendingIntent. Il seguente snippet mostra l'elaborazione dei risultati:

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

Gestire le transizioni di geofence

Quando i servizi di localizzazione rilevano che l'utente è entrato o uscito da un recinto geografico, inviano l'Intent contenuto nell'PendingIntent che hai incluso nella richiesta di aggiunta di recinti geografici. Un broadcast receiver come GeofenceBroadcastReceiver nota che è stato richiamato Intent e può quindi ottenere l'evento di geofencing dall'intent, determinare il tipo di transizione del recinto virtuale e determinare quale dei recinti virtuali definiti è stato attivato. Il ricevitore di trasmissione può indirizzare un'app per iniziare a eseguire il lavoro in background o, se desiderato, inviare una notifica come output.

Nota:su Android 8.0 (livello API 26) e versioni successive, se un'app è in esecuzione in background durante il monitoraggio di un geofence, il dispositivo risponde agli eventi di geofencing ogni due minuti. Per scoprire come adattare la tua app a questi limiti di risposta, consulta Limiti di localizzazione in background.

Lo snippet seguente mostra come definire un BroadcastReceiver che pubblica una notifica quando si verifica una transizione di geofence. Quando l'utente fa clic sulla notifica, viene visualizzata l'attività principale dell'app:

Kotlin

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

Java

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

Dopo aver rilevato l'evento di transizione tramite PendingIntent, BroadcastReceiver ottiene il tipo di transizione del geofence e verifica se si tratta di uno degli eventi utilizzati dall'app per attivare le notifiche, ovvero GEOFENCE_TRANSITION_ENTER o GEOFENCE_TRANSITION_EXIT in questo caso. Il servizio invia quindi una notifica e registra i dettagli della transizione.

Interrompere il monitoraggio del recinto virtuale

L'interruzione del monitoraggio del geofence quando non è più necessario o desiderato può contribuire a risparmiare batteria e cicli della CPU sul dispositivo. Puoi interrompere il monitoraggio del recinto virtuale nell'attività principale utilizzata per aggiungere e rimuovere i recinti virtuali; la rimozione di un recinto virtuale lo interrompe immediatamente. L'API fornisce metodi per rimuovere i recinti virtuali in base agli ID richiesta o rimuovendo i recinti virtuali associati a un determinato PendingIntent.

Il seguente snippet rimuove i recinti virtuali in base a PendingIntent, interrompendo tutte le ulteriori notifiche quando il dispositivo entra o esce dai recinti virtuali aggiunti in precedenza:

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

Puoi combinare il geofencing con altre funzionalità basate sulla posizione, ad esempio gli aggiornamenti periodici della posizione. Per ulteriori informazioni, consulta le altre lezioni di questo corso.

Utilizzare le best practice per il geofencing

Questa sezione illustra i consigli per l'utilizzo del geofencing con le API di localizzazione per Android.

Ridurre il consumo energetico

Per ottimizzare il consumo energetico nelle tue app che utilizzano il geofencing, puoi utilizzare le seguenti tecniche:

  • Imposta la reattività delle notifiche su un valore più alto. In questo modo, il consumo energetico viene migliorato aumentando la latenza degli avvisi di geofence. Ad esempio, se imposti un valore di reattività di cinque minuti, la tua app controlla l'avviso di entrata o uscita solo una volta ogni cinque minuti. L'impostazione di valori inferiori non significa necessariamente che gli utenti ricevano una notifica entro quel periodo di tempo (ad esempio, se imposti un valore di 5 secondi, potrebbe volerci un po' di più per ricevere l'avviso).

  • Utilizza un raggio del recinto virtuale più ampio per le posizioni in cui un utente trascorre una quantità significativa di tempo, come casa o lavoro. Sebbene un raggio più ampio non riduca direttamente il consumo energetico, diminuisce la frequenza con cui l'app verifica l'ingresso o l'uscita, riducendo di fatto il consumo energetico complessivo.

Scegliere il raggio ottimale per il geofence

Per risultati ottimali, il raggio minimo del recinto virtuale deve essere compreso tra 100 e 150 metri. Quando è disponibile una rete Wi-Fi, la precisione della localizzazione è generalmente compresa tra 20 e 50 metri. Quando la posizione al chiuso è disponibile, l'intervallo di precisione può essere di soli 5 metri. A meno che tu non sappia che la posizione interna è disponibile all'interno del recinto virtuale, supponi che la precisione della posizione Wi-Fi sia di circa 50 metri.

Quando la posizione Wi-Fi non è disponibile (ad esempio, quando guidi in zone rurali), la precisione della localizzazione diminuisce. L'intervallo di precisione può variare da diverse centinaia di metri a diversi chilometri. In casi come questo, devi creare recinti virtuali utilizzando un raggio più ampio.

Spiega agli utenti perché la tua app utilizza il geofencing

Poiché la tua app accede alla posizione in background quando utilizzi il geofencing, valuta in che modo la tua app offre vantaggi agli utenti. Spiega chiaramente il motivo per cui la tua app ha bisogno di questo accesso per aumentare la comprensione e la trasparenza per gli utenti.

Per saperne di più sulle best practice relative all'accesso alla posizione, incluso il geofencing, consulta la pagina Best practice per la privacy.

Utilizzare il tipo di transizione di permanenza per ridurre lo spam di avvisi

Se ricevi un numero elevato di avvisi quando passi brevemente davanti a un recinto virtuale, il modo migliore per ridurli è utilizzare un tipo di transizione GEOFENCE_TRANSITION_DWELL anziché GEOFENCE_TRANSITION_ENTER. In questo modo, l'avviso di sosta viene inviato solo quando l'utente si ferma all'interno di un recinto virtuale per un determinato periodo di tempo. Puoi scegliere la durata impostando un ritardo di permanenza.

Registra nuovamente i recinti virtuali solo quando necessario

I recinti geografici registrati vengono conservati nel processo com.google.process.location di proprietà del pacchetto com.google.android.gms. L'app non deve fare nulla per gestire i seguenti eventi, perché il sistema ripristina i recinti virtuali dopo questi eventi:

  • Google Play Services è stato aggiornato.
  • Google Play Services viene interrotto e riavviato dal sistema a causa della limitazione delle risorse.
  • Il processo di localizzazione si arresta in modo anomalo.

L'app deve registrare nuovamente i recinti virtuali se sono ancora necessari dopo i seguenti eventi, poiché il sistema non può recuperarli nei seguenti casi:

  • Il dispositivo viene riavviato. L'app deve rilevare l'azione di avvio completato del dispositivo e quindi registrare nuovamente i recinti virtuali richiesti.
  • L'app viene disinstallata e reinstallata.
  • I dati dell'app vengono cancellati.
  • I dati di Google Play Services vengono cancellati.
  • L'app ha ricevuto un avviso GEOFENCE_NOT_AVAILABLE. In genere, questo accade dopo la disattivazione di NLP (Network Location Provider di Android).

Risolvere i problemi relativi all'evento di ingresso nel recinto virtuale

Se i recinti virtuali non vengono attivati quando il dispositivo entra in un recinto virtuale (l'avviso GEOFENCE_TRANSITION_ENTER non viene attivato), assicurati innanzitutto che i recinti virtuali siano registrati correttamente come descritto in questa guida.

Ecco alcuni possibili motivi per cui gli avvisi non funzionano come previsto:

  • La posizione esatta non è disponibile all'interno del recinto virtuale o il recinto virtuale è troppo piccolo. Sulla maggior parte dei dispositivi, il servizio di geofence utilizza solo la posizione di rete per l'attivazione del geofence. Il servizio utilizza questo approccio perché la posizione di rete consuma molta meno energia, richiede meno tempo per ottenere posizioni discrete e, soprattutto, è disponibile all'interno.
  • Il Wi-Fi è disattivato sul dispositivo. Se il Wi-Fi è attivo, la precisione della localizzazione può migliorare notevolmente, quindi se il Wi-Fi è disattivato, l'applicazione potrebbe non ricevere mai avvisi di geofence a seconda di diverse impostazioni, tra cui il raggio del geofence, il modello del dispositivo o la versione di Android. A partire da Android 4.3 (livello API 18), abbiamo aggiunto la funzionalità "Modalità solo scansione Wi-Fi", che consente agli utenti di disattivare il Wi-Fi, ma di ottenere comunque una buona posizione di rete. È buona norma chiedere all'utente e fornire una scorciatoia per attivare la modalità Wi-Fi o solo scansione Wi-Fi se entrambe sono disattivate. Utilizza SettingsClient per assicurarti che le impostazioni di sistema del dispositivo siano configurate correttamente per un rilevamento della posizione ottimale.

    Nota: se la tua app ha come target Android 10 (livello API 29) o versioni successive, non puoi chiamare WifiManager.setEnabled() direttamente a meno che non sia un'app di sistema o un controller delle norme del dispositivo (DPC). Utilizza invece un pannello delle impostazioni.

  • Non è presente una connettività di rete affidabile all'interno del tuo recinto virtuale. Se non è presente una connessione dati affidabile, gli avvisi potrebbero non essere generati. Questo perché il servizio di geofence dipende dal fornitore di servizi di localizzazione di rete, che a sua volta richiede una connessione dati.
  • Gli avvisi possono arrivare in ritardo. Il servizio di geofence non esegue query continue per la posizione, quindi è normale che si verifichi una certa latenza nella ricezione degli avvisi. Di solito la latenza è inferiore a 2 minuti, ancora meno quando il dispositivo è in movimento. Se sono attive le limitazioni alla posizione in background, la latenza è in media di 2-3 minuti. Se il dispositivo è rimasto fermo per un periodo di tempo significativo, la latenza potrebbe aumentare (fino a 6 minuti).

Risorse aggiuntive

Per saperne di più sul geofencing, consulta i seguenti materiali:

Campioni

App di esempio per creare e monitorare recinti virtuali.