Buena forma de obtener la ubicación del usuario en Android

El problema:

Obtener la ubicación actual del usuario dentro de un umbral lo antes posible y, al mismo tiempo, ahorrar batería.

Por qué el problema es un problema:

En primer lugar, Android tiene dos proveedores; red y GPS. A veces, la red es mejor y, a veces, el GPS es mejor.

Por "mejor" me refiero a la relación entre velocidad y precisión.
Estoy dispuesto a sacrificar unos pocos metros de precisión si puedo obtener la ubicación casi al instante y sin encender el GPS.

En segundo lugar, si solicita actualizaciones para cambios de ubicación, no se envía nada si la ubicación actual es estable.

Google tiene un ejemplo de cómo determinar la "mejor" ubicación aquí: http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate
Pero creo que no es tan bueno como debería / podría ser.

Estoy un poco confundido por qué Google no tiene una API normalizada para la ubicación, el desarrollador no debería tener que preocuparse de dónde es la ubicación, solo debe especificar lo que desea y el teléfono debe elegir por usted.

Con qué necesito ayuda:

Necesito encontrar una buena manera de determinar la "mejor" ubicación, tal vez a través de alguna heurística o tal vez a través de alguna biblioteca de terceros.

¡Esto no significa determinar el mejor proveedor!
Probablemente usaré todos los proveedores y elegiré el mejor de ellos.

Antecedentes de la aplicación:

La aplicación recopilará la ubicación del usuario en un intervalo fijo (digamos cada 10 minutos aproximadamente) y la enviará a un servidor.
La aplicación debería conservar la mayor cantidad de batería posible y la ubicación debería tener una precisión de X (50-100?) Metros.

El objetivo es luego poder trazar la ruta del usuario durante el día en un mapa, por lo que necesito suficiente precisión para eso.

Otra información:

¿Cuáles cree que son los valores razonables de las precisiones deseadas y aceptadas?
He estado usando 100 m según lo aceptado y 30 m como deseaba, ¿es mucho pedir?
Me gustaría poder trazar la ruta del usuario en un mapa más tarde.
¿Son 100 m para lo deseado y 500 m para aceptado mejor?

Además, ahora mismo tengo el GPS encendido durante un máximo de 60 segundos por actualización de ubicación, ¿es esto demasiado corto para obtener una ubicación si estás en interiores con una precisión de tal vez 200 m?


Este es mi código actual, se agradece cualquier comentario (aparte de la falta de verificación de errores que es 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);
}

preguntado el 30 de mayo de 11 a las 20:05

Llegó muy tarde, pero el "Proveedor de ubicaciones fusionadas" que se anunció recientemente en IO 2013 parece que satisface muchas de sus necesidades: desarrollador.android.com/google/play-services/ubicación.html -

¿No debería ser la última línea de getBestLocation (): return currentBestLocation; en lugar de devolver bestLocation ;? -

10 Respuestas

Parece que estamos codificando la misma aplicación ;-)
Aquí está mi implementación actual. Todavía estoy en la fase de prueba beta de mi aplicación de carga de GPS, por lo que podría haber muchas mejoras posibles. pero parece funcionar bastante bien hasta ahora.

/**
 * 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: aquí está la parte que solicita las actualizaciones periódicas de los proveedores de ubicación:

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
}

Cosas para considerar:

  • no solicite actualizaciones de GPS con demasiada frecuencia, se agota la energía de la batería. Actualmente utilizo 30 minutos por defecto para mi aplicación.

  • añada una marca de "distancia mínima hasta la última ubicación conocida". sin esto, sus puntos "saltarán" cuando el GPS no esté disponible y la ubicación se triangule desde las torres de telefonía celular. o puede verificar si la nueva ubicación está fuera del valor de precisión de la última ubicación conocida.

Respondido 26 ago 13, 13:08

En realidad, nunca obtiene una ubicación nueva, solo usa ubicaciones que están allí de actualizaciones anteriores. Creo que este código se beneficiaría enormemente al agregar un oyente que actualice la ubicación activando el GPS de vez en cuando. - Nicklas A.

lo siento, pensé que solo te interesaba la parte que selecciona lo mejor de todas las ubicaciones disponibles. Agregué el código anterior que también los solicita. si se recibe una nueva ubicación gps, se almacena / carga de inmediato. Si recibo una actualización de ubicación de red, la guardo como referencia y 'espero' recibir una actualización de GPS también hasta que se realice la siguiente verificación de ubicación. - Gryphius

También tuve un método stopRecording () que canceló el temporizador. Eventualmente cambié de un temporizador a un ScheduledThreadPoolExecutor, por lo que stopRecording ahora básicamente llama a executeor.shutdown () y cancela el registro de todos los oyentes de actualización de ubicación - Gryphius

de acuerdo con mi scm, stopRecording solo llamó gpsTimer.cancel () y configuró gps_recorder_running = false, así que, como en su caso, no hay limpieza de los oyentes en ese entonces. el código actual realiza un seguimiento de todos los oyentes activos en un vector, no tenía esto cuando escribí esta respuesta hace 1.5 años. - Gryphius

ya es en github, pero no estoy seguro de que esta sea la mejor manera de hacer cosas con GPS hoy en día. Afaik, han realizado muchas mejoras en la API de ubicación desde que escribí este código. - Gryphius

Para seleccionar el proveedor de ubicación adecuado para su aplicación, puede utilizar Criterios objetos:

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

Lea la documentación para solicitudUbicaciónActualizaciones para obtener más detalles sobre cómo se tienen en cuenta los argumentos:

La frecuencia de notificación se puede controlar mediante los parámetros minTime y minDistance. Si minTime es mayor que 0, LocationManager podría descansar durante minTime milisegundos entre actualizaciones de ubicación para ahorrar energía. Si minDistance es mayor que 0, una ubicación solo se transmitirá si el dispositivo se mueve en metros minDistance. Para obtener notificaciones con la mayor frecuencia posible, establezca ambos parámetros en 0.

Mas pensamientos

  • Puede controlar la precisión de los objetos de ubicación con Location.getAccuracy (), que devuelve la precisión estimada de la posición en metros.
  • La Criteria.ACCURACY_HIGH El criterio debería proporcionarle errores por debajo de 100 m, lo que no es tan bueno como el GPS, pero se adapta a sus necesidades.
  • También debe controlar el estado de su proveedor de ubicación y cambiar a otro proveedor si el usuario no está disponible o lo deshabilita.
  • La proveedor pasivo también puede ser una buena combinación para este tipo de aplicación: la idea es usar actualizaciones de ubicación siempre que las solicite otra aplicación y difundan en todo el sistema.

Respondido el 08 de junio de 11 a las 14:06

He investigado Criteria pero, ¿qué pasa si la última ubicación de la red es impresionante (puede saberlo a través de wifi) y no se necesita tiempo ni batería para obtenerla (getLastKnown), entonces los criterios probablemente lo ignorarán y devolverán el GPS en su lugar? No puedo creer que Google se lo haya puesto tan difícil a los desarrolladores. - Nicklas A.

Además de utilizar los Criterios, puede, en cada actualización de ubicación enviada por el proveedor que seleccionó, verificar lastKnowLocation del proveedor de GPS y compararlo (precisión y fecha) con su ubicación actual. Pero esto me parece más bien tenerlo que un requisito de sus especificaciones; Si a veces se logra una precisión algo mayor, ¿será realmente útil para sus usuarios? - Stéphane

Eso es lo que estoy haciendo ahora, el problema es que me cuesta averiguar si el último conocimiento es lo suficientemente bueno. También puedo agregar que no tengo que limitarme a un solo proveedor, cuanto más uso, más rápido puedo obtener un bloqueo. - Nicklas A.

Tenga en cuenta que PASSIVE_PROVIDER requiere API Nivel 8 o superior. - Eduardo

@ Stéphane perdón por la edición. No te ocupes de eso. Tu publicación es correcta. Hice esa edición por error. Lo siento. Saludos. - Gaucho

Respondiendo a los dos primeros puntos:

  • GPS lo hará siempre darle una ubicación más precisa, si está habilitado y si no hay paredes gruesas alrededor.

  • Si la ubicación no cambió, puede llamar getLastKnownLocation (cadena) y recupere la ubicación inmediatamente.

Usando un enfoque alternativo:

Puedes intentar conseguir el identificación de celda en uso o todas las celdas vecinas

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

Puede consultar la ubicación de la celda a través de varias bases de datos abiertas (p. Ej., http://www.location-api.com/ or http://opencellid.org/ )


La estrategia sería leer la lista de ID de torres al leer la ubicación. Luego, en la siguiente consulta (10 minutos en su aplicación), léalos nuevamente. Si al menos algunas torres son iguales, entonces es seguro usar getLastKnownLocation(String). Si no es así, entonces espera onLocationChanged(). Esto evita la necesidad de una base de datos de terceros para la ubicación. También puedes probar Este enfoque.

contestado el 31 de mayo de 11 a las 20:05

Sí, pero el problema viene si la última ubicación conocida es realmente mala. Necesito una buena forma de decidir cuál es la mejor de dos ubicaciones. - Nicklas A.

Puede almacenar la información de las torres y verificar si esas torres cambiaron. Si lo hicieron, espere una nueva ubicación, si no (o si sólo algunos cambiado), luego reutilícelo. De esa forma, evita comparar las ubicaciones de las torres con una base de datos. - Aleadam

Usar torres me parece una gran exageración, aunque es una buena idea. - Nicklas A.

@Nicklas el código no se vuelve más complicado que eso. Sin embargo, necesitará android.Manifest.permission # ACCESS_COARSE_UPDATES. - Aleadam

Sí, pero todavía necesito usar un servicio de terceros y también necesito una forma de decidir cuándo usar la información de la torre sobre los datos de ubicación, esto solo agrega una capa adicional de complejidad. - Nicklas A.

Esta es mi solución que funciona bastante 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);
}

Respondido 11 Jul 13, 16:07

Hola Nicklas, tengo el mismo requisito, así que podría comunicarme con usted por cualquier medio ... Le agradecería si pudiera ayudarnos ... - Niño de la escuela

¿Podrías publicar el código completo? Gracias, muy apreciado - Rodas

Eso es todo el código. Ya no tengo acceso al proyecto. - Nicklas A.

Parece que has tomado el código de este proyecto "android-protips-location" y todavía está vivo. La gente puede ver cómo funciona aquí. code.google.com/p/android-protips-ubicación/source/browse/trunk/… - Gödel77

La precisión de la ubicación depende principalmente del proveedor de ubicación utilizado:

  1. GPS: obtendrá una precisión de varios metros (suponiendo que tenga recepción GPS)
  2. Wifi: obtendrá unos cientos de metros de precisión
  3. Red celular: obtendrá resultados muy inexactos (he visto una desviación de hasta 4 km ...)

Si lo que busca es precisión, entonces el GPS es su única opción.

He leído un artículo muy informativo al respecto. aquí.

En cuanto al tiempo de espera del GPS, 60 segundos deberían ser suficientes y, en la mayoría de los casos, incluso demasiado. Creo que 30 segundos está bien y, a veces, incluso menos de 5 segundos ...

si solo necesita una ubicación, le sugiero que en su onLocationChanged , una vez que reciba una actualización, anulará el registro del oyente y evitará el uso innecesario del GPS.

Respondido el 08 de junio de 11 a las 13:06

Realmente no me importa de dónde obtengo mi ubicación, no quiero limitarme a un solo proveedor - Nicklas A.

Puede registrar todos los proveedores de ubicación disponibles en el dispositivo (puede obtener la lista de todos los proveedores en LocationManager.getProviders ()), pero si está buscando una solución precisa, en la mayoría de los casos, el proveedor de red no será útil para usted. - Músico

Sí, pero no se trata de elegir entre proveedores, se trata de obtener la mejor ubicación en general (incluso cuando hay varios proveedores involucrados). Nicklas A.

Actualmente lo estoy usando, ya que es confiable para obtener la ubicación y calcular la distancia para mi aplicación ... lo estoy usando para mi aplicación de taxi.

use la API de fusión que el desarrollador de Google ha desarrollado con la fusión de sensor GPS, magnetómetro, acelerómetro y también usa Wifi o ubicación celular para calcular o estimar la ubicación. También es capaz de proporcionar actualizaciones de ubicación también dentro del edificio con precisión. para obtener más detalles, vaya al enlace 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;
        }
    }
}

Respondido el 05 de enero de 16 a las 19:01

Busqué en Internet una respuesta actualizada (el año pasado) utilizando los últimos métodos de extracción de ubicación sugeridos por Google (para usar FusedLocationProviderClient). Finalmente aterricé en esto:

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

Creé un nuevo proyecto y copié la mayor parte de este código. Auge. Funciona. Y creo que sin líneas en desuso.

Además, el simulador no parece obtener una ubicación GPS, que yo sepa. Llegó a informar de esto en el registro: "Todas las configuraciones de ubicación están satisfechas".

Y finalmente, en caso de que quisieras saber (yo lo hice), NO necesitas una clave de API de Google Maps de la consola de desarrollo de Google, si todo lo que quieres es la ubicación GPS.

También es útil su tutorial. Pero quería un tutorial / ejemplo de código completo de una página, y eso. Su tutorial se acumula, pero es confuso cuando eres nuevo en esto porque no sabes qué piezas necesitas de las páginas anteriores.

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

Y finalmente, recuerda cosas como esta:

No solo tuve que modificar mainActivity.Java. También tuve que modificar Strings.xml, androidmanifest.xml Y el build.gradle correcto. Y también tu activity_Main.xml (pero esa parte fue fácil para mí).

Necesitaba agregar dependencias como esta: implementación 'com.google.android.gms: play-services-location: 11.8.0', y actualizar la configuración de mi SDK de Android Studio para incluir los servicios de Google Play. (configuración de archivo configuración del sistema de apariencia Android SDK SDK Tools verifique los servicios de Google Play).

actualización: el simulador de Android parecía obtener una ubicación y eventos de cambio de ubicación (cuando cambié el valor en la configuración del sim). Pero mis mejores y primeros resultados fueron en un dispositivo real. Por lo que probablemente sea más fácil de probar en dispositivos reales.

Respondido 07 Feb 18, 15:02

Recientemente refactorizado para obtener la ubicación del código, aprender algunas buenas ideas y finalmente lograr una biblioteca y una demostración relativamente perfectas.

La respuesta de @ Gryphius es buena

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

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

1.Gracias a las ideas de soluciones de @Gryphius, también comparto el código completo.

2.Cada solicitud para completar la ubicación, es mejor eliminar las actualizaciones; de lo contrario, la barra de estado del teléfono siempre mostrará el icono de posicionamiento

respondido 04 mar '18, 08:03

En mi experiencia, he encontrado que es mejor ir con la corrección de GPS a menos que no esté disponible. No sé mucho sobre otros proveedores de ubicación, pero sé que para el GPS hay algunos trucos que se pueden usar para dar una medida de precisión de gueto. La altitud es a menudo una señal, por lo que puede verificar valores ridículos. Existe la medida de precisión en las correcciones de ubicación de Android. Además, si puede ver el número de satélites utilizados, esto también puede indicar la precisión.

Una forma interesante de tener una mejor idea de la precisión podría ser solicitar un conjunto de correcciones muy rápidamente, como ~ 1 / seg durante 10 segundos y luego dormir durante uno o dos minutos. Una charla en la que he estado me ha llevado a creer que algunos dispositivos Android harán esto de todos modos. Luego, eliminaría los valores atípicos (he escuchado que se menciona el filtro de Kalman aquí) y usaría algún tipo de estrategia de centrado para obtener una única solución.

Obviamente, la profundidad a la que llegue aquí depende de cuán difíciles sean sus requisitos. Si tiene un requisito particularmente estricto para obtener LA MEJOR ubicación posible, creo que encontrará que el GPS y la ubicación de la red son tan similares como manzanas y naranjas. Además, el GPS puede ser muy diferente de un dispositivo a otro.

Respondido el 09 de junio de 11 a las 20:06

Bueno, no es importante que sea lo mejor, solo que sea lo suficientemente bueno para trazar en un mapa y que no agote la batería, ya que esta es una tarea en segundo plano. - Nicklas A.

Skyhook (http://www.skyhookwireless.com/) tiene un proveedor de ubicación que es mucho más rápido que el estándar que proporciona Google. Puede que sea lo que estás buscando. No estoy afiliado a ellos.

Respondido el 08 de junio de 11 a las 23:06

De hecho, es interesante, sin embargo, solo parecen usar WiFi, lo cual es muy bueno, pero aún necesito que funcione cuando no hay conexión wifi o 3G / 2G, por lo que esto agregaría otra capa de abstracción. Aunque es una buena captura. - Nicklas A.

Skyhook parece utilizar una combinación de WiFi, GPS y torres de telefonía móvil. Ver skyhookwireless.com/howitworks para los detalles técnicos. Últimamente han obtenido varias victorias en diseño, por ejemplo, Mapquest, Twydroid, ShopSavvy y Sony NGP. Tenga en cuenta que descargar y probar su SDK parece ser gratuito, pero debe comunicarse con ellos para obtener una licencia para distribuirlo en su aplicación. Desafortunadamente, no enumeran el precio en su sitio web. - Ed Burnette

Oh ya veo. Bueno, si no es de uso comercial gratuito, me temo que no puedo usarlo. - Nicklas A.

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.