NeuroAgent

Fix OutOfMemoryError in Android ListView Images

Learn how to fix OutOfMemoryError when loading images into Bitmap in Android ListView. Discover on-the-fly resizing techniques, memory management strategies, and best practices for efficient image loading in list views.

How to resolve OutOfMemoryError when loading images into Bitmap in Android ListView?

I’m developing an Android application with a ListView containing image buttons on each row. When users click a list row, it launches a new activity. I’ve built custom tabs due to camera layout issues. The launched activity is a map, and when I click a button to launch an image preview (loading an image from the SD card), the application returns to the ListView activity and then attempts to relaunch the image preview activity.

The image preview in the ListView is implemented using a cursor and ListAdapter. I need to resize images (in terms of bit size, not pixel dimensions) on the fly for the image button’s src attribute, so I’ve been resizing the images from the phone camera.

The issue is that I get an OutOfMemoryError when the application tries to go back and re-launch the second activity.

Key questions:

  1. Is there a way to build the list adapter row by row, allowing for on-the-fly bitwise resizing of images?
    This would be preferable as I also need to modify widget properties in each row due to focus issues with the touch screen (though rollerball navigation works).

  2. I’m aware of out-of-band resizing and saving images as an alternative, but I’d prefer not to use this approach. However, sample code for this method would still be helpful.

Disabling the image in the ListView resolves the issue, confirming that the problem is related to image loading.

Current implementation:

java
String[] from = new String[] { DBHelper.KEY_BUSINESSNAME, DBHelper.KEY_ADDRESS,
    DBHelper.KEY_CITY, DBHelper.KEY_GPSLONG, DBHelper.KEY_GPSLAT,
    DBHelper.KEY_IMAGEFILENAME  + ""};
int[] to = new int[] { R.id.businessname, R.id.address, R.id.city, R.id.gpslong,
    R.id.gpslat, R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);

Where R.id.imagefilename is a ButtonImage.

Error logs show:

ERROR/dalvikvm-heap: 6291456-byte external allocation too large for this process.
ERROR/AndroidRuntime: java.lang.OutOfMemoryError: bitmap size exceeds VM budget

Additional error when displaying images:

DEBUG/skia: xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
INFO/System.out: resolveUri failed on bad bitmap uri: 
ERROR/dalvikvm-heap: 6291456-byte external allocation too large for this process.
DEBUG/skia: xxxxxxxxxxxxxxxxxxxx allocPixelRef failed

How can I properly resize images to avoid OutOfMemoryError while maintaining image quality in my Android ListView?

NeuroAgent

To resolve the OutOfMemoryError when loading images into Bitmap in Android ListView, you need to implement proper bitmap scaling techniques and memory management strategies. The key solution involves using BitmapFactory.Options with inSampleSize parameter to resize images on-the-fly before loading them into memory, combined with recycling bitmaps and implementing caching mechanisms to prevent memory leaks.

Contents

Understanding the OutOfMemoryError Issue

The OutOfMemoryError: bitmap size exceeds VM budget occurs when Android’s Dalvik Virtual Machine cannot allocate sufficient memory to load large bitmaps. This is particularly problematic in ListView scenarios where multiple images need to be loaded simultaneously.

As mentioned in the research findings, the error occurs because “the image preview on the ListView is being done with the cursor and ListAdapter,” which can lead to accumulating large bitmap objects in memory. The specific error message 6291456-byte external allocation too large for this process indicates that a 6MB bitmap is being requested, which exceeds the available memory budget.

The root cause is that high-resolution images from phone cameras can consume significant memory. For example, a 12MP image at full resolution could require 36MB of memory (3 bytes per pixel × 12 million pixels). When multiple such images are loaded into ListView rows, the memory quickly exceeds the available VM budget.


On-the-Fly Image Resizing Solutions

Using BitmapFactory.Options with inSampleSize

The most effective solution for on-the-fly resizing involves using BitmapFactory.Options with the inSampleSize parameter. This allows you to decode the image at a smaller resolution before loading it into memory.

java
public static Bitmap decodeSampledBitmapFromPath(String path, int reqWidth, int reqHeight) {
    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(path, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeFile(path, options);
}

public static 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;
}

This approach directly addresses your first question about building the list adapter row by row with on-the-fly bitwise resizing. You can implement this in your SimpleCursorAdapter by overriding getView() method.

Purgeable Bitmaps

Another approach is to use the inPurgeable option, which allows the system to reclaim memory from the bitmap if needed:

java
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
options.inInputShareable = true;
Bitmap bitmap = BitmapFactory.decodeFile(path, options);

However, according to the research findings, this method may not always be reliable as noted in the Stack Overflow discussion about “Improper call to JPEG library in state” errors.


Memory Management and Recycling

Proper Bitmap Recycling

As mentioned in the research findings, a common solution is to “free the allocated memory on leaving the activity” by calling the recycle() method for each displayed bitmap. This is crucial for preventing memory leaks:

java
@Override
protected void onDestroy() {
    super.onDestroy();
    // Recycle bitmaps to free memory
    for (int i = 0; i < notes.getCount(); i++) {
        View view = notes.getView(i, null, null);
        ImageView imageView = view.findViewById(R.id.imagefilename);
        BitmapDrawable drawable = (BitmapDrawable) imageView.getDrawable();
        if (drawable != null) {
            drawable.getBitmap().recycle();
        }
    }
}

Implementing onLowMemory Callback

The research findings also suggest implementing the onLowMemory() callback to proactively clear memory when the system is running low:

java
@Override
public void onLowMemory() {
    super.onLowMemory();
    System.gc(); // clear bitmap and other objects here to reduce memory
}

Alternative Approaches and Libraries

Using Picasso Library

The research findings recommend using the Picasso library from Square as an effective solution. Picasso handles memory management, caching, and resizing automatically:

java
Picasso.with(context)
       .load(url)
       .resize(50, 50)
       .centerCrop()
       .into(imageView);

This approach significantly reduces memory usage by resizing images before loading them into memory. The library also implements efficient caching strategies to prevent reloading the same images.

External Storage Caching

Another alternative mentioned in the research is to “cache the loaded bitmaps in hard storage such as external SD card, and reload them on the fly when needed, instead of attempting to hold everything in RAM.” This approach involves:

  1. Resizing images to appropriate dimensions
  2. Saving them to external storage
  3. Loading the pre-resized images when needed

This method is particularly useful if you have limited RAM but sufficient storage space.


Implementation Examples

Custom SimpleCursorAdapter with On-the-Fly Resizing

To address your specific need for row-by-row adapter building with on-the-fly resizing, here’s a complete implementation:

java
public class CustomCursorAdapter extends SimpleCursorAdapter {
    private Context context;
    private int layout;
    private Cursor cursor;
    
    public CustomCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
        super(context, layout, c, from, to);
        this.context = context;
        this.layout = layout;
        this.cursor = c;
    }
    
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = super.getView(position, convertView, parent);
        ImageView imageView = view.findViewById(R.id.imagefilename);
        
        if (cursor.moveToPosition(position)) {
            String imagePath = cursor.getString(cursor.getColumnIndex(DBHelper.KEY_IMAGEFILENAME));
            if (imagePath != null && !imagePath.isEmpty()) {
                // Resize image on-the-fly
                Bitmap resizedBitmap = decodeSampledBitmapFromPath(
                    imagePath, 
                    100, // desired width
                    100  // desired height
                );
                imageView.setImageBitmap(resizedBitmap);
            }
        }
        
        return view;
    }
}

Out-of-Band Resizing and Saving

For the alternative approach you mentioned, here’s how to implement out-of-band resizing and saving:

java
public void resizeAndSaveImage(String inputPath, String outputPath, int width, int height) {
    Bitmap resizedBitmap = decodeSampledBitmapFromPath(inputPath, width, height);
    
    try {
        FileOutputStream out = new FileOutputStream(outputPath);
        resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 85, out);
        out.flush();
        out.close();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        resizedBitmap.recycle();
    }
}

This approach processes images once and saves them in a resized format, eliminating the need for on-the-fly resizing during list loading.


Best Practices for ListView Image Loading

Implementing ViewHolder Pattern

For better performance in ListView, implement the ViewHolder pattern to avoid repeated view lookups:

java
static class ViewHolder {
    ImageView imageView;
    // other views
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    
    if (convertView == null) {
        convertView = LayoutInflater.from(context).inflate(layout, parent, false);
        holder = new ViewHolder();
        holder.imageView = convertView.findViewById(R.id.imagefilename);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }
    
    // Load image into holder.imageView
    // ...
    
    return convertView;
}

LruCache for Memory Caching

Implement an LruCache to cache recently used bitmaps in memory:

java
private LruCache<String, Bitmap> memoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    // ...
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    final int cacheSize = maxMemory / 8; // Use 1/8 of available memory
    
    memoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            return bitmap.getByteCount() / 1024;
        }
    };
}

Lazy Loading with AsyncTask

For better user experience, implement lazy loading using AsyncTask or similar background loading mechanisms:

java
class LoadImageTask extends AsyncTask<String, Void, Bitmap> {
    private final ImageView imageView;
    
    LoadImageTask(ImageView imageView) {
        this.imageView = imageView;
    }
    
    @Override
    protected Bitmap doInBackground(String... params) {
        String path = params[0];
        return decodeSampledBitmapFromPath(path, 100, 100);
    }
    
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
        }
    }
}

By implementing these solutions, you can effectively resolve the OutOfMemoryError while maintaining good image quality in your Android ListView application. The key is to balance image quality with memory constraints by implementing proper scaling, caching, and memory management strategies.

Sources

  1. Android Training - Displaying Bitmaps Efficiently - Official Android documentation on bitmap loading and memory management
  2. Strange OutOfMemory issue while loading an image to a Bitmap object - Stack Overflow - Comprehensive discussion about bitmap memory issues and solutions
  3. Android Bitmap OutOfMemoryError in ListView - Stack Overflow - Specific solution for ListView bitmap memory management
  4. How to avoid OutOfMemoryError - Stack Overflow - Picasso library usage and memory optimization techniques
  5. Out of memory error: vast bitmap - Stack Overflow - External storage caching approach

Conclusion

Resolving OutOfMemoryError in Android ListView image loading requires a multi-faceted approach combining proper on-the-fly image resizing, efficient memory management, and strategic caching. The key takeaways are:

  1. Implement BitmapFactory.Options with inSampleSize for on-the-fly resizing before loading bitmaps into memory
  2. Use libraries like Picasso to handle automatic resizing, caching, and memory management
  3. Properly recycle bitmaps when they’re no longer needed to prevent memory leaks
  4. Consider caching resized images externally if memory constraints are severe
  5. Apply best practices like ViewHolder pattern and lazy loading for optimal performance

By following these strategies, you can maintain good image quality while avoiding memory crashes in your ListView implementation. Start with the on-the-fly resizing solution as it directly addresses your need for row-by-row adapter building with image modification capabilities, and consider implementing external caching if you continue to experience memory issues.