0
  • I have 2 different list-view filed with images only

  • Best solution for memory optimization for static images in list-view

  • I am having memory issue every time Out of memory issue

  • Every solution is regarding dynamic images or loading images from web-service

  • What about static image ?

  • I am having about 70-80 images in listview (total)

  • Code is not required as i am simply filling listview with images, no web-service is used.

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
tiger
  • 413
  • 1
  • 5
  • 18

2 Answers2

1

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.

Varun Kumar
  • 1,241
  • 1
  • 9
  • 18
  • No, I can't. But, you need to use the inJustDecodeBounds and inSampleSize technique to load the bitmaps efficiently. Loading bitmaps directly without scaling them down will definitely lead to OutOfMemory errors. – Varun Kumar Mar 19 '16 at 10:18
  • If you still don't want to do this, then edit your images in any image editing tool like Photoshop and scale them down. Then your code might work. But, I can still not guarantee success with this method. inSampleSize method is definitely successful. – Varun Kumar Mar 19 '16 at 10:19
  • There is one more way which just might work, in case you are not ready to try inSampleSize. You can use Bitmap.createScaledBitmap() method of Bitmap class to scale your bitmaps down. This code is very small as compared to inSampleSize and might do the trick. – Varun Kumar Mar 19 '16 at 10:23
  • what if i use all size images i.e. mdpi,hdpi,xhdpi,xxhdpi ?? would that make any difference ? – tiger Mar 19 '16 at 12:04
  • It will make a difference. But, for images in hdpi and above drawable folders you will require to scale down, since the size of those images will still remain quiet large. Especially xxhdpi images will consume huge amount of memory. – Varun Kumar Mar 19 '16 at 12:11
  • it is difficult to understand . if i see code ,it would be easy – tiger Mar 19 '16 at 12:14
  • Ok, give me a couple of minutes to write a demo code. – Varun Kumar Mar 19 '16 at 12:19
  • i am getting error in (list.get(position))) place ,ie. cannot be applied to integer – tiger Mar 19 '16 at 12:55
  • In resID, you have to add the id for your own bitmaps from the resource folder of your app. – Varun Kumar Mar 19 '16 at 13:01
  • Please post your complete adapter code, I'll be able to help you in a better way then. – Varun Kumar Mar 19 '16 at 13:02
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/106798/discussion-between-tiger-and-varun-kumar). – tiger Mar 19 '16 at 13:04
  • @ Varun Kumar , can we discuss in chat ? – tiger Mar 19 '16 at 13:06
  • Ya sure, but chat room link that you created above is not working for me. – Varun Kumar Mar 19 '16 at 13:11
  • can u create the link ? – tiger Mar 19 '16 at 13:11
  • Tried it a couple of times, can't create the link. – Varun Kumar Mar 19 '16 at 13:15
  • I'll continue trying, meanwhile, continue here only – Varun Kumar Mar 19 '16 at 13:16
  • check my adpater now – tiger Mar 19 '16 at 13:17
  • 1
    You've just posted one single line of code. Atleast post that complete method because your line of code has a couple of bugs and I need to see more of your code to rectify the errors. – Varun Kumar Mar 19 '16 at 13:18
  • check now .. full adpater clas – tiger Mar 19 '16 at 13:18
  • 1
    Check my updated answer. In the last I've added segment from your code. Your getView method needed a little change in the last line. Try it out. – Varun Kumar Mar 19 '16 at 13:28
1

Provided you display only 5-10 images on the screen at a time, and each image is just a few hundred kb at max, using a normal recycled list view should be enough to avoid a OOM.

add all your drawable resource Ids to a listview first

imageResList.add(R.drawable.fulllogocard1)
imageResList.add(R.drawable.fulllogocard2)
imageResList.add(R.drawable.fulllogocard3)
imageResList.add(R.drawable.fulllogocard4)
......

Then, implement your adapter as follows:

public ImageViewAdapter extends BaseAdapter
{
    @Override
    public int getCount()
    {
        return imageList.size();
    }

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

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

    @Override
    public View getView(int i, View existingView, ViewGroup viewGroup)
    {
        if(existingView == null)
        {
            ImageView imageView = new ImageView(viewGroup.getContext());
            imageView.setImageResource(imageResList.get(i));
            return imageView;
        }
        else 
        {
            ImageView imageView = ((ImageView) existingView);
            imageView.setImageResource(imageResList.get(i));
            return imageView;
        }
    }

}

The adapter simply recycles existing listView views, so at any given point of time only the visible views (and therefore only the visible images) are rendered on the screen at a given time.

This is just a workaround/hack. Ideally, you would want to use a image library built for this purpose, such as Universal image loader, glide, picasso, or fresco

Picasso v/s Imageloader v/s Fresco vs Glide

Community
  • 1
  • 1
Manoj Madanmohan
  • 1,214
  • 1
  • 12
  • 14