TomCB
TomCB

Reputation: 4063

Max size image to avoid OutOfMemoryException on Android

I find it hard to handle OutOfMemoryExceptions are hard to manage in Android. For now, I'm always resizing images in PhotoShop first, then I check on different devices and emulators to see how far I can push the limits.

The goal of my current application is to have an input image, map text and images on it and save it to a PDF. Right now I'm working with an image that is about 620x842 and 250kb in size and the output is not good enough. The original is 2480x3368 and 482kb.

Working with the original would cause OutOfMemoryExceptions, no doubt. But I'm wondering how to close I can get to the original.

Any ideas or tips on this?

Upvotes: 1

Views: 1479

Answers (5)

Jakob
Jakob

Reputation: 369

You can use the BitmapFactory to just get the bounds of the image and then load a smaller version of the image that fits in your current memory:

     /**
     * Decodes the given inputstream into a Bitmap, scales the bitmap to the
     * required height and width if image dimensions are bigger. Note: the
     * decoder will try to fulfill this request, but the resulting bitmap may
     * have different dimensions that precisely what has been requested.
     *
     * @param _inputStreamJustDecode The input stream for bound decoding
     * @param _inputStreamData The input stream for image decoding
     * @param _preview true to generate a preview image, false to generate a high quality image
     * @return
     */
    private Bitmap decodeSampledBitmapFromResource(InputStream _inputStreamJustDecode,InputStream _inputStreamData) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(_inputStreamJustDecode, null, options);
        float imageSize = options.outWidth * options.outHeight * 4;
        float scaleFactor = 1;
        long availableMem = getAvailableMemory(mContext);

        //new image should not use more than 66% of the available memory
        availableMem*=0.66f;
        if(imageSize > availableMem){
            scaleFactor = (float)availableMem / imageSize;
        }

        float maxDimen = options.outWidth > options.outHeight ? options.outWidth : options.outHeight;

        //Don't let the image get to big.
        if(maxDimen * scaleFactor > Constant.MAX_TEXTURE_SIZE ){
            scaleFactor = Constant.MAX_TEXTURE_SIZE/maxDimen;
        }

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, options.outWidth*scaleFactor,
                options.outHeight*scaleFactor);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        // set other options
        options.inPurgeable = true;
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        return BitmapFactory.decodeStream(_inputStreamData,null,options);
    }

     /**
     * Returns the currently available memory (ram) in bytes.
     * @param _context The context.
     * @return The available memory in bytes.
     */
    public long getAvailableMemory(Context _context){
        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
        ActivityManager activityManager = (ActivityManager) _context.getSystemService(Activity.ACTIVITY_SERVICE);
        activityManager.getMemoryInfo(mi);
        long availableMegs = mi.availMem;
        return availableMegs;
    }

Note: both input streams are from the same resource, but as you cant rewind the stream after decoding the bounds, you need a new stream.

Upvotes: 0

Boris Pawlowski
Boris Pawlowski

Reputation: 1771

You can try this :

 <activity
            android:name="..."
            android:configChanges="orientation|keyboardHidden"
            android:hardwareAccelerated="true"
            android:label="@string/app_name"
            android:largeHeap="true" >
        </activity>

Sometimes it work, the other times it dosnt. Keep in mind that this sizes are device specific so if you want you application to be public you should resize the images in all needed sizes. Lowest I've seen reacently was on the Samsung galaxy fame. Its heep will not load image larger than 2048x2048.

If you want to load a number of images and you are running out of memory you can try use thirparty lib like: http://square.github.io/picasso/ . It works pretty well for me and handles all the bitmap problems android nativly has.

The real problem is the size of the picture. Even if the picture has 300kb or 400kb when you scale it up to fit screensize it could go to incredibly large bitmaps like 20 or 30 mb that will overflow heap for this thread.

Upvotes: 0

Karol Żygłowicz
Karol Żygłowicz

Reputation: 2452

Are you decoding files acordingly to google guide? This solved problem with outOfMemoryException for me. Now i can easly decode files like 2676x3326 on galaxy SII

Upvotes: 1

njzk2
njzk2

Reputation: 39397

The original is 2480x3368 and 482kb

You are confusing image size and file size. 482KB is the size of the file, which is a compressed image.

2480x3368 is really 33MB in 32 bits ARGB. (That is a lot for a single object in memory, to allocate at once)

The size of the file is irrelevant to the size in memory occupied by the image.

Nota: This is a bit off topic, and probably should be a comment, but is rather too long for the comment format.

Upvotes: 3

Patrick Chan
Patrick Chan

Reputation: 1029

You can use Bitmap.Options while calling BitmapFactory.decodeFile(String,Options)

by setting inSampleSize=2, you can get the resultant Bitmap with dimension divided by 2.

Upvotes: 0

Related Questions