androidlistviewout-of-memorymemory-optimization

Memory optimization for static images in listview


code :

private ListView lv;

private ArrayList<Integer> cd;
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_select);


        lv = (ListView) findViewById(R.id.lv);
        cd = new ArrayList<Integer>();

        cd1.add(R.drawable.fuld1);
        cd1.add(R.drawable.ful2);
        cd1.add(R.drawable.fu4);




        lv.setAdapter(new Select(this, cd1));
        lv.setOnItemClickListener(this);

    }

Adapter Class :

public class SelectAdapter extends BaseAdapter {

private Activity activity;
private LayoutInflater inflater;
private ViewHolder holder;
private ArrayList<Integer> list;


public SelectAdapter(Activity activity, ArrayList<Integer> list) {
    this.activity = activity;
    inflater = (LayoutInflater) activity
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    this.list = list;
}

@Override
public int getCount() {
    return 43;
}

@Override
public Object getItem(int arg0) {
    return arg0;
}

@Override
public long getItemId(int arg0) {
    return 0;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = inflater.inflate(R.layout.select_item, parent,
                false);
        holder = new ViewHolder();
        holder.iv = (ImageView) convertView
                .findViewById(R.id.ivSelect;

        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }


    holder.iv.setBackgroundResource(list.get(position));



    return convertView;
}

private class ViewHolder {
    ImageView ivCard;
}


public static int calculateInSampleSize(
        BitmapFactory.Options options, int reqWidth, int reqHeight) {
    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;
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
                                                     int reqWidth, int reqHeight) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

}

Log :

java.lang.OutOfMemoryError: Failed to allocate a 34560012 byte allocation with 4194304 free bytes and 14MB until OOM

Solution

  • You need to use BitmapFactory.Options. BitmapFactory.Options can be used to process Bitmap size and other properties without loading them into the memory by help of inJustDecodeBounds. In order to remove OutOfMemory error, you need to load a scaled down version of the Bitmap from your resources (drawable folder). This can be achieved by help of inSampleSize. If inSampleSize > 1, it requests the decoder to load a scaled down version into the memory saving you from OutOfMemory errors.

    Go through the following webpage for more details:

    http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

    Demo code:

    You will need the following two methods to process each bitmap or drawable file:

    public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
        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;
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
    
    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
                                                         int reqWidth, int reqHeight) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }
    

    The calculateInSampleSize method is used to calculate the inSampleSize required for each bitmap. The resultant inSampleSize value will be the best suitable value or the best fit to scale your bitmap to your specified requirements as you will specify by help of the arguments in the very same method.

    The method decodeSampleBitmapFromResource will decode the bitmap file from your app's resources and let you calculate the inSampleSize without allocating memory for the bitmap. The memory for the bitmap will only be allocated once the correct inSampleSize for that particular bitmap is calculated. This is accomplished by help of inJustDecodeBounds property for the BitmapFactory.Options object.

    Now, you just have to use these methods to add the bitmaps to your list view. For the sake of example, lets assume you have an ImageView in each element or row of your ListView. now, we will add the bitmap to the ImageView like this:

    imageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(),
                resID, imageView.getMaxWidth(), imageView.getMaxHeight()));
    

    Here, resID will be the Resource ID for your Bitmap and for the width and height I have currently used the width and height of the ImageView itself because I personally find it the best solution. But, you can use any value. Make sure, your value for width and height does not exceed the width and height of the view on which the bitmap will be placed.

    Updated segment of your code:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = inflater.inflate(R.layout.select_item, parent,
                    false);
            holder = new ViewHolder();
            holder.ivCard = (ImageView) convertView
                    .findViewById(R.id.ivSelect);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
    
    
        holder.ivCard.setImageBitmap(decodeSampledBitmapFromResource(parent.getResources(),
                list.get(position), holder.ivCard.getMaxWidth(), holder.ivCard.getMaxHeight()));
    
    
    
        return convertView;
    }
    

    Look at the last line of getView method. ivCard is your ImageView from your ViewHolder for your Adapter which will now use the method setImageBitmap to set the resource as a bitmap on the ImageView.