Android - ImageView with large Bitmaps and OutOfMemory failures

   Android's recommendation to deal with OutOfMemory failures is to optimize the usage of Bitmaps in ImageView. The approach is to determine the original dimensions of the source image and then scale it down to the actual size of the hosting ImageView. Note that this scaling happens in the Bitmap decoding phase and there by reduces the memory usage. Developers might misunderstand this with ImageView's scale feature like FIT_CENTER, FIT_END. The difference is that ScaleType doesn't really reduce the memory footprint as it is achieved during the rendering phase and not during Bitmap decoding phase.
   
     The ImageView always holds an active reference to the last set Drawable resource and in case of Bitmaps, it would be a BitmapDrawable. The BitmapDrawable in turn holds a reference to the decoded Bitmap (of original dimensions) and this chain holds true until another Drawable is set or until the ImageView is removed from the View hierarchy and eventually garbage collected.

    So why wouldn't Android developers provide an optimized version of ImageView with their own suggestion to reduce memory footprint? The reason is because an ImageView might be resized for a number of reasons (scaling, animations etc) and its new height and width would be known only after its measured during the layout and measure phase and hence its necessary to keep track of the original bitmap in order to be able to render it without any pixelation or data loss.

   However, any application which uses a fixed height and width ImageView could very well extend ImageView for easy usage across the application.

public class MyImageView extends ImageView {

    public MyImageView(Context context) {
        super(context);
    }

    public MyImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setImage(int resId) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(), resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, getWidth(), getHeight());

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        setImageBitmap(BitmapFactory.decodeResource(getResources(), resId, options));
    }

    private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}

No comments: