Loading large bitmaps to ImageView in ViewPager - out of memory
Asked Answered
C

2

6

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
Compressive answered 3/2, 2013 at 15:39 Comment(1)
C
2

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.

Catherine answered 3/2, 2013 at 16:18 Comment(4)
Thanks for answer. My target SDK Version is 15 and minimum is 4. I've already read that article but it didn't help me - the problem here is that I can't resize or scale my image, because it must stay zoomable and show details when zoomed.Compressive
I won't lie to you, you will not find a possible answer to fit a 2000x1000 image in memory. Think about it, no matter what you do you will exceed available heapspace. Perhaps you can find a different strategy similar to how Chrome web browser works, render only part of the image in memory, as the user pans around you quickly reload a new window based on the current viewport.Catherine
I thought about that - reloading zoomed part of image - but it looks quite hard to implement. Do you have any tutorials of it? Thanks!Compressive
You could take a look at the Gallery app source code and see what they do. Those images are pretty big coming from the camera of the phone so maybe thats a good place to start!Catherine
N
2

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; 
    }
Nazar answered 6/2, 2013 at 0:55 Comment(2)
As an extra comment to my previous answer. I think that your server returns very very large images, because the VM tries to allocate 69,500,000 bytes. This would be a ARGB_8888 image of almost 3000x3000 pixels.Nazar
This solution really improved my memory usage. I was just doing what you said , starting with "Also, I found an issue with using Bitmaps as a return/result value in AsyncTasks......." I modified my code as you said and now I can display around x2 times more image without crashing. Thank youSynsepalous

© 2022 - 2024 — McMap. All rights reserved.