Il geofencing combina la consapevolezza della posizione attuale dell'utente con la consapevolezza della sua prossimità a località che potrebbero essere di suo interesse. Per contrassegnare una località di interesse, devi specificarne la latitudine e la longitudine. Per regolare la prossimità della località, aggiungi un raggio. La latitudine, la longitudine e il raggio definiscono un recinto virtuale, creando un 'area circolare, o recinto, intorno alla località 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 recinto virtuale specificando una durata di scadenza in millisecondi. Una volta scaduto il recinto virtuale, 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 l'energia in modo efficiente. Non consigliamo queste API su Wear. Per ulteriori informazioni, leggi Risparmiare energia e batteria.
Configurare il monitoraggio dei recinti virtuali
Il primo passaggio per richiedere il monitoraggio dei recinti virtuali è richiedere le autorizzazioni necessarie. Per utilizzare il geofencing, la tua app deve richiedere quanto segue:
-
ACCESS_FINE_LOCATION -
ACCESS_BACKGROUND_LOCATIONse la tua app ha come target Android 10 (livello API 29) o versioni successive
Per saperne di più, consulta la guida su come richiedere le autorizzazioni di accesso alla posizione.
Se vuoi utilizzare un BroadcastReceiver per ascoltare le transizioni dei recinti virtuali,
aggiungi un elemento che specifica il nome del servizio. Questo elemento deve essere
un elemento figlio dell'elemento
<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 di localizzazione per
creare oggetti Geofence e la classe di convenienza per aggiungerli. Inoltre, per gestire gli
intent inviati dai Servizi di localizzazione quando si verificano transizioni dei recinti virtuali, puoi definire un
PendingIntent come mostrato in questa sezione.
Nota: sui dispositivi a utente singolo, 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 recinto virtuale
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 popolare 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.
Specificare 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 per impostare la modalità di attivazione degli eventi dei 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 trigger di recinti virtuali. 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. La specifica di
INITIAL_TRIGGER_ENTER indica ai Servizi di localizzazione che
GEOFENCE_TRANSITION_ENTER
deve essere attivato se il dispositivo si trova già all'interno del recinto virtuale.
In molti casi, potrebbe essere preferibile utilizzare invece
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 dai
recinti virtuali è impostare un raggio minimo di 100 metri. In questo modo si tiene conto della precisione della localizzazione
delle tipiche reti Wi-Fi e si contribuisce a ridurre il consumo di energia del dispositivo.
Definire un broadcast receiver per le transizioni dei recinti virtuali
Un Intent inviato dai Servizi di localizzazione può attivare varie azioni in
la tua app, ma non deve avviare un'attività o un fragment, 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 un recinto virtuale. 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 lavoro in background a lunga esecuzione.
Il seguente snippet mostra come
definire un PendingIntent che avvia un BroadcastReceiver:
Kotlin
class MainActivity : AppCompatActivity() { // ... private val geofencePendingIntent: PendingIntent by lazy { // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling // addGeofences() and removeGeofences(). val flags = PendingIntent.FLAG_UPDATE_CURRENT if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { // Starting on Android S+ the pending intent has to be mutable. flags or PendingIntent.FLAG_MUTABLE } val intent = Intent(this, GeofenceBroadcastReceiver::class.java) PendingIntent.getBroadcast(this, 0, intent, flags) } }
Java
public class MainActivity extends AppCompatActivity { // ... private PendingIntent getGeofencePendingIntent() { // Reuse the PendingIntent if we already have it. if (geofencePendingIntent != null) { return geofencePendingIntent; } // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling // addGeofences() and removeGeofences(). int flags = PendingIntent.FLAG_UPDATE_CURRENT; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { // Starting on Android S+ the pending intent has to be mutable. flags |= PendingIntent.FLAG_MUTABLE; } Intent intent = new Intent(this, GeofenceBroadcastReceiver.class); geofencePendingIntent = PendingIntent.getBroadcast(this, 0, intent, flags); return geofencePendingIntent; } }
Aggiungere recinti virtuali
Per aggiungere recinti virtuali, utilizza il metodo
.
Fornisci l'oggetto GeofencingClient.addGeofences()
GeofencingRequest e il 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 dei recinti virtuali
Quando i Servizi di localizzazione rilevano che l'utente è entrato o uscito da un recinto virtuale, inviano il Intent incluso nel PendingIntent
che hai incluso nella richiesta di aggiunta dei recinti virtuali. Un broadcast receiver come
GeofenceBroadcastReceiver rileva che l'Intent è stato richiamato e
può quindi ottenere l'evento di geofencing dall'intent, determinare il tipo di transizione dei recinti virtuali,
e determinare quale dei recinti virtuali definiti è stato attivato. Il ricevitore di trasmissione può indicare a un'app di avviare l'esecuzione di un lavoro in background o, se lo desideri, 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 recinto virtuale, 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.
Il seguente snippet mostra come definire un
BroadcastReceiver
che pubblica una notifica quando si verifica una transizione di un recinto virtuale. 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,
il BroadcastReceiver ottiene il tipo di transizione del recinto virtuale e
verifica se si tratta di uno degli eventi utilizzati dall'app per attivare
le notifiche, in questo caso
GEOFENCE_TRANSITION_ENTER
o GEOFENCE_TRANSITION_EXIT. Il servizio invia quindi una notifica e registra i dettagli della transizione.
Interrompere il monitoraggio dei recinti virtuali
L'interruzione del monitoraggio dei recinti virtuali quando non è più necessario o desiderato può contribuire a risparmiare energia della batteria
e cicli della CPU sul dispositivo. Puoi interrompere il monitoraggio dei recinti virtuali
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 delle richieste o rimuovendo i recinti virtuali associati a un determinato
PendingIntent.
Il seguente snippet rimuove i recinti virtuali in base a PendingIntent, interrompendo tutte le
notifiche successive 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à che tengono conto della posizione, ad esempio gli aggiornamenti periodici della posizione. Per ulteriori informazioni, consulta le altre lezioni di questa classe.
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 di energia
Puoi utilizzare le seguenti tecniche per ottimizzare il consumo di energia nelle app che utilizzano il geofencing:
Imposta la reattività delle notifiche su un valore più alto. In questo modo si migliora il consumo di energia aumentando la latenza degli avvisi dei recinti virtuali. Ad esempio, se imposti un valore di reattività di cinque minuti, la tua app controlla la presenza di un 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 essere necessario un po' più di tempo per ricevere l'avviso).
Utilizza un raggio del recinto virtuale più grande per le località in cui un utente trascorre una quantità significativa di tempo, ad esempio a casa o al lavoro. Sebbene un raggio più grande non riduca direttamente il consumo di energia, riduce la frequenza con cui l'app verifica l'entrata o l'uscita, riducendo di fatto il consumo di energia complessivo.
Scegliere il raggio ottimale per il recinto virtuale
Per risultati ottimali, il raggio minimo del recinto virtuale deve essere compreso tra 100 e 150 metri. Quando il Wi-Fi è disponibile, la precisione della localizzazione è in genere compresa tra 20 e 50 metri. Quando è disponibile la localizzazione indoor, l'intervallo di precisione può essere di soli 5 metri. A meno che tu non sappia che la localizzazione indoor è disponibile all'interno del recinto virtuale, presupponi che la precisione della localizzazione Wi-Fi sia di circa 50 metri.
Quando la localizzazione Wi-Fi non è disponibile (ad esempio, quando guidi in aree rurali), la precisione della localizzazione peggiora. L'intervallo di precisione può essere compreso tra diverse centinaia di metri e diversi chilometri. In questi casi, devi creare recinti virtuali con un raggio più grande.
Spiegare 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 loro chiaramente perché la tua app ha bisogno di questo accesso per aumentare la comprensione e la trasparenza degli utenti.
Per ulteriori informazioni sulle best practice relative all'accesso alla posizione, incluso il geofencing, consulta la pagina delle 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
ridurre gli avvisi è utilizzare un tipo di transizione
GEOFENCE_TRANSITION_DWELL anziché
GEOFENCE_TRANSITION_ENTER. In questo modo, l'avviso di permanenza 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.
Registrare nuovamente i recinti virtuali solo quando necessario
I recinti virtuali 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 viene aggiornato.
- Google Play Services viene terminato e riavviato dal sistema a causa di una 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 ascoltare l'azione di completamento dell'avvio 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 che NLP (Network Location Provider di Android) è stato disattivato.
Risolvere i problemi relativi all'evento di entrata del 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 precisa non è disponibile all'interno del recinto virtuale o il recinto virtuale è troppo piccolo. Sulla maggior parte dei dispositivi, il servizio di recinti virtuali utilizza solo la localizzazione di rete per l'attivazione dei recinti virtuali. Il servizio utilizza questo approccio perché la localizzazione di rete consuma molta meno energia, richiede meno tempo per ottenere posizioni discrete e, soprattutto, è disponibile al chiuso.
Il Wi-Fi è disattivato sul dispositivo. L'attivazione del Wi-Fi può migliorare notevolmente la precisione della localizzazione, quindi se il Wi-Fi è disattivato, la tua applicazione potrebbe non ricevere mai avvisi di recinti virtuali a seconda di diverse impostazioni, tra cui il raggio del recinto virtuale, 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 localizzazione di rete. È una best practice chiedere all'utente e fornire una scorciatoia per attivare il Wi-Fi o la modalità solo scansione Wi-Fi se entrambi sono disattivati. Utilizza SettingsClient per assicurarti che le impostazioni di sistema del dispositivo siano configurate correttamente per il rilevamento ottimale della posizione.
Nota: se la tua app ha come target Android 10 (livello API 29) o versioni successive, non puoi chiamare direttamente
WifiManager.setEnabled()a meno che la tua app non sia un'app di sistema o un controller dei criteri del dispositivo (DPC). Utilizza invece un pannello delle impostazioni.- Non esiste una connettività di rete affidabile all'interno del recinto virtuale. Se non è presente una connessione dati affidabile, le notifiche potrebbero non essere generate. Questo perché il servizio di recinti virtuali dipende dal provider di localizzazione di rete che a sua volta richiede una connessione dati.
- Gli avvisi possono essere in ritardo. Il servizio di recinti virtuali non esegue query continue per la posizione, quindi prevedi una certa latenza nella ricezione degli avvisi. In genere la latenza è inferiore a 2 minuti, anche meno quando il dispositivo è in movimento. Se i limiti di localizzazione in background sono in vigore, la latenza è in media di circa 2-3 minuti. Se il dispositivo è 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:
Esempi
App di esempio per la creazione e il monitoraggio dei recinti virtuali.