Un buon modo per ottenere la posizione dell’utente in Android (Android Studio – Java)

Condividi questo articolo:


Il problema che mi descrivono:

Denterminare la posizione attuale dell’utente entro una soglia il prima possibile e allo stesso tempo conservare la batteria.

Perché il problema è un problema:

Innanzitutto, Android ha due fornitori: rete e GPS. A volte la rete è migliore e a volte il GPS è migliore.

Per “migliore” intendo il rapporto tra velocità e precisione.
Sono disposto a sacrificare qualche metro di precisione se riesco ad ottenere la posizione quasi istantaneamente e senza accendere il GPS.

In secondo luogo, se si richiedono aggiornamenti per i cambiamenti di posizione non viene inviato nulla se la posizione attuale è stabile.

Google ha qui un esempio per determinare la posizione “migliore”: http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate
Ma penso che non sia buono come dovrebbe/potrebbe essere.

Sono un po’ confuso sul perché Google non abbia un’API normalizzata per la localizzazione, lo sviluppatore non dovrebbe preoccuparsi di dove si trova la localizzazione, si dovrebbe solo specificare cosa si vuole e il telefono dovrebbe scegliere per te.

Per cosa ho bisogno di aiuto:

Ho bisogno di trovare un buon modo per determinare la “migliore” posizione, magari attraverso qualche euristico o magari attraverso qualche libreria di terze parti.

Questo non significa determinare il fornitore migliore!
Probabilmente userò tutti i provider e sceglierò il migliore.

Background dell’app:

L’app raccoglierà la posizione dell’utente ad un intervallo fisso (diciamo ogni 10 minuti circa) e la invierà ad un server.
L’app dovrebbe conservare più batteria possibile e la posizione dovrebbe avere una precisione di X (50-100?) metri.

L’obiettivo è quello di essere in grado di tracciare il percorso dell’utente durante il giorno su una mappa, quindi ho bisogno di una precisione sufficiente per questo.

Varie:

  • Quali sono, secondo lei, i valori ragionevoli sulle precisioni desiderate e accettate?
  • Ho usato 100m come accettato e 30m come desiderato, è chiedere troppo?
  • Mi piacerebbe poter tracciare il percorso dell’utente su una mappa in un secondo momento.
  • 100m per la precisione desiderata e 500m per quella accettata sono meglio?

Inoltre, in questo momento ho il GPS acceso per un massimo di 60 secondi per ogni aggiornamento della posizione, è troppo breve per ottenere una posizione se si è in casa con una precisione di circa 200m?

 

Soluzione:

Sembra che stiamo sviluppando la stessa applicazione 😉
Ecco la mia attuale implementazione. Sono ancora in fase di beta testing della mia applicazione di uploader GPS, quindi potrebbero esserci molti possibili miglioramenti, ma sembra funzionare abbastanza bene finora.

/**
 * try to get the 'best' location selected from all providers
 */
private Location getBestLocation() {
    Location gpslocation = getLocationByProvider(LocationManager.GPS_PROVIDER);
    Location networkLocation =
            getLocationByProvider(LocationManager.NETWORK_PROVIDER);
    // if we have only one location available, the choice is easy
    if (gpslocation == null) {
        Log.d(TAG, "No GPS Location available.");
        return networkLocation;
    }
    if (networkLocation == null) {
        Log.d(TAG, "No Network Location available");
        return gpslocation;
    }
    // a locationupdate is considered 'old' if its older than the configured
    // update interval. this means, we didn't get a
    // update from this provider since the last check
    long old = System.currentTimeMillis() - getGPSCheckMilliSecsFromPrefs();
    boolean gpsIsOld = (gpslocation.getTime() < old);
    boolean networkIsOld = (networkLocation.getTime() < old);
    // gps is current and available, gps is better than network
    if (!gpsIsOld) {
        Log.d(TAG, "Returning current GPS Location");
        return gpslocation;
    }
    // gps is old, we can't trust it. use network location
    if (!networkIsOld) {
        Log.d(TAG, "GPS is old, Network is current, returning network");
        return networkLocation;
    }
    // both are old return the newer of those two
    if (gpslocation.getTime() > networkLocation.getTime()) {
        Log.d(TAG, "Both are old, returning gps(newer)");
        return gpslocation;
    } else {
        Log.d(TAG, "Both are old, returning network(newer)");
        return networkLocation;
    }
}

/**
 * get the last known location from a specific provider (network/gps)
 */
private Location getLocationByProvider(String provider) {
    Location location = null;
    if (!isProviderSupported(provider)) {
        return null;
    }
    LocationManager locationManager = (LocationManager) getApplicationContext()
            .getSystemService(Context.LOCATION_SERVICE);
    try {
        if (locationManager.isProviderEnabled(provider)) {
            location = locationManager.getLastKnownLocation(provider);
        }
    } catch (IllegalArgumentException e) {
        Log.d(TAG, "Cannot acces Provider " + provider);
    }
    return location;
}

Modifica: ecco la parte che richiede gli aggiornamenti periodici ai fornitori di location:

public void startRecording() {
    gpsTimer.cancel();
    gpsTimer = new Timer();
    long checkInterval = getGPSCheckMilliSecsFromPrefs();
    long minDistance = getMinDistanceFromPrefs();
    // receive updates
    LocationManager locationManager = (LocationManager) getApplicationContext()
            .getSystemService(Context.LOCATION_SERVICE);
    for (String s : locationManager.getAllProviders()) {
        locationManager.requestLocationUpdates(s, checkInterval,
                minDistance, new LocationListener() {

                    @Override
                    public void onStatusChanged(String provider,
                            int status, Bundle extras) {}

                    @Override
                    public void onProviderEnabled(String provider) {}

                    @Override
                    public void onProviderDisabled(String provider) {}

                    @Override
                    public void onLocationChanged(Location location) {
                        // if this is a gps location, we can use it
                        if (location.getProvider().equals(
                                LocationManager.GPS_PROVIDER)) {
                            doLocationUpdate(location, true);
                        }
                    }
                });
        // //Toast.makeText(this, "GPS Service STARTED",
        // Toast.LENGTH_LONG).show();
        gps_recorder_running = true;
    }
    // start the gps receiver thread
    gpsTimer.scheduleAtFixedRate(new TimerTask() {

        @Override
        public void run() {
            Location location = getBestLocation();
            doLocationUpdate(location, false);
        }
    }, 0, checkInterval);
}

public void doLocationUpdate(Location l, boolean force) {
    long minDistance = getMinDistanceFromPrefs();
    Log.d(TAG, "update received:" + l);
    if (l == null) {
        Log.d(TAG, "Empty location");
        if (force)
            Toast.makeText(this, "Current location not available",
                    Toast.LENGTH_SHORT).show();
        return;
    }
    if (lastLocation != null) {
        float distance = l.distanceTo(lastLocation);
        Log.d(TAG, "Distance to last: " + distance);
        if (l.distanceTo(lastLocation) < minDistance && !force) {
            Log.d(TAG, "Position didn't change");
            return;
        }
        if (l.getAccuracy() >= lastLocation.getAccuracy()
                && l.distanceTo(lastLocation) < l.getAccuracy() && !force) {
            Log.d(TAG,
                    "Accuracy got worse and we are still "
                      + "within the accuracy range.. Not updating");
            return;
        }
        if (l.getTime() <= lastprovidertimestamp && !force) {
            Log.d(TAG, "Timestamp not never than last");
            return;
        }
    }
    // upload/store your location here
}

Cose da considerare:

non richiedere troppo spesso gli aggiornamenti del GPS, scarica la batteria. Attualmente utilizzo 30 min come default per la mia applicazione.
aggiungere un controllo di “distanza minima dall’ultima posizione conosciuta”. senza questo, i punti “saltano” quando il GPS non è disponibile e la posizione viene triangolata dalle torri di cella. oppure si può controllare se la nuova posizione è al di fuori del valore di precisione dell’ultima posizione conosciuta.

Condividi questo articolo: