Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loading large bitmaps to ImageView in ViewPager - out of memory

I have ViewPager which I use to show zoomable images (using ImageViewTouch). I need to load large bitmaps from Internet (http). By large I mean 2000x1000. The images need to be so large, because they are zoomable and need to show details. Images on server are .jpg format, but it's not problem - I can change it.

How can I manage to load so large images to ImageViewTouch (ImageView) without getting probles with memory?

By now I'm using simply this (AsyncTask):

    ImageView currentView; //ImageView where to place image loaded from Internet
    String ImageUrl; //URL of image to load

    protected Bitmap doInBackground(ArrayList... params) {

            Bitmap bitmap;
            InputStream in = null;
            imageUrl = (String)params[0].get(1);

            try{
                HttpClient httpclient = new DefaultHttpClient();
                HttpResponse response = httpclient.execute(new HttpGet(imageUrl));
                in = response.getEntity().getContent();
            } catch(Exception e){
                e.printStackTrace();
            }
            try {
                bitmapa = BitmapFactory.decodeStream(in);
                in.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }

            return bitmap;
        }


        @Override
        protected void onPostExecute(Bitmap bitmap) {

            currentView.setImageBitmap(bitmap);
        }

And it cause many problems with memory:

E/dalvikvm-heap(369): 69560740-byte external allocation too large for this process.
E/GraphicsJNI(369): VM won't let us allocate 69560740 bytes

or

E/AndroidRuntime(369): java.lang.RuntimeException: An error occured while executing doInBackground()
E/AndroidRuntime(369):  at android.os.AsyncTask$3.done(AsyncTask.java:200)
E/AndroidRuntime(369):  at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:274)
E/AndroidRuntime(369):  at java.util.concurrent.FutureTask.setException(FutureTask.java:125)
E/AndroidRuntime(369):  at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:308)
E/AndroidRuntime(369):  at java.util.concurrent.FutureTask.run(FutureTask.java:138)
E/AndroidRuntime(369):  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
E/AndroidRuntime(369):  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
E/AndroidRuntime(369):  at java.lang.Thread.run(Thread.java:1019)
E/AndroidRuntime(369): Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
E/AndroidRuntime(369):  at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
E/AndroidRuntime(369):  at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:470)
E/AndroidRuntime(369):  at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:525)
E/AndroidRuntime(369):  at com.package.app.ImageDownloader.doInBackground(ImageDownloader.java:78)
E/AndroidRuntime(369):  at com.package.app.ImageDownloader.doInBackground(ImageDownloader.java:1)
E/AndroidRuntime(369):  at android.os.AsyncTask$2.call(AsyncTask.java:185)
E/AndroidRuntime(369):  at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:306)
E/AndroidRuntime(369):  ... 4 more
like image 374
michalsol Avatar asked Feb 03 '13 15:02

michalsol


2 Answers

What version of the OS do you test this on? On earlier versions the heap space available is much lower than on later versions.

I would seriously consider downsizing your bitmaps to avoid this. If you are downloading a 2000x1000 Bitmap for an mdpi phone then this is probably a bad idea.

I would also recommend reading this article for more information on how to load the appropriate image exactly for a specific ImageView, based on its dimensions.

Finally, you should always close an InputStream in a finally block:

try {
        bitmap = BitmapFactory.decodeStream(in);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (in != null) { in.close(); }
    }

There is also android:largeHeap="true" which you can consider, specifically made for apps that deal with large bitmaps, such as photo editing apps.

like image 116
dnkoutso Avatar answered Sep 20 '22 00:09

dnkoutso


A 2000x1000 pixel image is large, but not THAT large.

I see, though that the process is trying to allocate 69560740 bytes before it dies, that is about 66MByte!

Your 2000x1000, in worst case, is about 2000x1000x4 = 8MBytes, way less than 66MBytes. Something else is going on.

Also, I found an issue with using Bitmaps as a return/result value in AsyncTasks. AsyncTasks that have finished still hold on to their result until they are garbage collected. Since you don't control the garbarge collection of AsyncTask instances, don't use Bitmap as a return/result value. Instead put the Bitmap in a field and assign it when the doInBackground is about to end, and use this field to get the Bitmap in onPostExecute and(!) set this field to null:

Bitmap bitmap;
protected Void doInBackground(ArrayList... params) {

        InputStream in = null;
        imageUrl = (String)params[0].get(1);

        try{
            HttpClient httpclient = new DefaultHttpClient();
            HttpResponse response = httpclient.execute(new HttpGet(imageUrl));
            in = response.getEntity().getContent();
        } catch(Exception e){
            e.printStackTrace();
        }
        try {
            bitmap = BitmapFactory.decodeStream(in);
            in.close();
        } catch (IOException e1) {
            e1.printStackTrace();
        }

        return null;
    }


    @Override
    protected void onPostExecute(Void unused) {
        if (bitmap != null) {
            currentView.setImageBitmap(bitmap);
        }

        // And don't forget to null the bitmap field!
        bitmap = null; 
    }
like image 39
Streets Of Boston Avatar answered Sep 18 '22 00:09

Streets Of Boston