I recently encountered the need to have an AsyncTask in an Android app that gets the current GPS position, then fetches some data from the network upon getting the position. This isn’t very straightforward to do, since the GPS stuff uses a callback driven model. I did a quick search and came up with a lot of Stack Overflow solutions to the problem that were really bad. Most of them suggested launching a GPS request, then having some sort of loop that spins until the location comes in. At least one of them didn’t even have any sort of delay in the loop, making it burn up your phone’s battery while it waited.
It turns out to be pretty simple to solve this problem, using the Looper class, which is a bit of a confusing thing. The looper will establish a loop which waits for events/messages within the AsyncTask thread. Once the position comes in, you have the code exit the loop, and then continue on with your network request. Here’s the basic idea:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class AsyncTaskGps extends AsyncTask implements LocationListener { | |
private Location location; | |
@Override | |
protected Object doInBackground(Object… arg0) { | |
LocationManager lm = (LocationManager) arg0[0]; | |
Looper.prepare(); | |
// Request GPS updates. The third param is the looper to use, which defaults the the one for | |
// the current thread. | |
lm.requestSingleUpdate(LocationManager.GPS_PROVIDER, this, null); | |
Looper.loop(); // start waiting…when this is done, we'll have the location in this.location | |
try { | |
// now go use the location to load some data | |
URL url = new URL("…?latitude="+location.getLatitude()+"&longitude="+location.getLongitude()); | |
URLConnection con = url.openConnection(); | |
InputStream is = con.getInputStream(); | |
byte[] data = new byte[1024]; | |
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
int len = –1; | |
do { | |
len = is.read(data); | |
if (len > 0) { | |
baos.write(data, 0, len); | |
} | |
} while (len > –1); | |
// parse data and do stuff… | |
} catch (IOException e) { | |
Log.e("LoadScheduleTask", "Error", e); | |
} catch (JSONException e) { | |
Log.e("LoadScheduleTask", "Error", e); | |
} | |
return null; | |
} | |
@Override | |
protected void onPostExecute(Object result) { | |
// notify someone we are done… | |
} | |
@Override | |
public void onLocationChanged(Location location) { | |
// Store the location, then get the current thread's looper and tell it to | |
// quit looping so it can continue on doing work with the new location. | |
this.location = location; | |
Looper.myLooper().quit(); | |
} | |
} |
Nice solution! Came in handy for me.
One suggestion – I would call removeUpdates(this) for your LocationManager instance in the “onLocationChanged” method to avoid messages sent to dead threads.
after lots of search this helped. Thank you
onLocationChanged is never called