4

In my app i'm iterating through URLs of images, decoding and putting them in an ArrayList<Bitmap>.

They may vary greatly in size, so i'm doing a "pre-decode" with the inJustDecodeBounds = true option to calculate the necessary inSampleSize value for the actual decode.

See my method for this below, i hope it's not too hard to understand. Basically i'm aiming for a size similar to the screen size of the device.

for (Element e: posts) {
    if (!e.id().equals("")) {
        //preparing decode
        options = new BitmapFactory.Options();
        input = new URL(e.url).openStream();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(input, null, options);
        input.close();

        //setting inSampleSize if necessary
        int picPixels = options.outWidth * options.outHeight;
        int picScreenRatio = picPixels / screenPixels;
        if (picScreenRatio > 1) {
            int sampleSize = picScreenRatio % 2 == 0 ? picScreenRatio : picScreenRatio + 1;
            options.inSampleSize = sampleSize;
        }

        //actual decode
        input = new URL(e.url).openStream();
        options.inJustDecodeBounds = false;
        Bitmap pic = BitmapFactory.decodeStream(input, null, options);
        input.close();

        picList.add(pic);
    }
}

Code for calculating screenPixels:

Display display = getWindowManager().getDefaultDisplay();
Point screenSize = new Point();
display.getSize(screenSize);
int screenPixels = screenSize.x * screenSize.y;

I'm going through ~60 images and around 40 my app crashes with java.lang.OutOfMemoryError.

As i understand, with inJustDecodeBounds = true there's no memory allocated, and if my method is correct (i believe it is), very big images get very big inSampleSizes, so i don't know what could be the problem.

Would appreciate any advice.

3 Answers3

2

Sample size must be a power of two. From the documentation:

If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory. The sample size is the number of pixels in either dimension that correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1. Note: the decoder uses a final value based on powers of 2, any other value will be rounded down to the nearest power of 2.

Here's a good trick from the Android - Volley library on how to find the best sample size for bitmaps:

    int findBestSampleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
        double wr = (double) actualWidth / desiredWidth;
        double hr = (double) actualHeight / desiredHeight;
        double ratio = Math.min(wr, hr);
        float n = 1.0f;
        while ((n * 2) <= ratio) {
            n *= 2;
        }

        return (int) n;
    }
Gil Moshayof
  • 16,633
  • 4
  • 47
  • 58
2

No matter that you use inSampleSize you can't easily hold 60 bitmaps as large as your screen in memory.

Assuming a fullhd screen you get about 1920*1080*60*4 bytes.

Thats roughly 500 MB of data. You need to somehow change your bitmap loading logic.

In this answer you can find out how much memory you can count with. It differs for various devices.

But always try to make you app as memory efficient as possible.

Community
  • 1
  • 1
simekadam
  • 7,334
  • 11
  • 56
  • 79
0

It is notoriously hard to get image manipulation right on Android. I would recommend that you use an established Image Management Library like Facebook's Fresco:

https://github.com/facebook/fresco

An example from their docs:

Fresco's Drawees show a placeholder for you until the image has loaded and automatically show to the image when it arrives. When the image goes off-screen, it automatically releases its memory.

Also:

Apps using Fresco can run even on low-end devices without having to constantly struggle to keep their image memory footprint under control.

Heinrich Filter
  • 5,760
  • 1
  • 33
  • 34