17

In my application I load a couple of images from JPEG and PNG files. When I place all those files into assets directory and load it in this way, everything is ok:

InputStream stream = getAssets().open(path);
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null);
stream.close();
return new BitmapDrawable(bitmap);

But when I try to load the exact same images from sd card, I get an OutOfMemory exception!

InputStream stream = new FileInputStream("/mnt/sdcard/mydata/" + path);
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null);
stream.close();
return new BitmapDrawable(bitmap);

This is what I get in the log:

11-05 00:53:31.003: ERROR/dalvikvm-heap(13183): 827200-byte external allocation too large for this process.
11-05 00:53:31.003: ERROR/GraphicsJNI(13183): VM won't let us allocate 827200 bytes
...
11-05 00:53:31.053: ERROR/AndroidRuntime(13183): Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
11-05 00:53:31.053: ERROR/AndroidRuntime(13183):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
...

Why can this happen?

UPDATE: Tried both of these on real device - it seems that I can't load more than 12MB of bitmaps into whatever is called "external memory" (this is not an sd card).

Fixpoint
  • 9,619
  • 17
  • 59
  • 78
  • Under what condition are you testing the above code? In Emulator, or Real Device attaching USB? Chances is your USB mode is set to Disk mode that locks the SD Card. – xandy Nov 05 '10 at 00:59
  • I run this code in the emulator. – Fixpoint Nov 05 '10 at 07:37
  • What's the size of jpg/png files? – Fedor Nov 08 '10 at 08:10
  • File size of largest JPG I load is 400KB, it's 800x600x24. – Fixpoint Nov 09 '10 at 22:43
  • Do you have 800x600 screen. If you don't you should supersample images to use less memory. http://stackoverflow.com/questions/477572/android-strange-out-of-memory-issue/823966#823966 – Fedor Nov 10 '10 at 13:25
  • Maybe you should break the image up into 4 or 8 or 16 tiles. Quite possibly you have enough free memory, but not for one massive contiguous allocation. – Reuben Scratton Nov 12 '10 at 11:21
  • Reuben, I tried to do this with a small image (~50KB in bitmap form), loading it into memory repeatedly, and at total of 12MB I always have an exception if I load it from an SD card. – Fixpoint Nov 13 '10 at 22:07

12 Answers12

8

I tried all the approaches mentioned here & at other resources but I came to the inference that setting ImageView's reference to null will solve the issue:

  public Bitmap getimage(String path ,ImageView iv)
   {
    //iv is passed to set it null to remove it from external memory
    iv=null;
    InputStream stream = new FileInputStream("/mnt/sdcard/mydata/" + path);
    Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null);
    stream.close();
    stream=null;
    return bitmap;
    }

& you are done!

Note:Though it may solve above problem but I would suggest you to check Tom van Zummeren 's optimized image loading.

And also check SoftReference: All SoftReferences pointing to softly reachable objects are guaranteed to be cleared before the VM will throw an OutOfMemoryError.

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
100rabh
  • 6,156
  • 5
  • 27
  • 41
5
  • When doing a lot with bitmaps, don't debug the app - just run it. The debugger will leave memory leaks.
  • Bitmaps are very expensive. If possible, scale them down on load by creating BitmapFactory.Options and setting inSampleSize to >1.

EDIT: Also, be sure to check your app for memory leaks. Leaking a Bitmap (having static Bitmaps is an excellent way to do that) will quickly exhaust your available memory.

EboMike
  • 76,846
  • 14
  • 164
  • 167
  • I'm not debugging this application, just running it. The rest of the code is 100% same, so how could it be that bitmaps loaded from one place leak while others don't? Regarding scaling - I'd like to use images in high quality (there should be enough memory to load them, because they load without errors from assets). – Fixpoint Nov 05 '10 at 07:40
4

Probably nothing wrong with your API usage, I guess all we can do is infer that using the AssetManager involves less behind-the-scenes heap allocation than opening a random file from the SD card.

800KB is a serious allocation in anybody's book... this will doubtless be for the decompressed image pixels. Given that you know the size of the image, what depth is it? If it's 32bpp then try overriding that using inPreferredConfig.

Reuben Scratton
  • 38,595
  • 9
  • 77
  • 86
  • So, it really seems that AssetManager on *emulator only* can avoid total loaded bitmap size cap. :( – Fixpoint Nov 13 '10 at 22:05
3

This is a fairly common issue which all of us face while loading images from the sdcard.

The solution as I found was to use inJustDecodeBounds first while loading the image using decodeFileDescriptor . That would not actually decode the image, but give the image size. Now I can scale it appropriately(using the options) so as to resize the image for the display area. Its needed because low memory on the phone can be easily taken over by your 5MP image. This I believe is the most elegant solution.

the100rabh
  • 4,077
  • 4
  • 32
  • 40
  • I was contemplating it but that also means I would have to make 2 requests for one image – Bostone Oct 30 '11 at 16:42
  • Yes but I havnt faced any slowdown because of two calls possibly because the inJustDecodeBounds doesnt do a lot of work – the100rabh Nov 01 '11 at 13:48
2

There are two issues here....

  • Bitmap memory isn't in the VM heap but rather in the native heap - see BitmapFactory OOM driving me nuts
  • Garbage collection for the native heap is lazier than the VM heap - so you need to be quite aggressive about doing bitmap.recycle and bitmap =null every time you go through an Activity's onPause or onDestroy
Community
  • 1
  • 1
Torid
  • 4,176
  • 1
  • 28
  • 29
1
The best solution i found and edited according to my need

public static Bitmap getImageBitmap(String path) throws IOException{
        // Allocate files and objects outside of timingoops             
        File file = new File(thumbpath);        
        RandomAccessFile in = new RandomAccessFile(file, "rws");
        final FileChannel channel = in.getChannel();
        final int fileSize = (int)channel.size();
        final byte[] testBytes = new byte[fileSize];
        final ByteBuffer buff = ByteBuffer.allocate(fileSize);
        final byte[] buffArray = buff.array();
        @SuppressWarnings("unused")
        final int buffBase = buff.arrayOffset();

        // Read from channel into buffer, and batch read from buffer to byte array;
        long time1 = System.currentTimeMillis();
        channel.position(0);
        channel.read(buff);
        buff.flip();
        buff.get(testBytes);
        long time1 = System.currentTimeMillis();
        Bitmap bmp = Bitmap_process(buffArray);
        long time2 = System.currentTimeMillis();        
        System.out.println("Time taken to load: " + (time2 - time1) + "ms");

        return bmp;
    }

    public static Bitmap Bitmap_process(byte[] buffArray){
        BitmapFactory.Options options = new BitmapFactory.Options();

        options.inDither=false;                     //Disable Dithering mode
        options.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
        options.inInputShareable=true;              //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
        options.inTempStorage=new byte[32 * 1024];  //Allocate some temporal memory for decoding

        options.inSampleSize=1;

        Bitmap imageBitmap = BitmapFactory.decodeByteArray(buffArray, 0, buffArray.length, options);
        return imageBitmap;
    }
Arun
  • 196
  • 3
  • 6
1

Thanks to all the threads, I've found a solution that works for me on a real device. The tricks are all about using

BitmapFactory.Options opts=new BitmapFactory.Options();
opts.inSampleSize=(int)(target_size/bitmap_size); //if original bitmap is bigger

But for me this was not enough. My original image (taken from the Camera app) was 3264x2448. The correct ratio for me was 3, since i wanted a plain VGA image of 1024x768.

But setting inSampleSize to 3 was not enough: still out of memory exception. So in the end I opted for a iterative approach: I start from the computed correct size, and increase it until I stop having a OOM exception. For me it was at sample of 4.

// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
// o2.inSampleSize = scale;
float trueScale = o.outWidth / 1024;
o2.inPurgeable = true;
o2.inDither = false;
Bitmap b = null;
do {
     o2.inSampleSize = (int) trueScale;
     Log.d(TAG, "Scale is " + trueScale);
 try {
    b = BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
    } catch (OutOfMemoryError e) {
        Log.e(TAG,"Error decoding image at sampling "+trueScale+", resampling.."+e);
        System.gc();
    try {
        Thread.sleep(50);
     } catch (InterruptedException e1) { 
         e1.printStackTrace();
     }
}
    trueScale += 1;
} while (b==null && trueScale < 10);
return b;
zontar
  • 485
  • 7
  • 18
1

Instead of loading it from the SD Card directly, why not move the image to the cache in the phone's internal storage using getCacheDir() or use a temp directory to store the images in?

See this, this on external memory usage. Also, this article may be of relevance to you.

Skaty
  • 467
  • 2
  • 6
  • 19
1

Use the below code and you will never get the following error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget

              BitmapFactory.Options bounds = new BitmapFactory.Options();

              bounds.inSampleSize = 4;

              myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bounds);

              picturesView.setImageBitmap(myBitmap);
krisDrOid
  • 3,252
  • 3
  • 25
  • 36
0

Allows inSampleSize resize the final read image. getLength() of AssetFileDescriptor allows get size of file.

You can vary inSampleSize according to getLength() to prevent OutOfMemory like this :

private final int MAX_SIZE = 500000;

public Bitmap readBitmap(Uri selectedImage)
{
    Bitmap bm = null;
    AssetFileDescriptor fileDescriptor = null;
    try
    {
        fileDescriptor = this.getContentResolver().openAssetFileDescriptor(selectedImage,"r");
        long size = fileDescriptor.getLength();
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = (int) (size / MAX_SIZE);
        bm = BitmapFactory.decodeFileDescriptor(fileDescriptor.getFileDescriptor(), null, options);
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
    finally
    {
        try {
            if(fileDescriptor != null) fileDescriptor.close();
        } catch (IOException e) {}
    }
    return bm;
}
fingerup
  • 4,828
  • 2
  • 16
  • 22
0

Try this another way...

Bitmap bmpOrignal = BitmapFactory.decodeFile("/sdcard/mydata/" + path");
CoolBeans
  • 20,654
  • 10
  • 86
  • 101
Raju Jadhav
  • 57
  • 1
  • 6
0

You must not depends on the GC to recycle your bitmap memory. You must clearly recycle the bitmap when it is not needed.

See the Bitmap method:

void recycle() Free up the memory associated with this bitmap's pixels, and mark the bitmap as "dead", meaning it will throw an exception if getPixels() or setPixels() is called, and will draw nothing.

imcaptor
  • 360
  • 1
  • 8
  • I do not want to recycle all those bitmaps, I'm using them. – Fixpoint Nov 10 '10 at 14:13
  • If one bitmaps is hide to user, you'd better recycle them. If they show again, you load them again.The phone's memory is limited. – imcaptor Nov 11 '10 at 05:05
  • When trying to load a bitmap that has been recycled it will throw an exception saying can not load a recycled bitmap... – AjOnFire Dec 22 '10 at 05:51