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. Algunas veces la red es mejor y otras veces el GPS es mejor.
Por "mejor" me refiero a la velocidad frente a la relación de 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 determinación de 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.
Lo que necesito ayuda con:
Necesito encontrar una buena manera de determinar la "mejor" ubicación, quizás a través de alguna heurística o tal vez a través de una biblioteca de terceros.
¡Esto no significa determinar el mejor proveedor!
Probablemente voy a 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 más o menos) y la enviará a un servidor.
La aplicación debe conservar la mayor cantidad de batería posible y la ubicación debe 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.
Misceláneo:
¿Cuáles cree que son valores razonables en las precisiones deseadas y aceptadas?
He estado usando 100m según lo aceptado y 30m según lo deseado, ¿es mucho pedir?
Me gustaría poder trazar la ruta del usuario en un mapa más adelante.
¿Son 100m para deseados y 500m para aceptados mejor?
Además, en este momento tengo el GPS encendido durante un máximo de 60 segundos por actualización de ubicación, ¿es demasiado corto para obtener una ubicación si está en interiores con una precisión de quizás 200 m?
Este es mi código actual, cualquier comentario es apreciado (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);
}