Tuesday, September 18, 2012

Memory leak when setting Drawable from an AsyncTask

In this article I share my experience with memory leak problem which have relieved me of several hours of my precious time. Maybe I will not tell anything new to you, but maybe I save  someone's minute.

If you are working with ImageView on Android, you probably know, that it is recommended way to update (fill) the ImageView with a Drawable from an AsyncTask, especially in cases that the image for the Drawable have to be downloaded from the Internet first. In the examples below m.photo.getFullSizeImg() represents the method, which downloads the image from Internet and put it into the Drawable object.
During my work I have faced problem with the OutOfMemoryException when I try to start my image -showing-activity several times.

My original, bad one, memory-leak-causing code looked like this:
public class Populator extends AsyncTask<Void, Drawable, Drawable> {
    
    @Override
    protected Drawable doInBackground(Void... params) {
      Drawable preview = null;
      preview = m.photo.getFullSizeImg();
      return preview;
    }
    
    @Override
    protected void onPostExecute(Drawable _preview) {
      imageView.setImageDrawable(_preview);
    }
  }
The problem with this cute code is that variable preview holds the reference to the image Drawable even after onPostExecute() is finished and therefore the garbage collector can't remove the AsyncTask from the memory. So to solve the problem, we have to assign null to the preview variable. that's all and two possible ways of doing it I show bellow.

Well working code using onProgressUpdate() instead of onPostExecute():
public class Populator extends AsyncTask<Void, Drawable, Drawable> {
    
    @Override
    protected Drawable doInBackground(Void... params) {
      Drawable preview = null;
      preview = m.photo.getFullSizeImg();
      publishProgress(preview);
      preview=null;
      return null;
    }
    
    @Override
    protected void onProgressUpdate(Drawable... _preview) {
      imageView.setImageDrawable(_preview[0]);
    }
  }

Another well working code using class variable and onPostExecute():
  public class Populator extends AsyncTask<Void, Void, Void> {
    private Drawable preview = null;
    
    @Override
    protected Void doInBackground(Void... params) {
      preview = m.photo.getFullSizeImg();
      return null;
    }
    
    @Override
    protected void onPostExecute(Void params) {
      imageView.setImageDrawable(preview);
      preview = null;
    }
  }


Hope this will help you.

2 comments:

  1. i have a question, the preview is a local veriable, it's mean the preview will be destroy when the function return.why it causes the GC cann't remove the AsyncTask?

    ReplyDelete
  2. Well, the preview is a local in the doInBackground. But it is passed to the onPostExecute and then to imageView. As GC can't free object which are referenced from anywhere, it can't free the preview and can't free the AsyncTask. A typical memory leak.

    ReplyDelete