Crea e monitora le recinzioni

Il geofencing combina la consapevolezza della posizione attuale dell'utente con la consapevolezza della vicinanza dell'utente a località che potrebbero essere di interesse. Per contrassegnare una località di interesse, devi specificarne la latitudine e la longitudine. Per regolare la vicinanza 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 geolocalizzazione di inviarti eventi di ingresso e uscita oppure puoi specificare una durata di attesa all'interno dell'area del recinto virtuale, o dwell, prima di attivare un evento. Puoi limitare la durata di qualsiasi recinto virtuale specificando una durata di scadenza in millisecondi. Alla scadenza del recinto virtuale, i Servizi di geolocalizzazione lo rimuove automaticamente.

Questa lezione spiega come aggiungere e rimuovere i recinti virtuali e quindi ascoltare le transizioni del recinto virtuale utilizzando un BroadcastReceiver.

Configura per il monitoraggio del recinto virtuale

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

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

Se vuoi utilizzare un BroadcastReceiver per ascoltare le transizioni del recinto virtuale, 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 geolocalizzazione, devi creare un'istanza del client Geofencing. Per scoprire come connettere il tuo 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 del builder dell'API Location per la creazione di oggetti Geofence e la classe di convenienza per aggiungerli. Inoltre, per gestire gli intent inviati dai servizi di geolocalizzazione quando si verificano transizioni del recinto virtuale, puoi definire un PendingIntent come mostrato in questa sezione.

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

Creare oggetti di 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 completare 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 delle costanti. Nella pratica, le app potrebbero creare dinamicamente recinti virtuali in base alla posizione dell'utente.

Specifica i recinti virtuali e gli attivatori iniziali

Lo snippet seguente utilizza la classe GeofencingRequest e la relativa classe GeofencingRequestBuilder nidificata per specificare i recinti virtuali da monitorare e impostare la modalità di attivazione degli eventi del recinto virtuale 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'uso di due attivatori di recinto virtuale. 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, indichi ai servizi di geolocalizzazione 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 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ò aiutare a ridurre gli "avvisi di spam" derivanti da notifiche con numeri elevati quando un dispositivo entra e esce brevemente dai recinti virtuali. Un'altra strategia per ottenere risultati ottimali dai tuoi recinti virtuali è impostare un raggio minimo di 100 metri. Ciò contribuisce alla precisione della posizione delle reti Wi-Fi tipiche e contribuisce a ridurre il consumo di energia dei dispositivi.

Definisci un ricevitore per le transizioni del recinto virtuale

Un elemento Intent inviato dai servizi di geolocalizzazione può attivare varie azioni nella tua app, ma non dovresti chiedergli di 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 ottimo modo per gestire una transizione al recinto virtuale. Un elemento BroadcastReceiver riceve aggiornamenti quando si verifica un evento, ad esempio una transizione dentro o fuori un recinto virtuale, e può avviare operazioni in background a lunga esecuzione.

Il seguente snippet mostra come definire un PendingIntent che avvii 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. Lo snippet seguente 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 del recinto virtuale

Quando i servizi di geolocalizzazione rilevano che l'utente è entrato o uscito da un recinto virtuale, invia il Intent contenuto nell'PendingIntent che hai incluso nella richiesta di aggiungere recinti virtuali. Un ricevitore della trasmissione come GeofenceBroadcastReceiver nota che Intent è stato richiamato e può quindi ottenere l'evento di geofencing dall'intent, determinare il tipo o le transizioni di recinto virtuale e determinare quale dei recinti virtuali definiti è stato attivato. Il ricevitore può indicare a un'app di iniziare a eseguire operazioni in background o, se lo desideri, a inviare una notifica come output.

Nota: su Android 8.0 (livello API 26) e versioni successive, se un'app viene eseguita 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 località in background.

Lo snippet seguente mostra come definire un BroadcastReceiver che pubblichi una notifica quando si verifica una transizione del 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, BroadcastReceiver riceve il tipo di transizione del recinto virtuale e verifica se è 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.

Interrompi il monitoraggio del recinto virtuale

Interrompere il monitoraggio del recinto virtuale quando non è più necessario o desiderato può aiutare a risparmiare batteria e cicli di 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 di 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à sensibili alla posizione, ad esempio aggiornamenti periodici sulla posizione. Per ulteriori informazioni, consulta le altre lezioni di questo corso.

Usare le best practice per il geofencing

Questa sezione offre consigli per l'utilizzo del geofencing con le API di geolocalizzazione per Android.

Ridurre il consumo di corrente

Puoi utilizzare le seguenti tecniche per ottimizzare il consumo energetico nelle tue app che utilizzano il geofencing:

  • Imposta l'opzione Adattabilità alle notifiche su un valore più alto. In questo modo, il consumo di energia aumenta la latenza degli avvisi per il recinto virtuale. Ad esempio, se imposti un valore di reattività di cinque minuti, la tua app verifica la presenza di un avviso di ingresso o uscita solo una volta ogni cinque minuti. L'impostazione di valori più bassi non significa necessariamente che gli utenti vengano informati entro quel periodo di tempo (ad esempio, se imposti un valore di 5 secondi, la ricezione dell'avviso potrebbe richiedere un po' più di tempo).

  • Utilizza un raggio di recinto virtuale più ampio per i luoghi in cui un utente trascorre molto tempo, ad esempio casa o lavoro. Sebbene un raggio più ampio non riduca direttamente il consumo di energia, riduce la frequenza con cui l'app controlla l'entrata o l'uscita, riducendo di fatto il consumo complessivo di energia.

Scegli il raggio ottimale per il tuo recinto virtuale

Per ottenere risultati ottimali, il raggio minimo del recinto virtuale deve essere impostato su un valore compreso tra 100 e 150 metri. Quando è disponibile il Wi-Fi, la precisione della posizione è generalmente compresa tra 20 e 50 metri. Quando è disponibile la localizzazione in ambienti interni, il raggio di precisione può essere di soli 5 metri. A meno che tu non sappia che la posizione all'interno del recinto virtuale è disponibile, supponiamo 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 geolocalizzazione si riduce. L'intervallo di precisione può variare da diverse centinaia di metri a diversi chilometri. In questi casi, 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 perché la tua app ha bisogno di questo accesso per aumentare la comprensione e la trasparenza per gli utenti.

Per ulteriori informazioni sulle best practice relative all'accesso alla posizione, incluso il geofencing, consulta la pagina relativa alle best practice per la privacy.

Utilizza il tipo di transizione di attesa per ridurre lo spam di avvisi

Se ricevi un numero elevato di avvisi quando guidi per poco tempo oltre un recinto virtuale, il modo migliore per ridurre gli avvisi è utilizzare un tipo di transizione di GEOFENCE_TRANSITION_DWELL anziché GEOFENCE_TRANSITION_ENTER. In questo modo, l'avviso di dimora 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 per ritardo.

Registra di nuovo i recinti virtuali solo quando richiesto

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:

  • Viene eseguito l'upgrade di Google Play Services.
  • Google Play Services viene interrotto e riavviato dal sistema a causa di restrizioni in termini di risorse.
  • Il processo di geolocalizzazione 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ò recuperare i recinti virtuali nei seguenti casi:

  • Il dispositivo è stato riavviato. L'app deve rimanere in ascolto dell'azione di completamento dell'avvio del dispositivo, 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 sono stati cancellati.
  • L'app ha ricevuto un avviso GEOFENCE_NOT_AVAILABLE. In genere questo accade dopo la disattivazione dell'NLP (il provider di posizione di rete di Android).

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

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

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

  • Non è disponibile una posizione precisa all'interno del tuo recinto virtuale o il tuo recinto virtuale è troppo piccolo. Sulla maggior parte dei dispositivi, il servizio di recinto virtuale utilizza solo la posizione di rete per l'attivazione del recinto virtuale. Il servizio utilizza questo approccio perché la posizione di rete consuma molto meno energia, occorre meno tempo per recuperare posizioni discrete e, soprattutto, è disponibile al chiuso.
  • Il Wi-Fi è disattivato sul dispositivo. Se il Wi-Fi è attivo può migliorare notevolmente la precisione della posizione. Pertanto, se il Wi-Fi è disattivato, l'applicazione potrebbe non ricevere mai avvisi per il recinto virtuale, 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à "Solo ricerca Wi-Fi", che consente agli utenti di disattivare il Wi-Fi ma comunque ottenere una buona posizione di rete. È buona norma chiedere all'utente e fornire una scorciatoia per attivare la modalità Solo ricerca Wi-Fi o Solo ricerca Wi-Fi se entrambe sono disattivate. 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 WifiManager.setEnabled() direttamente a meno che la tua app non sia un'app di sistema o un controller dei criteri dei dispositivi (DPC). Utilizza invece un riquadro delle impostazioni.

  • Non è presente una connettività di rete affidabile all'interno del tuo recinto virtuale. Se non esiste una connessione dati affidabile, potrebbero non essere generati avvisi. Questo perché il servizio di recinto virtuale dipende dal provider di geolocalizzazione della rete, che a sua volta richiede una connessione dati.
  • Gli avvisi possono essere in ritardo. Il servizio di recinto virtuale non esegue query continue per la posizione, quindi è normale che si verifichi una certa latenza quando ricevono gli avvisi. Solitamente la latenza è inferiore a 2 minuti, anche meno quando il dispositivo è in movimento. Se i limiti di posizione in background sono attivi, la latenza è in media di circa 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 scoprire di più sul geofencing, consulta i seguenti materiali:

Campioni

App di esempio per creare e monitorare i recinti virtuali.