Mit der Lösung von Fedor habe ich die mehrfache Ausführung des Rückrufs erlebt gotLocation
. Dies scheint auf eine Rennbedingung in der außer Kraft gesetzten LocationListener.onLocationChanged
Methode, wenn die gotLocation-Methode lang genug . Ich bin mir nicht sicher, aber ich denke removeUpdates
verhindert das Einreihen neuer Nachrichten in die Looper-Warteschlange, entfernt aber nicht die bereits eingereihten, aber noch nicht verbrauchten Nachrichten. Daraus ergibt sich die Race Condition.
Um die Wahrscheinlichkeit dieses Fehlverhaltens zu verringern, ist es möglich, removeUpdates aufzurufen, bevor das onLocationChanged-Ereignis ausgelöst wird, aber die Wettlaufbedingung bleibt bestehen.
Die beste Lösung, die ich gefunden habe, ist, die requestLocationUpdates
con requestSingleUpdate
.
Dies ist meine Version, die auf Fedors Lösung basiert und einen Handler verwendet, um eine Nachricht an den Looper-Thread zu senden:
public class LocationResolver {
private Timer timer;
private LocationManager locationManager;
private LocationResult locationResult;
private boolean gpsEnabled = false;
private boolean networkEnabled = false;
private Handler locationTimeoutHandler;
private final Callback locationTimeoutCallback = new Callback() {
public boolean handleMessage(Message msg) {
locationTimeoutFunc();
return true;
}
private void locationTimeoutFunc() {
locationManager.removeUpdates(locationListenerGps);
locationManager.removeUpdates(locationListenerNetwork);
Location networkLocation = null, gpsLocation = null;
if (gpsEnabled)
gpsLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (networkEnabled)
networkLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
// if there are both values use the latest one
if (gpsLocation != null && networkLocation != null) {
if (gpsLocation.getTime() > networkLocation.getTime())
locationResult.gotLocation(gpsLocation);
else
locationResult.gotLocation(networkLocation);
return;
}
if (gpsLocation != null) {
locationResult.gotLocation(gpsLocation);
return;
}
if (networkLocation != null) {
locationResult.gotLocation(networkLocation);
return;
}
locationResult.gotLocation(null);
}
};
private final LocationListener locationListenerGps = new LocationListener() {
public void onLocationChanged(Location location) {
timer.cancel();
locationResult.gotLocation(location);
locationManager.removeUpdates(this);
locationManager.removeUpdates(locationListenerNetwork);
}
public void onProviderDisabled(String provider) {
}
public void onProviderEnabled(String provider) {
}
public void onStatusChanged(String provider, int status, Bundle extras) {
}
};
private final LocationListener locationListenerNetwork = new LocationListener() {
public void onLocationChanged(Location location) {
timer.cancel();
locationResult.gotLocation(location);
locationManager.removeUpdates(this);
locationManager.removeUpdates(locationListenerGps);
}
public void onProviderDisabled(String provider) {
}
public void onProviderEnabled(String provider) {
}
public void onStatusChanged(String provider, int status, Bundle extras) {
}
};
public void prepare() {
locationTimeoutHandler = new Handler(locationTimeoutCallback);
}
public synchronized boolean getLocation(Context context, LocationResult result, int maxMillisToWait) {
locationResult = result;
if (locationManager == null)
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
// exceptions will be thrown if provider is not permitted.
try {
gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
} catch (Exception ex) {
}
try {
networkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
} catch (Exception ex) {
}
// don't start listeners if no provider is enabled
if (!gpsEnabled && !networkEnabled)
return false;
if (gpsEnabled)
locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, locationListenerGps, Looper.myLooper());
//locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListenerGps);
if (networkEnabled)
locationManager.requestSingleUpdate(LocationManager.NETWORK_PROVIDER, locationListenerNetwork, Looper.myLooper());
//locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListenerNetwork);
timer = new Timer();
timer.schedule(new GetLastLocationTask(), maxMillisToWait);
return true;
}
private class GetLastLocationTask extends TimerTask {
@Override
public void run() {
locationTimeoutHandler.sendEmptyMessage(0);
}
}
public static abstract class LocationResult {
public abstract void gotLocation(Location location);
}
}
Ich verwende diese Klasse aus einem benutzerdefinierten Looper-Thread, wie dem folgenden:
public class LocationGetter {
private final Context context;
private Location location = null;
private final Object gotLocationLock = new Object();
private final LocationResult locationResult = new LocationResult() {
@Override
public void gotLocation(Location location) {
synchronized (gotLocationLock) {
LocationGetter.this.location = location;
gotLocationLock.notifyAll();
Looper.myLooper().quit();
}
}
};
public LocationGetter(Context context) {
if (context == null)
throw new IllegalArgumentException("context == null");
this.context = context;
}
public synchronized Coordinates getLocation(int maxWaitingTime, int updateTimeout) {
try {
final int updateTimeoutPar = updateTimeout;
synchronized (gotLocationLock) {
new Thread() {
public void run() {
Looper.prepare();
LocationResolver locationResolver = new LocationResolver();
locationResolver.prepare();
locationResolver.getLocation(context, locationResult, updateTimeoutPar);
Looper.loop();
}
}.start();
gotLocationLock.wait(maxWaitingTime);
}
} catch (InterruptedException e1) {
e1.printStackTrace();
}
if (location != null)
coordinates = new Coordinates(location.getLatitude(), location.getLongitude());
else
coordinates = Coordinates.UNDEFINED;
return coordinates;
}
}
wobei Coordinates eine einfache Klasse mit zwei Eigenschaften ist: Breitengrad und Längengrad.
1 Stimmen
Sie könnten eine einfache Bibliothek verwenden, die all die Dinge abstrahiert, die "unter der Haube" passieren müssen: github.com/delight-im/Android-SimpleLocation
0 Stimmen
Hier finden Sie die Antwort in Kotlin: stackoverflow.com/a/53800632/2201814
0 Stimmen
Sie können in Android verschmolzene Techniken zur Standorterfassung verwenden.
0 Stimmen
FusedLocation funktioniert gut (ich weiß allerdings immer noch nicht, warum getCurrentLocation() veraltet sein musste).