web-dev-qa-db-fra.com

Bonne manière d'obtenir l'emplacement de l'utilisateur dans Android

Le problème:

Obtenir l'emplacement actuel de l'utilisateur dans les limites du seuil et économiser la batterie en même temps.

Pourquoi le problème est un problème:

Tout d’abord, Android a deux fournisseurs; réseau et GPS. Parfois, le réseau est meilleur et parfois le GPS est meilleur.

Par "meilleur", je veux dire rapport vitesse/précision.
Je suis prêt à sacrifier la précision de quelques mètres si je peux obtenir la position presque instantanément et sans activer le GPS.

Deuxièmement, si vous demandez des mises à jour pour les changements d'emplacement, rien n'est envoyé si l'emplacement actuel est stable.

Google a un exemple de détermination du "meilleur" emplacement ici: http://developer.Android.com/guide/topics/location/obtaining-user-location.html#BestEstimate
Mais je pense que rien n’est aussi bon qu’il devrait/pourrait être.

Je ne comprends pas trop pourquoi Google n'a pas d'API normalisée pour l'emplacement. Le développeur ne devrait pas avoir à se soucier de l'origine de l'emplacement. Vous devez simplement spécifier ce que vous voulez et le téléphone doit choisir pour vous.

Qu'est-ce que j'ai besoin d'aide avec:

Je dois trouver un bon moyen de déterminer le "meilleur" emplacement, peut-être à l'aide d'une méthode heuristique ou peut-être par l'intermédiaire d'une bibliothèque tierce.

Cela ne signifie pas déterminer le meilleur fournisseur!
Je vais probablement utiliser tous les prestataires et choisir le meilleur d'entre eux.

Fond de l'application:

L'application collecte l'emplacement de l'utilisateur à un intervalle fixe (toutes les 10 minutes environ) et l'envoie à un serveur.
L’application doit conserver autant de batterie que possible et l’emplacement doit présenter une précision de X (50-100?).

L'objectif est de pouvoir ultérieurement tracer le chemin de l'utilisateur au cours de la journée sur une carte, de sorte que j'ai besoin de suffisamment de précision pour cela.

Misc:

Selon vous, quelles sont les valeurs raisonnables pour les précisions souhaitées et acceptées?
J'ai utilisé 100m comme accepté et 30m comme souhaité, est-ce trop demander?
J'aimerais pouvoir tracer le chemin de l'utilisateur sur une carte ultérieurement.
Est-ce que 100 m pour le désiré et 500 m pour le mieux accepté?

De plus, le GPS est activé pendant au maximum 60 secondes par mise à jour de lieu. Est-ce trop court pour obtenir un lieu si vous êtes à l'intérieur avec une précision de peut-être 200 m?


Ceci est mon code actuel, tout retour est apprécié (mis à part le manque de vérification d'erreur qui est 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);
}
210
Nicklas A.

On dirait que nous codons la même application ;-)
Voici ma mise en œuvre actuelle. Je suis toujours dans la phase de test bêta de mon application de téléverseur GPS. Il pourrait donc y avoir de nombreuses améliorations possibles. mais cela semble assez bien fonctionner jusqu'à présent.

/**
 * 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;
}

Edit: Voici la partie qui demande les mises à jour périodiques aux fournisseurs d'emplacement:

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
}

Points à considérer:

  • ne demandez pas de mises à jour GPS trop souvent, cela draine la batterie. J'utilise actuellement 30 min par défaut pour mon application.

  • ajoutez une vérification de "distance minimale au dernier emplacement connu". sans cela, vos points "sauteront" lorsque le GPS n’est pas disponible et que la position est triangulée à partir des tours cellulaires. ou vous pouvez vérifier si le nouvel emplacement est en dehors de la valeur de précision du dernier emplacement connu.

164
Gryphius

Pour sélectionner le bon fournisseur d'emplacement pour votre application, vous pouvez utiliser Critères objets:

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

Lisez la documentation de requestLocationUpdates pour plus de détails sur la manière dont les arguments sont pris en compte:

La fréquence de notification peut être contrôlée à l'aide des paramètres minTime et minDistance. Si minTime est supérieur à 0, LocationManager pourrait éventuellement rester en attente pendant minTime en millisecondes entre les mises à jour d'emplacement pour économiser l'énergie. Si minDistance est supérieur à 0, un emplacement ne sera diffusé que si le périphérique se déplace à l'aide de minDistance mètres. Pour obtenir des notifications aussi souvent que possible, définissez les deux paramètres sur 0.

Plus de pensées

  • Vous pouvez surveiller la précision des objets Location avec Location.getAccuracy () , qui renvoie la précision estimée de la position en mètres.
  • le critère Criteria.ACCURACY_HIGH devrait vous donner des erreurs inférieures à 100 m, ce qui n’est pas aussi bon que le GPS, mais correspond à vos besoins.
  • Vous devez également surveiller le statut de votre fournisseur de localisation et basculer vers un autre fournisseur s'il devient indisponible ou désactivé par l'utilisateur.
  • Le fournisseur passif peut également convenir à ce type d’application: l’idée est d’utiliser les mises à jour de localisation chaque fois qu’elles sont demandées par une autre application et diffusées à l’échelle du système.
33
Stéphane

Répondant aux deux premiers points :

  • Le GPS vous donnera toujours un emplacement plus précis, s'il est activé et s'il n'y a pas de murs épais autour .

  • Si l'emplacement n'a pas changé, vous pouvez appeler getLastKnownLocation (String) et récupérer l'emplacement immédiatement.

En utilisant une approche alternative :

Vous pouvez essayer d'obtenir l'ID de la cellule ou toutes les cellules voisines

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

Vous pouvez ensuite faire référence à l'emplacement de la cellule par le biais de plusieurs bases de données ouvertes (par exemple, http://www.location-api.com/ ou http://opencellid.org/ ).


La stratégie serait de lire la liste des identifiants de tour lors de la lecture de l'emplacement. Ensuite, dans la requête suivante (10 minutes dans votre application), relisez-les. Si au moins certaines tours sont identiques, vous pouvez utiliser getLastKnownLocation(String) en toute sécurité. Si ce n'est pas le cas, attendez onLocationChanged(). Cela évite le recours à une base de données tierce pour l'emplacement. Vous pouvez également essayer cette approche .

10
Aleadam

Voici ma solution qui fonctionne assez bien:

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);
}
9
Nicklas A.

La précision de la localisation dépend principalement du fournisseur de localisation utilisé:

  1. GPS - vous obtiendrez une précision de plusieurs mètres (en supposant que vous ayez une réception GPS)
  2. Wifi - Vous obtiendrez une précision de quelques centaines de mètres
  3. Réseau cellulaire - Vous obtiendrez des résultats très imprécis (j'ai déjà vu une déviation allant jusqu'à 4 km ...)

Si vous recherchez la précision, le GPS est votre seule option.

J'ai lu un article très informatif à ce sujet ici .

En ce qui concerne le délai d'attente GPS - 60 secondes devraient suffire, voire trop dans la plupart des cas. Je pense que 30 secondes, c'est bien et parfois même moins de 5 secondes ...

si vous n'avez besoin que d'un seul emplacement, je suggérerais que, dans votre méthode onLocationChanged, une fois que vous recevez une mise à jour, vous annulez l'enregistrement de l'auditeur et évitez toute utilisation inutile du GPS.

7
Muzikant

La bibliothèque Android-ReactiveLocation est une autre bibliothèque Nice permettant de gérer la localisation dans Android.

Petite bibliothèque qui englobe l’API du service Google Play dans de brillants observables RxJava, réduisant ainsi le standard au minimum.

https://github.com/mcharmas/Android-ReactiveLocation

3
Hugo Matilla

Actuellement, je l'utilise car c'est fiable pour obtenir l'emplacement et le calcul de la distance pour mon application ...... je l'utilise pour mon application de taxi.

utilisez l’API de fusion que les développeurs Google ont mise au point avec la fusion du capteur GPS, du magnétomètre et de l’accéléromètre utilisant également le Wifi ou la localisation de la cellule pour calculer ou estimer la localisation. Il est également capable de donner des mises à jour de localisation également à l'intérieur du bâtiment avec précision. pour plus de détails, accédez au lien 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;
        }
    }
}
3
AshisParajuli

J'ai parcouru Internet pour trouver une réponse mise à jour (année écoulée) à l'aide des dernières méthodes de localisation suggérées par Google (pour utiliser FusedLocationProviderClient). J'ai finalement atterri sur ceci:

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

J'ai créé un nouveau projet et copié dans la plupart de ce code. Boom. Ça marche. Et je pense sans lignes obsolètes.

De plus, le simulateur ne semble pas avoir de position GPS, à ma connaissance. Il est arrivé jusqu'à signaler dans le journal: "Tous les paramètres d'emplacement sont satisfaits".

Et enfin, au cas où vous voudriez savoir (je l’ai fait), vous n’avez PAS besoin d’une clé d’API Google Maps à partir de la console de développeur Google, si vous ne voulez que l’emplacement GPS.

Aussi utile est leur tutoriel. Mais je voulais un tutoriel complet/exemple de code, et cela. Leur didacticiel s’empile, mais est déroutant lorsque vous débutez dans ce domaine car vous ne savez pas de quels éléments vous avez besoin dans les pages précédentes.

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

Et enfin, rappelez-vous des choses comme celle-ci:

Je n’ai pas seulement dû modifier le fichier mainActivity.Java. J'ai également dû modifier Strings.xml, androidmanifest.xml ET le build.gradle correct. Et aussi votre activity_Main.xml (mais cette partie était facile pour moi).

J'avais besoin d'ajouter des dépendances telles que celle-ci: implémentation 'com.google.Android.gms: play-services-location: 11.8.0', et mettre à jour les paramètres de mon SDK studio Android pour inclure les services de lecture Google . (paramètres de fichier, paramètres système d’apparence Android SDK SDK Tools, vérifiez les services Google Play).

mise à jour: le simulateur Android semblait avoir un emplacement et des événements de changement d'emplacement (lorsque j'ai modifié la valeur dans les paramètres de la sim). Mais mes meilleurs et premiers résultats ont été obtenus avec un appareil réel. Il est donc probablement plus facile de tester sur des appareils réels.

2
Neo42

salut, c’est un lien qui pourra donner l’intégralité du code source pour intégrer celui du lieu gps qui pourra suivre toute personne par le gps et qui vous informera:

comme: http://code.google.com/p/mytracks/

2
Akash Thakkar

Récemment refactorisé pour obtenir l'emplacement du code, apprendre quelques bonnes idées, et enfin obtenir une bibliothèque et une démo relativement parfaites.

@ La réponse de Gryphius est bonne

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

Mise en œuvre complète: https://github.com/bingerz/FastLocation/blob/master/fastlocationlib/src/main/Java/cn/bingerz/fastlocation/FastLocation.Java

1.Merci @Gryphius des idées de solution, je partage également le code complet.

2.Chaque demande de compléter la localisation, il est préférable de supprimer les mises à jour, sinon la barre d'état du téléphone affichera toujours l'icône de positionnement.

1
Bingerz

D'après mon expérience, je trouve qu'il est préférable d'utiliser le repère GPS à moins qu'il ne soit pas disponible. Je ne connais pas grand-chose aux autres fournisseurs d’emplacement, mais je sais que, pour le GPS, il existe quelques astuces qui peuvent être utilisées pour donner un peu de précision au ghetto. L'altitude est souvent un signe, vous pouvez donc vérifier les valeurs ridicules. Il y a la mesure de précision sur Android correctifs de localisation. De plus, si vous pouvez voir le nombre de satellites utilisés, cela peut également indiquer la précision.

Un moyen intéressant d’obtenir une meilleure idée de l’exactitude pourrait être de demander très rapidement une série de correctifs, par exemple ~ 1/s pendant 10 secondes, puis de dormir pendant une minute ou deux. Une conversation à laquelle j'ai assisté a conduit à penser que certains appareils Android le feraient malgré tout. Vous pouvez ensuite éliminer les valeurs aberrantes (le filtre de Kalman est mentionné ici) et utiliser une sorte de stratégie de centrage pour obtenir un correctif unique.

Évidemment, la profondeur que vous obtenez ici dépend de la rigueur de vos exigences. Si vous avez des exigences particulièrement strictes pour obtenir LE MEILLEUR emplacement possible, vous constaterez que le GPS et l’emplacement du réseau sont aussi similaires que des pommes et des oranges. De plus, le GPS peut être très différent d’un appareil à l’autre.

0
tugs