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:
-
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). -
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:
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?
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
- On-the-Fly Image Resizing Solutions
- Memory Management and Recycling
- Alternative Approaches and Libraries
- Implementation Examples
- Best Practices for ListView Image Loading
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.
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:
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:
@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:
@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:
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:
- Resizing images to appropriate dimensions
- Saving them to external storage
- 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:
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:
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:
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:
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:
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
- Android Training - Displaying Bitmaps Efficiently - Official Android documentation on bitmap loading and memory management
- Strange OutOfMemory issue while loading an image to a Bitmap object - Stack Overflow - Comprehensive discussion about bitmap memory issues and solutions
- Android Bitmap OutOfMemoryError in ListView - Stack Overflow - Specific solution for ListView bitmap memory management
- How to avoid OutOfMemoryError - Stack Overflow - Picasso library usage and memory optimization techniques
- 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:
- Implement BitmapFactory.Options with inSampleSize for on-the-fly resizing before loading bitmaps into memory
- Use libraries like Picasso to handle automatic resizing, caching, and memory management
- Properly recycle bitmaps when they’re no longer needed to prevent memory leaks
- Consider caching resized images externally if memory constraints are severe
- 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.