Phate
Phate

Reputation: 6612

Memory leak with <a load image in a listview> asynctask

I acknoledged a memory leak when I saw that my small service, without any activity running, took about 15-16 MB. Normally (at the system boot) its memory usage was about 3MB, but if I opened my app memory usage increased, even after closing it!

Looking closer this memory usage went up with number of activities I opened! So I payed attention to my activities: in my app I have 3 of them and everyone has a listview, whose cells' contents are loaded from the net and each element is made like this:

|imageview |some text |

I think problem is in the async task in the adapter, so heres the code:

public class MyAdapter extends CursorAdapter{
    private final LayoutInflater mInflater;
    private LruCache<String, Bitmap> cachedBitmaps;
    private SimpleDateFormat srcFormat; //I have to convert dates
    private SimpleDateFormat dstFormat;


   @Override
public void bindView(View view, Context context, Cursor c) {
    ViewHolder holder = (ViewHolder)view.getTag();
    if(holder == null){
        holder = new ViewHolder();
        holder.cellImg = (ImageView)view.findViewById(R.id.cellImg); 
        holder.cellProgress = view.findViewById(R.id.cellProgress);
        holder.titleText = (TextView)view.findViewById(R.id.titleText);
        view.setTag(holder);
    }
            try{
        String imageLink = c.getString(c.getColumnIndex("image_link"));
        String guid = c.getString(c.getColumnIndex("guid"));
        Bitmap bitmap = cachedBitmaps.get(guid);

        if(bitmap == null){ //need to download
            ImageView image = holder.cellImg; 
            image.setTag(guid);
            DownloadBitmapTask task = new DownloadBitmapTask(context,image,
                    holder.cellProgress);
            task.execute(new String[]{imageLink});
                     //else I just get it from cache

Using the holder and the cache I make sure to efficiently load the image...I don't see any errors here but I shared this with you because I'd like to give you a complete view! Now here's the task, where I think there's a problem:

private class DownloadBitmapTask extends AsyncTask<String, Void, Bitmap>{

    private ImageView img;
    private View progress;
    private String tag;
            //normally you see a progress bar, when image is loaded I substitute it
    public DownloadBitmapTask(Context c,ImageView img,View progress){
        this.progress = progress;
        this.img = img;
        tag = new String(img.getTag().toString());
    }

    @Override
    protected void onPreExecute() {
        progress.setVisibility(View.VISIBLE);
        img.setVisibility(View.INVISIBLE);
    }

    private final int MAX_TRIES = 3;

    @Override
    protected Bitmap doInBackground(String... args) {
        Bitmap bitmap = null;
        int failedTries = 0;
        boolean done = false;
        while((!done) && (failedTries<MAX_TRIES)){
            try{
                URLConnection conn = (new URL(args[0])).openConnection();
                bitmap = BitmapFactory.decodeStream(
                        conn.getInputStream());

                done = true;
            }catch(IOException ex){
                failedTries++;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
            }
        }

        return bitmap;
    }



    @Override
    protected void onPostExecute(Bitmap result) {
        progress.setVisibility(View.INVISIBLE);

        if( (result != null) && (img.getTag().toString().equals(tag)) ){
            cachedBitmaps.put((String)img.getTag(), result);
            img.setImageBitmap(result);
            img.setVisibility(View.VISIBLE);
            img.getParent().recomputeViewAttributes(img);
            notifyDataSetChanged();
        }
    }

}

I can't see any memory leak...but I'm sure there is! Please help me :(

EDIT: MAT tool gives me this:

The class "android.content.res.Resources", loaded by "", occupies 5.033.704 (38,96%) bytes. The memory is accumulated in one instance of "java.lang.Object[]" loaded by "".

They are all bitmaps, of curse :(

Upvotes: 3

Views: 1659

Answers (3)

Animesh Sinha
Animesh Sinha

Reputation: 1045

I think you should close the Input stream every time you open as shown below.

InputStream iStream = null;
try{
     URLConnection conn = (new URL(args[0])).openConnection();
     iStream = conn.getInputStream(); 
     itmap = BitmapFactory.decodeStream(iStream);

     done = true;
   }catch(IOException ex){
      failedTries++;
      try {
           Thread.sleep(100);
          } catch (InterruptedException e) {
          }
   } finally {
      if(iStream != null)
          iStream.close();
   }

NOTE:- And it is advisable to close any streams after it is used.

Upvotes: 0

Luis
Luis

Reputation: 12048

Try to set your task created in:

        DownloadBitmapTask task = new DownloadBitmapTask(context,image, 
                holder.cellProgress); 

to null when the bitmap download is completed. You are not closing/disposing all resources allocated in AsyncTask and GC can't release them.

Upvotes: 0

icastell
icastell

Reputation: 3605

I think the problem is with BitmapFactory in the doInBackground method. This decoding consumes a lot of memory and also has some known leaks. Instead of decode the entire image, I always scale it in order to reduce the memory consumption. There is an example of this:

  //decodes image and scales it to reduce memory consumption
  private static Bitmap decodeImage(InputStream in, InputStream in2){

          //Decode image size
          BitmapFactory.Options o = new BitmapFactory.Options();
          o.inJustDecodeBounds = true;
          BitmapFactory.decodeStream(in,null,o);

          //The new size we want to scale to
          final int REQUIRED_SIZE=100;

          //Find the correct scale value. It should be the power of 2.
          int scale=1;
          while(o.outWidth/scale/2>=REQUIRED_SIZE && o.outHeight/scale/2>=REQUIRED_SIZE)
              scale*=2;

          //Decode with inSampleSize
          BitmapFactory.Options o2 = new BitmapFactory.Options();
          o2.inSampleSize=scale;
          o2s.inTempStorage = new byte[16*1024];

          return BitmapFactory.decodeStream(in2, null, o2);

  }

NOTE: You'll need open two instances of your inputstream.

Other advice is to call the method clear() in Bitmap every time the image is no longer useful.

I hope this helps you!

Upvotes: 1

Related Questions