Android – Use location services


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.

About these ads

Tags: , , ,

15 Responses to “Android – Use location services”

  1. Build Our Biz » Blog Archive » Best Workflow for Positioning a Device Says:

    [...] This post almost conclusively verifies it. 0Register to vote. Email This Post Post a [...]

  2. mojo Says:

    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?

  3. Ibsta Says:

    ditto mojo

  4. Ibsta Says:

    Plus a small style comment. Why 2 class definitions dood? you didnt need to do this inline.

    … ummm good otherwise

  5. devdiscoveries Says:

    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.

  6. milansnv Says:

    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

  7. wolfie Says:

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

  8. wolfie Says:

    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.

  9. Dude Says:

    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.

  10. Dude Says:

    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.

  11. MatDag Says:

    Hi,
    really useful code. Was exactly what I looked for.
    Thanks a lot for your post.
    Bye

  12. Filipe Costa Says:

    OMFG! You’re the man! Thanks a lot, exactly what i need!

  13. Psychotext Says:

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

  14. Miguel K Says:

    Hi, your logic looks fine to me but the app always crashes on start. Why should that be? Permissions are OK.

  15. CFranko Says:

    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!

Comments are closed.


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: