Bon moyen d'obtenir l'emplacement de l'utilisateur dans Android

211

Le problème:

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

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 "mieux", j'entends le rapport vitesse / précision.
Je suis prêt à sacrifier quelques mètres de précision si je peux obtenir l'emplacement presque instantanément et sans allumer le GPS.

Deuxièmement, si vous demandez des mises à jour pour des 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 ce n'est pas aussi bon qu'il le devrait /pourrait être.

Je suis un peu confus pourquoi Google n'a pas d'API normalisée pour la localisation, le développeur ne devrait pas avoir à se soucier de la provenance de l'emplacement, vous devez simplement spécifier ce que vous voulez et le téléphone doit choisir pour vous.

Avec quoi j'ai besoin d'aide:

J'ai besoin de trouver un bon moyen de déterminer le "meilleur" emplacement, peut-être par le biais d'une heuristique ou par le biais d'une bibliothèque tierce.

Cela ne veut pas dire déterminer le meilleur fournisseur!
Je vais probablement utiliser tous les fournisseurs et choisir les meilleurs d'entre eux.

Contexte de l'application:

L'application collectera l'emplacement de l'utilisateur à un intervalle fixe (disons toutes les 10 minutes environ) et l'enverra à un serveur.
L'application doit économiser autant de batterie que possible et l'emplacement doit avoir une précision de X (50-100?) Mètres.

L'objectif est de pouvoir plus tard tracer le chemin de l'utilisateur pendant la journée sur une carte, j'ai donc besoin d'une précision suffisante pour cela.

Divers:

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

De plus, en ce moment, j'ai le GPS allumé pendant un maximum de 60 secondes par mise à jour de position, est-ce trop court pour obtenir une position si vous êtes à l'intérieur avec une précision de peut-être 200 m?


Ceci est mon code actuel, tout commentaire est apprécié (à 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);
}
Nicklas A.
la source
7
Réaction très tardive, mais le "Fused Location Provider" récemment annoncé à IO 2013 semble répondre à bon nombre de vos besoins - developer.android.com/google/play-services/location.html
Matt
la dernière ligne de getBestLocation () ne devrait-elle pas être: return currentBestLocation; au lieu de renvoyer bestLocation ;?
Gavriel

Réponses:

164

On dirait que nous codons la même application ;-)
Voici mon implémentation actuelle. Je suis encore dans la phase de test bêta de mon application de téléchargement 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;
}

Modifier: voici la partie qui demande les mises à jour périodiques aux fournisseurs de localisation:

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
}

Choses à considérer:

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

  • ajouter une vérification «distance minimale au dernier emplacement connu». sans cela, vos points «sauteront» lorsque le GPS n'est pas disponible et que l'emplacement est triangulé à partir des tours de téléphonie cellulaire. ou vous pouvez vérifier si le nouvel emplacement est en dehors de la valeur de précision du dernier emplacement connu.

Gryphius
la source
2
Vous n'obtenez jamais réellement un nouvel emplacement, vous n'utilisez que des emplacements qui se trouvent être là à partir de mises à jour précédentes. Je pense que ce code bénéficierait grandement de l'ajout d'un écouteur qui met à jour l'emplacement en allumant le GPS de temps en temps.
Nicklas A.
2
Désolé, je pensais que vous n'étiez intéressé que par la partie qui sélectionne le meilleur parmi tous les emplacements disponibles. J'ai ajouté le code ci-dessus qui le demande également. si un nouvel emplacement GPS est reçu, il est stocké / téléchargé immédiatement. si je reçois une mise à jour de l'emplacement réseau, je la stocke pour référence et «j'espère» que je recevrai également une mise à jour gps jusqu'à la prochaine vérification de l'emplacement.
Gryphius
2
J'ai également eu une méthode stopRecording () qui a annulé la minuterie. Je suis finalement passé d'un minuteur à un ScheduledThreadPoolExecutor, donc stopRecording appelle maintenant essentiellement executor.shutdown () et désenregistre tous les écouteurs de mise à jour de l'emplacement
Gryphius
1
selon ma scm, stopRecording ne s'appelait que gpsTimer.cancel () et définissait gps_recorder_running = false, donc comme dans votre cas, pas de nettoyage des auditeurs à l'époque. le code actuel garde la trace de tous les auditeurs actifs dans un vecteur, je ne l'avais pas quand j'ai écrit cette réponse il y a 1,5 ans.
Gryphius
1
il est déjà sur github , mais je ne suis pas sûr que ce soit toujours le meilleur moyen de faire des trucs GPS de nos jours. Afaik, ils ont apporté de nombreuses améliorations à l'API de localisation depuis que j'ai écrit ce code.
Gryphius
33

Pour sélectionner le bon fournisseur de localisation pour votre application, vous pouvez utiliser des objets Critères :

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 façon 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, le LocationManager peut potentiellement se reposer pendant minTime millisecondes entre les mises à jour d'emplacement pour économiser l'énergie. Si minDistance est supérieur à 0, un emplacement ne sera diffusé que si l'appareil se déplace 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 Criteria.ACCURACY_HIGHcritère devrait vous donner des erreurs en dessous de 100m, ce qui n'est pas aussi bon que le GPS peut l'être, mais correspond à vos besoins.
  • Vous devez également surveiller l'état de votre fournisseur de localisation et basculer vers un autre fournisseur s'il n'est pas disponible ou désactivé par l'utilisateur.
  • Le fournisseur passif peut également être un bon match pour 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.
Stéphane
la source
J'ai vérifié, Criteriamais que se passe-t-il si le dernier emplacement réseau est génial (il peut le savoir via le wifi) et qu'il ne faut pas de temps ou de batterie pour l'obtenir (getLastKnown), alors les critères ne tiendront probablement pas compte de cela et retourneront le GPS à la place. Je ne peux pas croire que Google ait rendu cela difficile pour les développeurs.
Nicklas A.
En plus d'utiliser les critères, vous pouvez, à chaque mise à jour de position envoyée par le fournisseur que vous avez sélectionné, vérifier le lastKnowLocation du fournisseur GPS et le comparer (précision et date) à votre position actuelle. Mais cela me semble être un atout plutôt qu'une exigence de vos spécifications; si une précision un peu meilleure est parfois atteinte, sera-t-elle vraiment utile à vos utilisateurs?
Stéphane
C'est ce que je fais maintenant, le problème est que j'ai du mal à déterminer si le dernier savoir est assez bon. Je peux également ajouter que je n'ai pas à me limiter à un seul fournisseur, plus j'utilise, plus vite je pourrais obtenir un verrou.
Nicklas A.
Gardez à l'esprit que PASSIVE_PROVIDER nécessite une API de niveau 8 ou supérieur.
Eduardo
@ Stéphane désolé pour la retouche. Ne t'en occupe pas. Votre message est correct. J'ai fait cette modification pour erreur. Désolé. Cordialement.
Gaucho
10

Répondre 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 immédiatement l'emplacement.

En utilisant une approche alternative :

Vous pouvez essayer d'obtenir l' ID de cellule en cours d'utilisation 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 vous référer à l'emplacement des cellules via 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 des tours 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, il est sûr de les utiliser getLastKnownLocation(String). Si ce n'est pas le cas, attendez onLocationChanged(). Cela évite le besoin d'une base de données tierce pour l'emplacement. Vous pouvez également essayer cette approche .

Aleadam
la source
Ouais, mais je le problème vient si le lastKnownLocation est vraiment mauvais. J'ai besoin d'un bon moyen de décider du meilleur des deux emplacements.
Nicklas A.
Vous pouvez stocker les informations sur les tours et vérifier si ces tours ont changé. Si c'est le cas, attendez un nouvel emplacement, sinon (ou si certains seulement ont changé), puis réutilisez-le. De cette façon, vous évitez de comparer les emplacements des tours à une base de données.
Aleadam
Utiliser des tours me semble une grande exagération, bonne idée cependant.
Nicklas A.
@Nicklas le code n'est pas plus compliqué que ça. Vous aurez cependant besoin de android.Manifest.permission # ACCESS_COARSE_UPDATES.
Aleadam
Oui, mais j'ai toujours besoin d'utiliser un service tiers et j'ai également besoin d'un moyen de décider quand utiliser les informations de la tour sur les données de localisation, cela ajoute simplement une couche supplémentaire de complexité.
Nicklas A.
9

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);
}
Nicklas A.
la source
Salut Nicklas j'ai la même équation donc pourrais-je vous communiquer par n'importe quel moyen .. je vous serais très reconnaissant si vous pouviez nous aider ..
School Boy
Pourriez-vous publier tout le code? Merci, vraiment apprécié
rodi
C'est tout le code. Je n'ai plus accès au projet.
Nicklas A.
1
Vous semblez avoir pris le code de ce projet "android-protips-location" et il est toujours vivant. Les gens peuvent voir comment cela fonctionne ici code.google.com/p/android-protips-location/source/browse/trunk/…
Gödel77
7

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

  1. GPS - vous donnera une précision de plusieurs mètres (en supposant que vous ayez une réception GPS)
  2. Wifi - vous obtiendra une précision de quelques centaines de mètres
  3. Cell Network - Vous obtiendrez des résultats très inexacts (j'ai vu jusqu'à 4 km de déviation ...)

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

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

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

si vous n'avez besoin que d'un seul emplacement, je suggère que dans votre onLocationChangedméthode, une fois que vous aurez reçu une mise à jour, vous désenregistrerez l'auditeur et éviterez une utilisation inutile du GPS.

Muzikant
la source
Peu m'importe d'où je viens, je ne veux pas me limiter à un seul fournisseur
Nicklas A.
Vous pouvez enregistrer tous les fournisseurs de localisation disponibles sur l'appareil (vous pouvez obtenir la liste de tous les fournisseurs à partir de LocationManager.getProviders ()), mais si vous recherchez une solution précise, dans la plupart des cas, le fournisseur de réseau ne vous sera pas utile.
Muzikant
Oui, mais ce n'est pas une question de choisir entre les fournisseurs, c'est une question d'obtenir le meilleur emplacement en général (même lorsque plusieurs fournisseurs sont impliqués)
Nicklas A.
4

Actuellement, j'utilise car c'est fiable pour obtenir l'emplacement et calculer la distance pour mon application ...... j'utilise ceci pour mon application de taxi.

utilisez l'API de fusion que le développeur Google a développée avec la fusion du capteur GPS, du magnétomètre et de l'accéléromètre en utilisant également le Wifi ou la localisation des cellules pour calculer ou estimer la position. Il est également capable de fournir 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;
        }
    }
}
AshisParajuli
la source
2

J'ai parcouru Internet pour trouver une réponse mise à jour (l'année dernière) en utilisant les dernières méthodes d'extraction de position 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 aucune ligne obsolète.

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

Et enfin, au cas où vous voudriez savoir (je l'ai fait), vous N'AVEZ PAS besoin d'une clé api google maps depuis la console développeur de google, si tout ce que vous voulez c'est la position GPS.

Leur tutoriel est également utile. Mais je voulais un tutoriel / exemple de code complet d'une page, et cela. Leur didacticiel s'empile mais est déroutant lorsque vous êtes nouveau 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 ceci:

J'ai non seulement dû modifier le 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 a été facile pour moi).

J'ai dû ajouter des dépendances comme celle-ci: implémentation 'com.google.android.gms: play-services-location: 11.8.0', et mettre à jour les paramètres de mon SDK de studio Android pour inclure les services google play. (paramètres du fichier apparence paramètres système android SDK SDK Tools vérifier les services google play).

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

Neo42
la source
1

Récemment refactorisé pour obtenir l'emplacement du code, apprendre quelques bonnes idées, et enfin réalisé 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();
            }
        }
    }
}

Implémentation complète: https://github.com/bingerz/FastLocation/blob/master/fastlocationlib/src/main/java/cn/bingerz/fastlocation/FastLocation.java

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

2.Chaque demande pour terminer l'emplacement, 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

Bingerz
la source
0

D'après mon expérience, j'ai trouvé préférable d'utiliser le correctif GPS, sauf s'il n'est pas disponible. Je ne connais pas beaucoup les autres fournisseurs de localisation, mais je sais que pour le GPS, il existe quelques astuces qui peuvent être utilisées pour donner une mesure de précision du ghetto. L'altitude est souvent un signe, vous pouvez donc vérifier les valeurs ridicules. Il y a la mesure de précision sur les correctifs de localisation Android. De plus, si vous pouvez voir le nombre de satellites utilisés, cela peut également indiquer la précision.

Une façon intéressante d'avoir une meilleure idée de la précision pourrait être de demander un ensemble de correctifs très rapidement, comme ~ 1 / sec pendant 10 secondes, puis de dormir pendant une minute ou deux. Une conversation que j'ai eue a conduit à croire que certains appareils Android le feront de toute façon. Vous élimineriez ensuite les valeurs aberrantes (j'ai entendu le filtre de Kalman mentionné ici) et utiliser une sorte de stratégie de centrage pour obtenir un seul correctif.

Évidemment, la profondeur que vous atteignez ici dépend de la dureté de vos besoins. Si vous avez une exigence particulièrement stricte pour obtenir LE MEILLEUR emplacement possible, je pense que vous constaterez que le GPS et l'emplacement du réseau sont aussi similaires que les pommes et les oranges. Le GPS peut également être très différent d'un appareil à l'autre.

remorqueurs
la source
Eh bien, ce n'est pas important que ce soit le meilleur, juste qu'il est assez bon pour tracer sur une carte et que je ne vide pas la batterie car c'est une tâche de fond.
Nicklas A.
-3

Skyhook (http://www.skyhookwireless.com/) dispose d'un fournisseur de localisation bien plus rapide que celui proposé par Google. C'est peut-être ce que vous recherchez. Je ne suis pas affilié avec eux.

Ed Burnette
la source
Intéressant en effet, ils semblent utiliser uniquement le WiFi, ce qui est très agréable, mais j'en ai toujours besoin pour fonctionner lorsqu'il n'y a pas de connexion wifi ou 3G / 2G, ce qui ajouterait une autre couche d'abstraction. Bonne prise cependant.
Nicklas A.
1
Skyhook semble utiliser une combinaison de WiFi, GPS et tours de téléphonie cellulaire. Voir skyhookwireless.com/howitworks pour les détails techniques. Ils ont obtenu plusieurs victoires en matière de conception ces derniers temps, par exemple Mapquest, Twydroid, ShopSavvy et Sony NGP. Notez que le téléchargement et l'essai de leur SDK semblent être gratuits, mais vous devez les contacter à propos d'une licence pour le distribuer dans votre application. Malheureusement, ils n'indiquent pas le prix sur leur site Web.
Ed Burnette
Oh je vois. Eh bien, si ce n'est pas libre d'utiliser commercialement, je crains de ne pas pouvoir l'utiliser.
Nicklas A.