Un buon modo per ottenere la posizione dell'utente su Android


211

Il problema:

Ottenere la posizione corrente dell'utente entro una soglia AL PIÙ PRESTO e allo stesso tempo conservare la batteria.

Perché il problema è un problema:

Prima di tutto, Android ha due provider; rete e GPS. A volte la rete è migliore e a volte il GPS è migliore.

Per "migliore" intendo rapporto velocità / precisione.
Sono disposto a sacrificare alcuni metri di precisione se riesco a ottenere la posizione quasi istantaneamente e senza accendere il GPS.

In secondo luogo, se si richiedono aggiornamenti per le modifiche della posizione, non viene inviato nulla se la posizione corrente è stabile.

Google ha un esempio di come determinare la posizione "migliore" qui: http://developer.android.com/guide/topics/location/obenimento-user-location.html#BestEstimate
Ma penso che non sia così vicino come dovrebbe /potrebbe essere.

Sono un po 'confuso perché Google non ha un'API normalizzata per la posizione, lo sviluppatore non dovrebbe preoccuparsi della posizione da cui proviene, dovresti semplicemente specificare ciò che desideri e il telefono dovrebbe scegliere per te.

Di cosa ho bisogno di aiuto con:

Ho bisogno di trovare un buon modo per determinare la posizione "migliore", forse attraverso qualche euristica o forse attraverso una biblioteca di terze parti.

Questo non significa determinare il miglior fornitore!
Probabilmente userò tutti i fornitori e sceglierò il meglio di essi.

Sfondo dell'app:

L'app raccoglierà la posizione dell'utente a intervalli fissi (diciamo ogni 10 minuti circa) e la invierà a un server.
L'app dovrebbe conservare quanta più batteria possibile e la posizione dovrebbe avere una precisione di X (50-100?) Metri.

L'obiettivo è in seguito poter tracciare il percorso dell'utente durante il giorno su una mappa, quindi ho bisogno di una precisione sufficiente per questo.

Varie:

Quali ritieni siano valori ragionevoli sulla precisione desiderata e accettata?
Ho usato 100m come accettato e 30m come desiderato, è troppo chiedere?
Mi piacerebbe poter tracciare il percorso dell'utente su una mappa in seguito.
100 m sono desiderati e 500 m sono accettati meglio?

Inoltre, in questo momento ho il GPS acceso per un massimo di 60 secondi per aggiornamento di posizione, è troppo corto per ottenere una posizione se sei in casa con una precisione di forse 200m?


Questo è il mio codice attuale, ogni feedback è apprezzato (a parte la mancanza di controllo degli errori che è TODO):

protected void runTask() {
    final LocationManager locationManager = (LocationManager) context
            .getSystemService(Context.LOCATION_SERVICE);
    updateBestLocation(locationManager
            .getLastKnownLocation(LocationManager.GPS_PROVIDER));
    updateBestLocation(locationManager
            .getLastKnownLocation(LocationManager.NETWORK_PROVIDER));
    if (getLocationQuality(bestLocation) != LocationQuality.GOOD) {
        Looper.prepare();
        setLooper(Looper.myLooper());
        // Define a listener that responds to location updates
        LocationListener locationListener = new LocationListener() {

            public void onLocationChanged(Location location) {
                updateBestLocation(location);
                if (getLocationQuality(bestLocation) != LocationQuality.GOOD)
                    return;
                // We're done
                Looper l = getLooper();
                if (l != null) l.quit();
            }

            public void onProviderEnabled(String provider) {}

            public void onProviderDisabled(String provider) {}

            public void onStatusChanged(String provider, int status,
                    Bundle extras) {
                // TODO Auto-generated method stub
                Log.i("LocationCollector", "Fail");
                Looper l = getLooper();
                if (l != null) l.quit();
            }
        };
        // Register the listener with the Location Manager to receive
        // location updates
        locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER, 1000, 1, locationListener,
                Looper.myLooper());
        locationManager.requestLocationUpdates(
                LocationManager.NETWORK_PROVIDER, 1000, 1,
                locationListener, Looper.myLooper());
        Timer t = new Timer();
        t.schedule(new TimerTask() {

            @Override
            public void run() {
                Looper l = getLooper();
                if (l != null) l.quit();
                // Log.i("LocationCollector",
                // "Stopping collector due to timeout");
            }
        }, MAX_POLLING_TIME);
        Looper.loop();
        t.cancel();
        locationManager.removeUpdates(locationListener);
        setLooper(null);
    }
    if (getLocationQuality(bestLocation) != LocationQuality.BAD) 
        sendUpdate(locationToString(bestLocation));
    else Log.w("LocationCollector", "Failed to get a location");
}

private enum LocationQuality {
    BAD, ACCEPTED, GOOD;

    public String toString() {
        if (this == GOOD) return "Good";
        else if (this == ACCEPTED) return "Accepted";
        else return "Bad";
    }
}

private LocationQuality getLocationQuality(Location location) {
    if (location == null) return LocationQuality.BAD;
    if (!location.hasAccuracy()) return LocationQuality.BAD;
    long currentTime = System.currentTimeMillis();
    if (currentTime - location.getTime() < MAX_AGE
            && location.getAccuracy() <= GOOD_ACCURACY)
        return LocationQuality.GOOD;
    if (location.getAccuracy() <= ACCEPTED_ACCURACY)
        return LocationQuality.ACCEPTED;
    return LocationQuality.BAD;
}

private synchronized void updateBestLocation(Location location) {
    bestLocation = getBestLocation(location, bestLocation);
}

// Pretty much an unmodified version of googles example
protected Location getBestLocation(Location location,
        Location currentBestLocation) {
    if (currentBestLocation == null) {
        // A new location is always better than no location
        return location;
    }
    if (location == null) return currentBestLocation;
    // Check whether the new location fix is newer or older
    long timeDelta = location.getTime() - currentBestLocation.getTime();
    boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
    boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
    boolean isNewer = timeDelta > 0;
    // If it's been more than two minutes since the current location, use
    // the new location
    // because the user has likely moved
    if (isSignificantlyNewer) {
        return location;
        // If the new location is more than two minutes older, it must be
        // worse
    } else if (isSignificantlyOlder) {
        return currentBestLocation;
    }
    // Check whether the new location fix is more or less accurate
    int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation
            .getAccuracy());
    boolean isLessAccurate = accuracyDelta > 0;
    boolean isMoreAccurate = accuracyDelta < 0;
    boolean isSignificantlyLessAccurate = accuracyDelta > 200;
    // Check if the old and new location are from the same provider
    boolean isFromSameProvider = isSameProvider(location.getProvider(),
            currentBestLocation.getProvider());
    // Determine location quality using a combination of timeliness and
    // accuracy
    if (isMoreAccurate) {
        return location;
    } else if (isNewer && !isLessAccurate) {
        return location;
    } else if (isNewer && !isSignificantlyLessAccurate
            && isFromSameProvider) {
        return location;
    }
    return bestLocation;
}

/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
    if (provider1 == null) {
        return provider2 == null;
    }
    return provider1.equals(provider2);
}

7
È arrivato molto tardi, ma il "Fused Location Provider" che è stato recentemente annunciato all'IO 2013 sembra rispondere a molte delle tue esigenze - developer.android.com/google/play-services/location.html
Matt

l'ultima riga di getBestLocation () non dovrebbe essere: return currentBestLocation; anziché restituire bestLocation ;?
Gavriel,

Risposte:


164

Sembra che stiamo codificando la stessa applicazione ;-)
Ecco la mia attuale implementazione. Sono ancora nella fase di beta test della mia app 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 dai fornitori di servizi di localizzazione:

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 aggiornamenti GPS troppo spesso, scarica la batteria. Attualmente utilizzo 30 min come impostazione predefinita per la mia applicazione.

  • aggiungere un controllo "distanza minima all'ultima posizione nota". senza questo, i tuoi punti "salteranno" quando il GPS non è disponibile e la posizione viene triangolata dalle torri cellulari. oppure puoi verificare se la nuova posizione è al di fuori del valore di precisione dall'ultima posizione nota.


2
In realtà non ottieni mai una nuova posizione, usi solo le posizioni che si trovano lì da precedenti aggiornamenti. Penso che questo codice trarrebbe grandi benefici aggiungendo effettivamente un ascoltatore che aggiorna la posizione attivando di tanto in tanto il GPS.
Nicklas A.

2
scusa, pensavo fossi interessato solo alla parte che seleziona la migliore tra tutte le posizioni disponibili. Ho aggiunto il codice sopra che richiede anche questi. se viene ricevuta una nuova posizione GPS, questa viene archiviata / caricata immediatamente. se ricevo un aggiornamento della posizione di rete, lo conservo come riferimento e 'spero' di ricevere anche un aggiornamento GPS fino al successivo controllo della posizione.
Gryphius,

2
Ho anche avuto un metodo stopRecording () che ha annullato il timer. Alla fine sono passato da un timer a un ScheduledThreadPoolExecutor, quindi stopRecording ora praticamente chiamaecutor.shutdown () e annulla la registrazione di tutti i listener di aggiornamento della posizione
Gryphius

1
secondo il mio scm, stopRecording si chiamava solo gpsTimer.cancel () e imposta gps_recorder_running = false, quindi come nel tuo caso, allora nessuna pulizia degli ascoltatori. il codice attuale tiene traccia di tutti gli ascoltatori attivi in ​​un vettore, non avevo questo quando ho scritto questa risposta 1,5 anni fa.
Gryphius

1
è già su github , ma non sono sicuro che questo sia ancora il modo migliore per fare cose GPS al giorno d'oggi. Dopo aver apportato molti miglioramenti all'API di localizzazione da quando ho scritto questo codice.
Gryphius,

33

Per selezionare il giusto fornitore di posizione per la tua app, puoi usare gli oggetti Criteria :

Criteria myCriteria = new Criteria();
myCriteria.setAccuracy(Criteria.ACCURACY_HIGH);
myCriteria.setPowerRequirement(Criteria.POWER_LOW);
// let Android select the right location provider for you
String myProvider = locationManager.getBestProvider(myCriteria, true); 

// finally require updates at -at least- the desired rate
long minTimeMillis = 600000; // 600,000 milliseconds make 10 minutes
locationManager.requestLocationUpdates(myProvider,minTimeMillis,0,locationListener); 

Leggi la documentazione per requestLocationUpdates per maggiori dettagli su come vengono presi in considerazione gli argomenti:

La frequenza di notifica può essere controllata utilizzando i parametri minTime e minDistance. Se minTime è maggiore di 0, il LocationManager potrebbe potenzialmente riposare per minTime millisecondi tra gli aggiornamenti della posizione per risparmiare energia. Se minDistance è maggiore di 0, una posizione verrà trasmessa solo se il dispositivo si sposta di minDistance meter. Per ottenere le notifiche il più frequentemente possibile, impostare entrambi i parametri su 0.

Più pensieri

  • È possibile monitorare l'accuratezza degli oggetti Posizione con Location.getAccuracy () , che restituisce l'accuratezza stimata della posizione in metri.
  • il Criteria.ACCURACY_HIGH criterio dovrebbe fornire errori al di sotto di 100 m, che non è buono come può essere il GPS, ma soddisfa le tue esigenze.
  • È inoltre necessario monitorare lo stato del proprio fornitore di servizi di localizzazione e passare a un altro fornitore se non è disponibile o disabilitato dall'utente.
  • Il provider passivo può anche essere una buona corrispondenza per questo tipo di applicazione: l'idea è quella di utilizzare gli aggiornamenti di posizione ogni volta che vengono richiesti da un'altra app e trasmessi in tutto il sistema.

Ho esaminato Criteriama cosa succede se l'ultima posizione della rete è fantastica (potrebbe sapere tramite wifi) e non ci vuole tempo o batteria per ottenerla (getLastKnown), quindi i criteri probabilmente lo ignoreranno e restituiranno il GPS. Non posso credere che Google abbia reso questo difficile per gli sviluppatori.
Nicklas A.

Oltre a utilizzare i criteri, è possibile, ad ogni aggiornamento di posizione inviato dal provider selezionato, controllare lastKnowLocation per il provider GPS e confrontarlo (precisione e data) con la posizione corrente. Ma questo mi sembra un piacere piuttosto che un requisito dalle vostre specifiche; se a volte viene raggiunta una precisione un po 'migliore, sarà davvero utile per i tuoi utenti?
Stéphane,

Questo è quello che sto facendo ora, il problema è che ho difficoltà a capire se l'ultimo sapere è abbastanza buono. Posso anche aggiungere che non devo limitarmi a un provider, più uso più velocemente potrei ottenere un blocco.
Nicklas A.

Tieni presente che PASSIVE_PROVIDER richiede API Level 8 o superiore.
Eduardo,

@ Stéphane scusa per la modifica. Non preoccuparti. Il tuo post è corretto. Ho fatto quella modifica per errore. Scusate. Saluti.
Gaucho,

10

Rispondere ai primi due punti :

  • Il GPS ti darà sempre una posizione più precisa, se è abilitato e se non ci sono muri spessi intorno .

  • Se la posizione non è cambiata, è possibile chiamare getLastKnownLocation (String) e recuperare immediatamente la posizione.

Utilizzando un approccio alternativo :

Puoi provare a utilizzare l' id cella in uso o tutte le celle vicine

TelephonyManager mTelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
GsmCellLocation loc = (GsmCellLocation) mTelephonyManager.getCellLocation(); 
Log.d ("CID", Integer.toString(loc.getCid()));
Log.d ("LAC", Integer.toString(loc.getLac()));
// or 
List<NeighboringCellInfo> list = mTelephonyManager.getNeighboringCellInfo ();
for (NeighboringCellInfo cell : list) {
    Log.d ("CID", Integer.toString(cell.getCid()));
    Log.d ("LAC", Integer.toString(cell.getLac()));
}

È quindi possibile fare riferimento alla posizione della cella attraverso diversi database aperti (ad esempio, http://www.location-api.com/ o http://opencellid.org/ )


La strategia sarebbe quella di leggere l'elenco degli ID torre durante la lettura della posizione. Quindi, nella query successiva (10 minuti nella tua app), rileggili. Se almeno alcune torri sono uguali, allora è sicuro da usare getLastKnownLocation(String). Se non lo sono, allora aspetta onLocationChanged(). Questo evita la necessità di un database di terze parti per la posizione. Puoi anche provare questo approccio .


Sì, ma io il problema arriva se lastKnownLocation è davvero male. Ho bisogno di un buon modo per decidere il meglio di due posizioni.
Nicklas A.

È possibile memorizzare le informazioni sulle torri e verificare se tali torri sono state modificate. In tal caso, attendere una nuova posizione, in caso contrario (o se solo alcune sono state modificate), quindi riutilizzarla. In questo modo si evita di confrontare le posizioni della torre con un database.
Aleadam,

L'uso delle torri mi sembra un grosso problema, buona idea però.
Nicklas A.

@Nicklas il codice non diventa più complicato di così. Tuttavia, avrai bisogno di android.Manifest.permission # ACCESS_COARSE_UPDATES.
Aleadam,

Sì, ma ho ancora bisogno di utilizzare un servizio di terze parti e ho anche bisogno di un modo per decidere quando utilizzare le informazioni della torre sui dati di posizione, questo aggiunge solo un ulteriore livello di complessità.
Nicklas A.

9

Questa è la mia soluzione che funziona abbastanza bene:

private Location bestLocation = null;
private Looper looper;
private boolean networkEnabled = false, gpsEnabled = false;

private synchronized void setLooper(Looper looper) {
    this.looper = looper;
}

private synchronized void stopLooper() {
    if (looper == null) return;
    looper.quit();
}

@Override
protected void runTask() {
    final LocationManager locationManager = (LocationManager) service
            .getSystemService(Context.LOCATION_SERVICE);
    final SharedPreferences prefs = getPreferences();
    final int maxPollingTime = Integer.parseInt(prefs.getString(
            POLLING_KEY, "0"));
    final int desiredAccuracy = Integer.parseInt(prefs.getString(
            DESIRED_KEY, "0"));
    final int acceptedAccuracy = Integer.parseInt(prefs.getString(
            ACCEPTED_KEY, "0"));
    final int maxAge = Integer.parseInt(prefs.getString(AGE_KEY, "0"));
    final String whichProvider = prefs.getString(PROVIDER_KEY, "any");
    final boolean canUseGps = whichProvider.equals("gps")
            || whichProvider.equals("any");
    final boolean canUseNetwork = whichProvider.equals("network")
            || whichProvider.equals("any");
    if (canUseNetwork)
        networkEnabled = locationManager
                .isProviderEnabled(LocationManager.NETWORK_PROVIDER);
    if (canUseGps)
        gpsEnabled = locationManager
                .isProviderEnabled(LocationManager.GPS_PROVIDER);
    // If any provider is enabled now and we displayed a notification clear it.
    if (gpsEnabled || networkEnabled) removeErrorNotification();
    if (gpsEnabled)
        updateBestLocation(locationManager
                .getLastKnownLocation(LocationManager.GPS_PROVIDER));
    if (networkEnabled)
        updateBestLocation(locationManager
                .getLastKnownLocation(LocationManager.NETWORK_PROVIDER));
    if (desiredAccuracy == 0
            || getLocationQuality(desiredAccuracy, acceptedAccuracy,
                    maxAge, bestLocation) != LocationQuality.GOOD) {
        // Define a listener that responds to location updates
        LocationListener locationListener = new LocationListener() {

            public void onLocationChanged(Location location) {
                updateBestLocation(location);
                if (desiredAccuracy != 0
                        && getLocationQuality(desiredAccuracy,
                                acceptedAccuracy, maxAge, bestLocation)
                                == LocationQuality.GOOD)
                    stopLooper();
            }

            public void onProviderEnabled(String provider) {
                if (isSameProvider(provider,
                        LocationManager.NETWORK_PROVIDER))networkEnabled =true;
                else if (isSameProvider(provider,
                        LocationManager.GPS_PROVIDER)) gpsEnabled = true;
                // The user has enabled a location, remove any error
                // notification
                if (canUseGps && gpsEnabled || canUseNetwork
                        && networkEnabled) removeErrorNotification();
            }

            public void onProviderDisabled(String provider) {
                if (isSameProvider(provider,
                        LocationManager.NETWORK_PROVIDER))networkEnabled=false;
                else if (isSameProvider(provider,
                        LocationManager.GPS_PROVIDER)) gpsEnabled = false;
                if (!gpsEnabled && !networkEnabled) {
                    showErrorNotification();
                    stopLooper();
                }
            }

            public void onStatusChanged(String provider, int status,
                    Bundle extras) {
                Log.i(LOG_TAG, "Provider " + provider + " statusChanged");
                if (isSameProvider(provider,
                        LocationManager.NETWORK_PROVIDER)) networkEnabled = 
                        status == LocationProvider.AVAILABLE
                        || status == LocationProvider.TEMPORARILY_UNAVAILABLE;
                else if (isSameProvider(provider,
                        LocationManager.GPS_PROVIDER))
                    gpsEnabled = status == LocationProvider.AVAILABLE
                      || status == LocationProvider.TEMPORARILY_UNAVAILABLE;
                // None of them are available, stop listening
                if (!networkEnabled && !gpsEnabled) {
                    showErrorNotification();
                    stopLooper();
                }
                // The user has enabled a location, remove any error
                // notification
                else if (canUseGps && gpsEnabled || canUseNetwork
                        && networkEnabled) removeErrorNotification();
            }
        };
        if (networkEnabled || gpsEnabled) {
            Looper.prepare();
            setLooper(Looper.myLooper());
            // Register the listener with the Location Manager to receive
            // location updates
            if (canUseGps)
                locationManager.requestLocationUpdates(
                        LocationManager.GPS_PROVIDER, 1000, 1,
                        locationListener, Looper.myLooper());
            if (canUseNetwork)
                locationManager.requestLocationUpdates(
                        LocationManager.NETWORK_PROVIDER, 1000, 1,
                        locationListener, Looper.myLooper());
            Timer t = new Timer();
            t.schedule(new TimerTask() {

                @Override
                public void run() {
                    stopLooper();
                }
            }, maxPollingTime * 1000);
            Looper.loop();
            t.cancel();
            setLooper(null);
            locationManager.removeUpdates(locationListener);
        } else // No provider is enabled, show a notification
        showErrorNotification();
    }
    if (getLocationQuality(desiredAccuracy, acceptedAccuracy, maxAge,
            bestLocation) != LocationQuality.BAD) {
        sendUpdate(new Event(EVENT_TYPE, locationToString(desiredAccuracy,
                acceptedAccuracy, maxAge, bestLocation)));
    } else Log.w(LOG_TAG, "LocationCollector failed to get a location");
}

private synchronized void showErrorNotification() {
    if (notifId != 0) return;
    ServiceHandler handler = service.getHandler();
    NotificationInfo ni = NotificationInfo.createSingleNotification(
            R.string.locationcollector_notif_ticker,
            R.string.locationcollector_notif_title,
            R.string.locationcollector_notif_text,
            android.R.drawable.stat_notify_error);
    Intent intent = new Intent(
            android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
    ni.pendingIntent = PendingIntent.getActivity(service, 0, intent,
            PendingIntent.FLAG_UPDATE_CURRENT);
    Message msg = handler.obtainMessage(ServiceHandler.SHOW_NOTIFICATION);
    msg.obj = ni;
    handler.sendMessage(msg);
    notifId = ni.id;
}

private void removeErrorNotification() {
    if (notifId == 0) return;
    ServiceHandler handler = service.getHandler();
    if (handler != null) {
        Message msg = handler.obtainMessage(
                ServiceHandler.CLEAR_NOTIFICATION, notifId, 0);
        handler.sendMessage(msg);
        notifId = 0;
    }
}

@Override
public void interrupt() {
    stopLooper();
    super.interrupt();
}

private String locationToString(int desiredAccuracy, int acceptedAccuracy,
        int maxAge, Location location) {
    StringBuilder sb = new StringBuilder();
    sb.append(String.format(
            "qual=%s time=%d prov=%s acc=%.1f lat=%f long=%f",
            getLocationQuality(desiredAccuracy, acceptedAccuracy, maxAge,
                    location), location.getTime() / 1000, // Millis to
                                                            // seconds
            location.getProvider(), location.getAccuracy(), location
                    .getLatitude(), location.getLongitude()));
    if (location.hasAltitude())
        sb.append(String.format(" alt=%.1f", location.getAltitude()));
    if (location.hasBearing())
        sb.append(String.format(" bearing=%.2f", location.getBearing()));
    return sb.toString();
}

private enum LocationQuality {
    BAD, ACCEPTED, GOOD;

    public String toString() {
        if (this == GOOD) return "Good";
        else if (this == ACCEPTED) return "Accepted";
        else return "Bad";
    }
}

private LocationQuality getLocationQuality(int desiredAccuracy,
        int acceptedAccuracy, int maxAge, Location location) {
    if (location == null) return LocationQuality.BAD;
    if (!location.hasAccuracy()) return LocationQuality.BAD;
    long currentTime = System.currentTimeMillis();
    if (currentTime - location.getTime() < maxAge * 1000
            && location.getAccuracy() <= desiredAccuracy)
        return LocationQuality.GOOD;
    if (acceptedAccuracy == -1
            || location.getAccuracy() <= acceptedAccuracy)
        return LocationQuality.ACCEPTED;
    return LocationQuality.BAD;
}

private synchronized void updateBestLocation(Location location) {
    bestLocation = getBestLocation(location, bestLocation);
}

protected Location getBestLocation(Location location,
        Location currentBestLocation) {
    if (currentBestLocation == null) {
        // A new location is always better than no location
        return location;
    }
    if (location == null) return currentBestLocation;
    // Check whether the new location fix is newer or older
    long timeDelta = location.getTime() - currentBestLocation.getTime();
    boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
    boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
    boolean isNewer = timeDelta > 0;
    // If it's been more than two minutes since the current location, use
    // the new location
    // because the user has likely moved
    if (isSignificantlyNewer) {
        return location;
        // If the new location is more than two minutes older, it must be
        // worse
    } else if (isSignificantlyOlder) {
        return currentBestLocation;
    }
    // Check whether the new location fix is more or less accurate
    int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation
            .getAccuracy());
    boolean isLessAccurate = accuracyDelta > 0;
    boolean isMoreAccurate = accuracyDelta < 0;
    boolean isSignificantlyLessAccurate = accuracyDelta > 200;
    // Check if the old and new location are from the same provider
    boolean isFromSameProvider = isSameProvider(location.getProvider(),
            currentBestLocation.getProvider());
    // Determine location quality using a combination of timeliness and
    // accuracy
    if (isMoreAccurate) {
        return location;
    } else if (isNewer && !isLessAccurate) {
        return location;
    } else if (isNewer && !isSignificantlyLessAccurate
            && isFromSameProvider) {
        return location;
    }
    return bestLocation;
}

/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
    if (provider1 == null) return provider2 == null;
    return provider1.equals(provider2);
}

Ciao Nicklas, ho la stessa condizione, così potrei comunicarti con ogni mezzo .. Ti sarei grato se potessi aiutarci ..
School Boy,

Potresti pubblicare l'intero codice? Grazie, davvero apprezzato
rodi,

Questo è tutto il codice. Non ho più accesso al progetto.
Nicklas A.

1
Sembra che tu abbia preso il codice di questo progetto "android-protips-location" ed è ancora vivo. Le persone possono vedere come funziona qui code.google.com/p/android-protips-location/source/browse/trunk/…
Gödel77

7

La precisione della posizione dipende principalmente dal fornitore di posizione utilizzato:

  1. GPS: ti garantirà una precisione di diversi metri (supponendo che tu abbia la ricezione GPS)
  2. Wifi: ti garantirà una precisione di poche centinaia di metri
  3. Rete cellulare - Otterrai risultati molto imprecisi (ho visto una deviazione fino a 4 km ...)

Se è la precisione che stai cercando, il GPS è l'unica opzione.

Ho letto un articolo molto informativo al riguardo qui .

Per quanto riguarda il timeout GPS - 60 secondi dovrebbero essere sufficienti e nella maggior parte dei casi anche troppo. Penso che 30 secondi sia OK e talvolta anche meno di 5 secondi ...

se hai bisogno di un'unica posizione, ti suggerirei che nel tuo onLocationChangedmetodo, una volta ricevuto un aggiornamento, annulli la registrazione dell'ascoltatore ed eviterai un uso non necessario del GPS.


Non mi interessa davvero da dove ottengo la mia posizione, non voglio limitarmi a un fornitore
Nicklas A.

Puoi registrare tutti i provider di posizione disponibili sul dispositivo (puoi ottenere l'elenco di tutti i provider da LocationManager.getProviders ()), ma se stai cercando una soluzione accurata, nella maggior parte dei casi il provider di rete non ti sarà utile.
Muzikant,

Sì, ma questa non è una domanda sulla scelta tra i fornitori, questa è una domanda su come ottenere la migliore posizione in generale (anche quando ci sono più fornitori coinvolti)
Nicklas A.

4

Attualmente sto usando poiché questo è affidabile per ottenere la posizione e il calcolo della distanza per la mia applicazione ...... Sto usando questo per la mia applicazione di taxi.

utilizzare l'API di fusione sviluppata dallo sviluppatore di Google con la fusione di Sensore GPS, Magnetometro, Accelerometro utilizzando anche Wifi o la posizione della cella per calcolare o stimare la posizione. È anche in grado di fornire con precisione aggiornamenti di posizione anche all'interno dell'edificio. per i dettagli ottenere il collegamento https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderApi

import android.app.Activity;
import android.location.Location;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


public class MainActivity extends Activity implements LocationListener,
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {

    private static final long ONE_MIN = 500;
    private static final long TWO_MIN = 500;
    private static final long FIVE_MIN = 500;
    private static final long POLLING_FREQ = 1000 * 20;
    private static final long FASTEST_UPDATE_FREQ = 1000 * 5;
    private static final float MIN_ACCURACY = 1.0f;
    private static final float MIN_LAST_READ_ACCURACY = 1;

    private LocationRequest mLocationRequest;
    private Location mBestReading;
TextView tv;
    private GoogleApiClient mGoogleApiClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (!servicesAvailable()) {
            finish();
        }

        setContentView(R.layout.activity_main);
tv= (TextView) findViewById(R.id.tv1);
        mLocationRequest = LocationRequest.create();
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        mLocationRequest.setInterval(POLLING_FREQ);
        mLocationRequest.setFastestInterval(FASTEST_UPDATE_FREQ);

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addApi(LocationServices.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();


        if (mGoogleApiClient != null) {
            mGoogleApiClient.connect();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (mGoogleApiClient != null) {
            mGoogleApiClient.connect();
        }
    }

    @Override
    protected void onPause() {d
        super.onPause();

        if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
            mGoogleApiClient.disconnect();
        }
    }


        tv.setText(location + "");
        // Determine whether new location is better than current best
        // estimate
        if (null == mBestReading || location.getAccuracy() < mBestReading.getAccuracy()) {
            mBestReading = location;


            if (mBestReading.getAccuracy() < MIN_ACCURACY) {
                LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
            }
        }
    }

    @Override
    public void onConnected(Bundle dataBundle) {
        // Get first reading. Get additional location updates if necessary
        if (servicesAvailable()) {

            // Get best last location measurement meeting criteria
            mBestReading = bestLastKnownLocation(MIN_LAST_READ_ACCURACY, FIVE_MIN);

            if (null == mBestReading
                    || mBestReading.getAccuracy() > MIN_LAST_READ_ACCURACY
                    || mBestReading.getTime() < System.currentTimeMillis() - TWO_MIN) {

                LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);

               //Schedule a runnable to unregister location listeners

                    @Override
                    public void run() {
                        LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, MainActivity.this);

                    }

                }, ONE_MIN, TimeUnit.MILLISECONDS);

            }

        }
    }

    @Override
    public void onConnectionSuspended(int i) {

    }


    private Location bestLastKnownLocation(float minAccuracy, long minTime) {
        Location bestResult = null;
        float bestAccuracy = Float.MAX_VALUE;
        long bestTime = Long.MIN_VALUE;

        // Get the best most recent location currently available
        Location mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
        //tv.setText(mCurrentLocation+"");
        if (mCurrentLocation != null) {
            float accuracy = mCurrentLocation.getAccuracy();
            long time = mCurrentLocation.getTime();

            if (accuracy < bestAccuracy) {
                bestResult = mCurrentLocation;
                bestAccuracy = accuracy;
                bestTime = time;
            }
        }

        // Return best reading or null
        if (bestAccuracy > minAccuracy || bestTime < minTime) {
            return null;
        }
        else {
            return bestResult;
        }
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {

    }

    private boolean servicesAvailable() {
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);

        if (ConnectionResult.SUCCESS == resultCode) {
            return true;
        }
        else {
            GooglePlayServicesUtil.getErrorDialog(resultCode, this, 0).show();
            return false;
        }
    }
}

2

Ho cercato su Internet una risposta aggiornata (l'anno scorso) utilizzando gli ultimi metodi di estrazione della posizione suggeriti da Google (per utilizzare FusedLocationProviderClient). Alla fine sono arrivato su questo:

https://github.com/googlesamples/android-play-location/tree/master/LocationUpdates

Ho creato un nuovo progetto e copiato nella maggior parte di questo codice. Boom. Funziona. E penso senza linee deprecate.

Inoltre, il simulatore non sembra ottenere una posizione GPS, di cui sono a conoscenza. È arrivato al punto di riportarlo nel registro: "Tutte le impostazioni della posizione sono soddisfatte".

E infine, nel caso volessi sapere (l'ho fatto), NON hai bisogno di una chiave API di google maps dalla console degli sviluppatori di google, se tutto ciò che vuoi è la posizione GPS.

Utile anche il loro tutorial. Ma volevo un intero tutorial / codice di una pagina di esempio, e quello. Il loro tutorial si accumula ma è confuso quando sei nuovo a questo perché non sai di quali pezzi hai bisogno dalle pagine precedenti.

https://developer.android.com/training/location/index.html

E infine, ricorda cose come questa:

Non ho dovuto solo modificare mainActivity.Java. Ho anche dovuto modificare Strings.xml, androidmanifest.xml E il build.gradle corretto. E anche la tua activity_Main.xml (ma quella parte è stata facile per me).

Avevo bisogno di aggiungere dipendenze come questa: implementazione "com.google.android.gms: play-services-location: 11.8.0" e aggiornare le impostazioni del mio SDK Android Studio per includere i servizi di Google Play. (impostazioni del file aspetto impostazioni di sistema android SDK SDK Tools controlla i servizi di Google play).

aggiornamento: il simulatore Android sembrava ottenere una posizione e eventi di cambio di posizione (quando ho cambiato il valore nelle impostazioni della sim). Ma i miei risultati migliori e primi sono stati su un dispositivo reale. Quindi è probabilmente più semplice testare su dispositivi reali.


1

Recentemente refactored per ottenere l'ubicazione del codice, apprendere alcune buone idee e infine ottenere una libreria e una demo relativamente perfette.

@ La risposta di Gryphius è buona

    //request all valid provider(network/gps)
private boolean requestAllProviderUpdates() {
    checkRuntimeEnvironment();
    checkPermission();

    if (isRequesting) {
        EasyLog.d("Request location update is busy");
        return false;
    }


    long minTime = getCheckTimeInterval();
    float minDistance = getCheckMinDistance();

    if (mMapLocationListeners == null) {
        mMapLocationListeners = new HashMap<>();
    }

    mValidProviders = getValidProviders();
    if (mValidProviders == null || mValidProviders.isEmpty()) {
        throw new IllegalArgumentException("Not available provider.");
    }

    for (String provider : mValidProviders) {
        LocationListener locationListener = new LocationListener() {
            @Override
            public void onLocationChanged(Location location) {
                if (location == null) {
                    EasyLog.e("LocationListener callback location is null.");
                    return;
                }
                printf(location);
                mLastProviderTimestamp = location.getTime();

                if (location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
                    finishResult(location);
                } else {
                    doLocationResult(location);
                }

                removeProvider(location.getProvider());
                if (isEmptyValidProviders()) {
                    requestTimeoutMsgInit();
                    removeUpdates();
                }
            }

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

            @Override
            public void onProviderEnabled(String provider) {
            }

            @Override
            public void onProviderDisabled(String provider) {
            }
        };
        getLocationManager().requestLocationUpdates(provider, minTime, minDistance, locationListener);
        mMapLocationListeners.put(provider, locationListener);
        EasyLog.d("Location request %s provider update.", provider);
    }
    isRequesting = true;
    return true;
}

//remove request update
public void removeUpdates() {
    checkRuntimeEnvironment();

    LocationManager locationManager = getLocationManager();
    if (mMapLocationListeners != null) {
        Set<String> keys = mMapLocationListeners.keySet();
        for (String key : keys) {
            LocationListener locationListener = mMapLocationListeners.get(key);
            if (locationListener != null) {
                locationManager.removeUpdates(locationListener);
                EasyLog.d("Remove location update, provider is " + key);
            }
        }
        mMapLocationListeners.clear();
        isRequesting = false;
    }
}

//Compared with the last successful position, to determine whether you need to filter
private boolean isNeedFilter(Location location) {
    checkLocation(location);

    if (mLastLocation != null) {
        float distance = location.distanceTo(mLastLocation);
        if (distance < getCheckMinDistance()) {
            return true;
        }
        if (location.getAccuracy() >= mLastLocation.getAccuracy()
                && distance < location.getAccuracy()) {
            return true;
        }
        if (location.getTime() <= mLastProviderTimestamp) {
            return true;
        }
    }
    return false;
}

private void doLocationResult(Location location) {
    checkLocation(location);

    if (isNeedFilter(location)) {
        EasyLog.d("location need to filtered out, timestamp is " + location.getTime());
        finishResult(mLastLocation);
    } else {
        finishResult(location);
    }
}

//Return to the finished position
private void finishResult(Location location) {
    checkLocation(location);

    double latitude = location.getLatitude();
    double longitude = location.getLongitude();
    float accuracy = location.getAccuracy();
    long time = location.getTime();
    String provider = location.getProvider();

    if (mLocationResultListeners != null && !mLocationResultListeners.isEmpty()) {
        String format = "Location result:<%f, %f> Accuracy:%f Time:%d Provider:%s";
        EasyLog.i(String.format(format, latitude, longitude, accuracy, time, provider));

        mLastLocation = location;
        synchronized (this) {
            Iterator<LocationResultListener> iterator =  mLocationResultListeners.iterator();
            while (iterator.hasNext()) {
                LocationResultListener listener = iterator.next();
                if (listener != null) {
                    listener.onResult(location);
                }
                iterator.remove();
            }
        }
    }
}

Implementazione completa: https://github.com/bingerz/FastLocation/blob/master/fastlocationlib/src/main/java/cn/bingerz/fastlocation/FastLocation.java

1. Grazie a idee di soluzioni @Gryphius, condivido anche il codice completo.

2.Ogni richiesta per completare la posizione, è meglio rimuovere Aggiornamenti, altrimenti la barra di stato del telefono mostrerà sempre l'icona di posizionamento


0

Nella mia esperienza, ho trovato la soluzione migliore per la correzione GPS, a meno che non sia disponibile. Non so molto su altri fornitori di servizi di localizzazione, ma so che per il GPS ci sono alcuni trucchi che possono essere usati per dare un po 'di una misura di precisione del ghetto. L'altitudine è spesso un segno, quindi potresti verificare valori ridicoli. C'è la misura della precisione nelle correzioni della posizione Android. Inoltre, se puoi vedere il numero di satelliti utilizzati, questo può anche indicare la precisione.

Un modo interessante per avere un'idea migliore dell'accuratezza potrebbe essere quello di richiedere una serie di correzioni molto rapidamente, come ~ 1 / sec per 10 secondi e poi dormire per un minuto o due. Un discorso a cui sono stato ha portato a credere che alcuni dispositivi Android lo faranno comunque. Dovresti quindi eliminare gli outlier (ho sentito il filtro Kalman menzionato qui) e utilizzare una sorta di strategia di centraggio per ottenere una singola correzione.

Ovviamente la profondità che si arriva qui dipende da quanto sono difficili le vostre esigenze. Se hai requisiti particolarmente severi per ottenere la MIGLIORE posizione possibile, penso che scoprirai che il GPS e la posizione della rete sono simili a mele e arance. Inoltre, il GPS può essere molto diverso da dispositivo a dispositivo.


Bene, non è importante che sia il migliore, solo che è abbastanza buono da tracciare su una mappa e che non esaurisco la batteria poiché si tratta di un'attività in background.
Nicklas A.

-3

Skyhook (http://www.skyhookwireless.com/) ha un fornitore di servizi di localizzazione che è molto più veloce di quello standard fornito da Google. Potrebbe essere quello che stai cercando. Non sono affiliato con loro.


Interessante in effetti, sembrano usare solo il WiFi, il che è molto bello, ma ho ancora bisogno che funzioni quando non c'è connessione wifi o 3G / 2G in giro, quindi questo aggiungerebbe un altro livello di astrazione. Buona cattura però.
Nicklas A.

1
Skyhook sembra utilizzare una combinazione di WiFi, GPS e torri cellulari. Vedi skyhookwireless.com/howitworks per i dettagli tecnici. Recentemente hanno ottenuto diverse vittorie nel design, ad esempio Mapquest, Twydroid, ShopSavvy e Sony NGP. Tieni presente che il download e il test dell'SDK sembrano essere gratuiti, ma devi contattarli in merito a una licenza per distribuirlo nella tua app. Purtroppo non elencano il prezzo sul loro sito web.
Ed Burnette,

Oh, capisco. Bene, se non è libero da usare commercialmente, temo di non poterlo usare.
Nicklas A.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.