简体   繁体   中英

Memory optimization for static images in listview

  • 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

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM