Gaurav Arora
Gaurav Arora

Reputation: 17274

Bitmap Memory leak - Android

I'm having a memory leak in my app which is triggering GC a number of times and causing performance issues. I generated a leak suspect report using MAT. Here is the report:

Problem Suspect 1: One instance of "android.graphics.Bitmap" loaded by "" occupies 4,194,368 (20.13%) bytes. The memory is accumulated in one instance of "byte[]" loaded by "".

Problem Suspect 2: The class "android.content.res.Resources", loaded by "", occupies 3,962,504 (19.02%) bytes. The memory is accumulated in one instance of "java.lang.Object[]" loaded by "".

Problem Suspect 3: One instance of "android.graphics.Bitmap" loaded by "" occupies 3,145,792 (15.10%) bytes. The memory is accumulated in one instance of "byte[]" loaded by "".

Judging from the reports its obvious that memory leak is because of bitmap. I've researched a lot but couldn't rectify this leak. Please help me out. I'm using ImageLoader class to download and display bitmaps. To use this class I simply call the displayImage() method. Here is the code:

public class ImageLoader {

    private static ImageLoader imageLoader;
    private int maxNoOfConnections = 4;
    FileCache fileCache;
    ExecutorService executorService;
    HttpURLConnection conn;
    InputStream is;
    OutputStream os;
    PhotosLoader photosLoader;
    Handler handler;
    Bitmap bitmap;

    private ImageLoader(Context context) {
        fileCache = new FileCache(context);
        executorService = Executors.newFixedThreadPool(maxNoOfConnections);
        handler = new Handler();
    }

    public static ImageLoader getInstance(Context context) {
        if (imageLoader == null)
            imageLoader = new ImageLoader(context);
        return imageLoader;
    }

    public void displayImage(String url, ProgressBar pBar, ImageView imageView) {
        photosLoader = new PhotosLoader(url, imageView, pBar);
        executorService.submit(photosLoader);
    }

    private Bitmap getBitmap(String url) {
        File f = fileCache.getFile(url);

        bitmap = decodeFile(f);
        if (bitmap != null)
            return bitmap;

        try
        {
            URL imageUrl = new URL(url);
            conn = (HttpURLConnection) imageUrl.openConnection();
            conn.setConnectTimeout(30000);
            conn.setReadTimeout(30000);
            conn.setInstanceFollowRedirects(true);
            is = conn.getInputStream();
            os = new FileOutputStream(f);
            Utils.CopyStream(is, os);
            os.close();
            bitmap = decodeFile(f);
            return bitmap;
        } catch (Exception ex)
        {
            Log.e("inNews", "Image Url Malformed");
            return null;
        }
    }

    private Bitmap decodeFile(File f) {
        try
        {
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(f), null, o);

            final int REQUIRED_SIZE = 70;
            int width_tmp = o.outWidth, height_tmp = o.outHeight;
            int scale = 1;
            while (true)
            {
                if (width_tmp / 2 < REQUIRED_SIZE || height_tmp / 2 < REQUIRED_SIZE)
                    break;
                width_tmp /= 2;
                height_tmp /= 2;
                scale *= 2;
            }

            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize = scale;
            return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
        } catch (FileNotFoundException e)
        {
        }
        return null;
    }

    class PhotosLoader implements Runnable {
        String url;
        ImageView imageView;
        ProgressBar pBar;
        Bitmap bmp;

        public PhotosLoader(String url, ImageView imageView, ProgressBar pBar) {
            this.url = url;
            this.imageView = imageView;
            this.pBar = pBar;
        }

        @Override
        public void run() {
            bmp = getBitmap(url);
            handler.post(new Runnable() {
                @Override
                public void run() {
                    if (bmp != null)
                    {
                        pBar.setVisibility(View.GONE);
                        imageView.setImageBitmap(bmp);
                    } else
                    {
                        pBar.setVisibility(View.GONE);
                        imageView.setImageResource(R.drawable.img_no_image_grid);
                    }
                }
            });
        }
    }

}

Please help me rectify my code. Thanks!

Note: I've not used bitmap.recycle() since, the documentation says that post-Honeycomb the GC collects bitmaps and its no longer necessary to forcefully recycle it !

Upvotes: 4

Views: 5265

Answers (4)

Divyesh Rudani
Divyesh Rudani

Reputation: 249

when you have to initialise a library in an activity, always pass the application context, not the activity context.

Upvotes: 0

Akshat Agarwal
Akshat Agarwal

Reputation: 2847

I think the answer is simple, just keep clearing cache when you dont need memory/when OOM exception arises. I did that for you

MemoryCache memoryCache = new MemoryCache();

try {
        Bitmap bitmap = null;
        URL imageUrl = new URL(url);
        HttpURLConnection conn = (HttpURLConnection) imageUrl
                .openConnection();
        conn.setConnectTimeout(30000);
        conn.setReadTimeout(30000);
        conn.setInstanceFollowRedirects(true);
        InputStream is = conn.getInputStream();
        OutputStream os = new FileOutputStream(f);
        Utils.CopyStream(is, os);
        os.close();
        conn.disconnect();
        bitmap = decodeFile(f);
        return bitmap;
    } catch (Throwable ex) {
        ex.printStackTrace();
        if (ex instanceof OutOfMemoryError)
            memoryCache.clear();
        return null;
    }

Upvotes: 0

Nicolas Jafelle
Nicolas Jafelle

Reputation: 2771

I think the problem is the Singleton instance... I made a fork of the LazyList project, check this:

https://github.com/nicolasjafelle/LazyList

I have the same Memory Leak but, and maybe I am wrong, this singleton will never be garbage collected unless you kill the process with System.exit().

That is why the original LazyList project not use Singleton. I also think that if you need a cache you will need to be fast and the same for all the application.

The important thing in this ImageLoader is the FileCache and MemoryCache, when you call clearCache the bitmaps where collected.

Upvotes: 0

nurisezgin
nurisezgin

Reputation: 1570

Memory leak problem always Java's problems. I understand your code, code simple imagecache tool. Check SampleSize value and executor service run on only one thread. Four Thread have big memory and this background thread action. Your "handler" exchange to "runOnUIThread"

You should use;

Activity activity = (Activity)imageView.getContext();

__activity__.runOnUIThread(new Runnable()
{
 if (bmp != null)
                    {
                        pBar.setVisibility(View.GONE);
                        imageView.setImageBitmap(bmp);
                    } else
                    {
                        pBar.setVisibility(View.GONE);
                        imageView.setImageResource(R.drawable.img_no_image_grid);
                    }
});

Upvotes: 0

Related Questions