Since my last post on the Android platform was about calculating the distance between two geolocations, I thought it might be usefull to discuss how you can actually get your own position on earth with the Location services from the Android platform.
The Android platform gets its location data from two sources. I think the best known source is the GPS that is built in the device. It will give you an accuracy up to a few meters under good conditions. However, it can take quite some time for a device to get a GPS fix from the satelites. Therefore it is worthwhile to look at a second source of location input. Android devices can also get their location from the cell towers of the mobile network. This is a very coarse estimate with an accuracy that can be a few hundred meters. However, the result is available almost instantly.
To accomodate for the differences of the two methods, I used a solution as the one shown below. First it will fetch the last known position of the GPS service, to make sure that there will be some location to use. Then it will register two LocationListeners to fetch an updated position fix.
The network (coarse accuracy) will deliver quick results and I will unregister this listener again when the accuracy is smaller than 1000 meters. (By the way, I was displaying distances rounded to 100 meters). Then a second listener will ask for updates from the GPS unit, and will unregister with an accuracy below 50 meters.
In this way I make sure that I get a position fix as quickly as possible, although with low accuracy, whereas in the meanwhile I try to get a better fix by the GPS unit. Even when the device cannot get a GPS fix, I will always be able to at least give a crude estimate on the position of the user.
The (stripped) code follows here.
public class Main extends Activity {
private LocationManager locationManager;
private LocationListener listenerCoarse;
private LocationListener listenerFine;
// Holds the most up to date location.
private Location currentLocation;
// Set to false when location services are
// unavailable.
private boolean locationAvailable = true;
/** {@inheritDoc} */
@Override
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.main);
super.onCreate(savedInstanceState);
registerLocationListeners();
...
...
}
private void registerLocationListeners() {
locationManager = (LocationManager)
getSystemService(LOCATION_SERVICE);
// Initialize criteria for location providers
Criteria fine = new Criteria();
fine.setAccuracy(Criteria.ACCURACY_FINE);
Criteria coarse = new Criteria();
coarse.setAccuracy(Criteria.ACCURACY_COARSE);
// Get at least something from the device,
// could be very inaccurate though
currentLocation = locationManager.getLastKnownLocation(
locationManager.getBestProvider(fine, true));
if (listenerFine == null || listenerCoarse == null)
createLocationListeners();
// Will keep updating about every 500 ms until
// accuracy is about 1000 meters to get quick fix.
locationManager.requestLocationUpdates(
locationManager.getBestProvider(coarse, true),
500, 1000, listenerCoarse);
// Will keep updating about every 500 ms until
// accuracy is about 50 meters to get accurate fix.
locationManager.requestLocationUpdates(
locationManager.getBestProvider(fine, true),
500, 50, listenerFine);
}
/**
* Creates LocationListeners
*/
private void createLocationListeners() {
listenerCoarse = new LocationListener() {
public void onStatusChanged(String provider,
int status, Bundle extras) {
switch(status) {
case LocationProvider.OUT_OF_SERVICE:
case LocationProvider.TEMPORARILY_UNAVAILABLE:
locationAvailable = false;
break;
case LocationProvider.AVAILABLE:
locationAvailable = true;
}
}
public void onProviderEnabled(String provider) {
}
public void onProviderDisabled(String provider) {
}
public void onLocationChanged(Location location) {
currentLocation = location;
if (location.getAccuracy() > 1000 &&
location.hasAccuracy())
locationManager.removeUpdates(this);
}
};
listenerFine = new LocationListener() {
public void onStatusChanged(String provider,
int status, Bundle extras) {
switch(status) {
case LocationProvider.OUT_OF_SERVICE:
case LocationProvider.TEMPORARILY_UNAVAILABLE:
locationAvailable = false;
break;
case LocationProvider.AVAILABLE:
locationAvailable = true;
}
}
public void onProviderEnabled(String provider) {
}
public void onProviderDisabled(String provider) {
}
public void onLocationChanged(Location location) {
currentLocation = location;
if (location.getAccuracy() > 1000
&& location.hasAccuracy())
locationManager.removeUpdates(this);
}
};
}
...
...
/** {@inheritDoc} */
@Override
protected void onResume() {
// Make sure that when the activity has been
// suspended to background,
// the device starts getting locations again
registerLocationListeners();
super.onResume();
}
...
...
@Override
protected void onPause() {
// Make sure that when the activity goes to
// background, the device stops getting locations
// to save battery life.
locationManager.removeUpdates(listenerCoarse);
locationManager.removeUpdates(listenerFine);
super.onPause();
}
}
The activity will create and register the necessary listeners. The onPause() method will actually unregister the location listeners again to avoid that the application keeps updating its location, even when moved to the background. This will help saving battery life. On Android, you cannot really close an application (unless you kill it with an app killer application). Therefore you need to stop fetching locations yourself when it is no longer needed.
The onResume() method will start listening again when the activity becomes active again. This will ensure that the user gets an updated location when he has moved in the meantime. Because returning to the home screen on Android means that the application might still be running, the location could be very outdated. Therefore I added this onResume() method.
February 21, 2010 at 12:15 am
[...] This post almost conclusively verifies it. 0Register to vote. Email This Post Post a [...]
May 1, 2010 at 8:42 pm
First, thank you for the really interesting post.
But, Isn’t there something wrong?
// Will keep updating about every 500 ms until
// accuracy is about 50 meters to get accurate fix.
locationManager.requestLocationUpdates(
locationManager.getBestProvider(fine, true),
500, 50, listenerFine
);
the api doc says(about the minDistance) :
“..If minDistance is greater than 0, a location will only be broadcasted if the device moves by minDistance meters…”
so it should be some kind of periodic update every 50 meters right?
May 21, 2010 at 7:31 am
ditto mojo
May 21, 2010 at 7:32 am
Plus a small style comment. Why 2 class definitions dood? you didnt need to do this inline.
… ummm good otherwise
June 1, 2010 at 9:08 pm
Hi mojo,
Sorry for the late reaction, I’ve had a small holiday
.
You are right in your comment, but only if the location was already known with the accuracy of 50 meters before. Then if you move 50 meters, you get a new broadcast.
However, when the location is not yet known (the first time you query for your location, the GPS must get satellite fix etc) and then you get an update every 500 ms.
It is not 100% clear in my text, I admit.
Ibsta, you are right about the class definitions. I used some of my code that first only had one listener and I added the other one later, so yeah, that means horrible copying. I will modify the post.
October 4, 2010 at 2:10 pm
your code cause “The application has stopped unexpectedly” when no provider is permitted in phone settings because locationManager.getBestProvider return NULL then. application is then not running, it is force to close
November 5, 2010 at 11:36 pm
Thanks for posting this, I found it very helpful! It took me a while to figure out why the app was crashing and showing this message “The application has stopped unexpectedly” and then I realized I had overlooked adding this to the manifest file:
However, I’m confused about one line of code, which shows up in the two onLocationChanged methods: location.getAccuracy() > 1000
The better the accuracy, the smaller the number returned by getAccuracy right?
So why are these listeners canceled when the accuracy is greater than 1000 meters?
I thought the goal was to cancel them when the coarse accuracy was better than 1000 meters (location.getAccuracy() < 1000) and when the fine accuracy was better than 50 meters (location.getAccuracy() < 50).
Can someone help me understand this part of the code? Thanks.
One other issue:
If I disable GPS under setting, both listeners (coarse and fine) still run, but location information is the same in both listeners (its coarse).
November 6, 2010 at 12:04 am
Made a couple of updates some people might find useful:
These first 3 check to see if GPS is enabled before getting lastKnownLocation and registering/unregistering the fine location listener:
1.
// Get at least something from the device,
// could be very inaccurate though
if(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
currentLocation = locationManager.getLastKnownLocation(
locationManager.getBestProvider(fine, true));
} else {
currentLocation = locationManager.getLastKnownLocation(
locationManager.getBestProvider(coarse, true));
Toast.makeText(getBaseContext(),
“gps disabled”,
Toast.LENGTH_LONG).show();
}
2.
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
// Will keep updating about every 500 ms until
// accuracy is about 50 meters to get accurate fix.
locationManager.requestLocationUpdates(
locationManager.getBestProvider(fine, true),
500, 50, listenerFine);
}
3. in onPause
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
locationManager.removeUpdates(listenerFine);
}
Sorry about the format.
November 10, 2010 at 12:42 am
Hi
Nice post. I wonder if you could help me with this.
I need to have a service that would have a location listener. But I am getting a runtime error of:
Can’t create handler inside thread that has not called Looper.prepare()
The error is justified, but could you please guide on how this can be tackled.
{Scenario:
an activity launches a service and itself ends. service needs to see the location after every few minutes (registering and deregistering the listener so as to save power and all). the location listener is giving trouble as stated above.
November 10, 2010 at 12:53 am
I just forgot to add that I get the runtime error because i am using the timer.scheduleatfixedrate and inside it i am registering the location listener.
Thanks in advance.
April 22, 2011 at 5:02 pm
Hi,
really useful code. Was exactly what I looked for.
Thanks a lot for your post.
Bye
June 10, 2011 at 8:29 pm
OMFG! You’re the man! Thanks a lot, exactly what i need!
June 17, 2011 at 1:19 am
For what it’s worth, I don’t believe the following lines are correct.
if (location.getAccuracy() > 1000
&& location.hasAccuracy())
locationManager.removeUpdates(this);
}
On testing (with judicious use of log.d) it’s clear that the updates will only be stopped if accuracy is over 1000m, which is exactly what you don’t want if you’re eventually trying to get the accuracy down to 1000m or 50m (Coarse / Fine). Not just that, but if you start with a good accuracy the updates will run continuously, eating up battery life.
Changing this line to <= 1000 and <= 50 (Coarse / Fine) means that the updates stop only once you have good accuracy, which is what I assume most people would want (certainly in my case).
October 23, 2011 at 11:02 am
Hi, your logic looks fine to me but the app always crashes on start. Why should that be? Permissions are OK.
October 31, 2011 at 9:42 pm
This content is a extraordinarily good undivided. Say thank you for share ining such great content out. Ill deff be surfing by more times then i do so i an observe whats great!