19

So I'm working out my first multi-threaded application using Android with the AsyncTask class. I'm trying to use it to fire off a Geocoder in a second thread, then update the UI with onPostExecute, but I keep running into an issue with the proper Context.

I kind of hobbled my way through using Contexts on the main thread, but I'm not exactly sure what the Context is or how to use it on background threads, and I haven't found any good examples on it. Any help? Here is an excerpt of what I'm trying to do:

public class GeoCode extends AsyncTask<GeoThread, Void, GeoThread> {
  @Override
  protected GeoThread doInBackground(GeoThread... i) {
    List<Address> addresses = null;
    Geocoder geoCode = null; 
    geoCode = new Geocoder(null); //Expects at minimum Geocoder(Context context);
    addresses = geoCode.getFromLocation(GoldenHour.lat, GoldenHour.lng, 1);
  }
}

It keeps failing at the sixth line there, because of the improper Context.

Segfault
  • 8,036
  • 3
  • 35
  • 54
Michael
  • 253
  • 2
  • 3
  • 7

5 Answers5

18

@Eugene van der Merwe

The following piece of code works for me : ) -->

public class ApplicationLauncher extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.applicationlauncher);

    LoadApplication loadApplication = new LoadApplication(this);
    loadApplication.execute(null);
}

private class LoadApplication extends AsyncTask {

    Context context;
    ProgressDialog waitSpinner;
    ConfigurationContainer configuration = ConfigurationContainer.getInstance();

    public LoadApplication(Context context) {
        this.context = context;
        waitSpinner = new ProgressDialog(this.context);
    }

    @Override
    protected Object doInBackground(Object... args) {
        publishProgress(null);
        //Parsing some stuff - not relevant
        configuration.initialize(context);
        return null;
    }

    @Override
    protected void onProgressUpdate(Object... values) {
        super.onProgressUpdate(values);
        // Only purpose of this method is to show our wait spinner, we dont
        // (and can't) show detailed progress updates
        waitSpinner = ProgressDialog.show(context, "Please Wait ...", "Initializing the application ...", true);
    }

    @Override
    protected void onPostExecute(Object result) {
        super.onPostExecute(result);
        waitSpinner.cancel();
    }
}
}

Cheers,

Ready4Android

Ready4Android
  • 2,022
  • 2
  • 25
  • 29
  • 29
    This code has a Context leak. You are saving the Activity as the Context in the AsyncTask, which is fine, except when the user rotates the device, which recreates the Activity. The AsyncTask keeps running, with a reference to the old copy of your Activity, and now you've got two copies of the Activity in memory. – Christopher Perry Nov 30 '12 at 23:22
  • @ChristopherPerry: would keeping the Context as a WeakReference in the AsyncTask solve the leak? Or is there a better way to safely tweak the above solution? – gcl1 Apr 29 '13 at 13:13
  • Or is it safe enough just to pass the application context? – gcl1 Apr 29 '13 at 13:40
  • 1
    @gcl1, a WeakReference would fix the leak. Unfortunately, a rotation will cause you to then lose your reference, as it's now eligible for garbage collection and Android's very aggressive garbage collection routine will expect your app to render undo Caesar that which is Caesar's. I'd avoid passing any context to an AsyncTask like the plague. Instead, you can start your progress dialog in the Activity just before the execute call of the AsyncTask then dismiss it when the Asynctask returns. An even better solution (in this context) is to use [Otto](http://square.github.io/otto/). – Christopher Perry Apr 30 '13 at 04:12
  • @ChristopherPerry: thanks, I hadn't heard of Otto - will look into that! In my case, I don't need context to display a dialog box, but rather to send a broadcast so a photo that's been downloaded can be written to the device gallery (per [this page](https://developer.android.com/training/camera/photobasics.html#TaskGallery)). I guess I just need to be careful and use a weak ref. – gcl1 Apr 30 '13 at 18:12
  • 1
    This code has a memory leak regardless of @ChristopherPerry comment: since `AsyncTask` is an inner class (not static), it has implicit reference to the enclosing `Activity`. This creates memory leak even if you don't pass `Context` at all. Therefore, `WeakReference` will not resolve the issue! – Vasiliy Aug 10 '16 at 07:42
4

The Context is an object which provides accees to application runtime environment. In most cases when you need to obtain objects from Android environment, such as resources, views, infrastructure classes etc -- you need to have Context in your hands.

To obtain Context instance is very simple when you're in the Activity class -- Activity itself is a subclass of the Context, so all you need to do -- is to use 'this' keyword to point on your current context.

Whever you create code which might require Context - you should take care to pass Context object from your parent Activity. In case of your example you could add explicit constructor which accepts Context as input argument.

teedyay
  • 23,293
  • 19
  • 66
  • 73
Vladimir Kroz
  • 5,237
  • 6
  • 39
  • 50
2

I did some more research, and someone suggested passing it to the thread (not sure why I didn't think of that). I passed it to the Geocoder thread through an argument, and just like that, it worked.

Michael
  • 253
  • 2
  • 3
  • 7
1

The problem with updating the UI from an AsyncTask is that you need the current activity context. But the context is destroyed and recreated for every orientation change.

Here's a good answer to your question: Android AsyncTask context behavior

Community
  • 1
  • 1
espinchi
  • 9,144
  • 6
  • 58
  • 65
0

If doesn't look like you are using the params. You could use that to pass in the Conetxt.

public class GeoCode extends AsyncTask<Context, Void, GeoThread> {
  @Override
  protected GeoThread doInBackground(Context... params) {
    List<Address> addresses = null;
    Geocoder geoCode = null; 
    geoCode = new Geocoder(params[0]); //Expects at minimum Geocoder(Context context);
    addresses = geoCode.getFromLocation(GoldenHour.lat, GoldenHour.lng, 1);
  }
}

Then from within your activity:

GeoCode myGeoCode = new GeoCode();
myGeoCode.execute(this);
Michael Peterson
  • 10,383
  • 3
  • 54
  • 51