Android应用加载位图引发OOM及内存泄漏问题求助
Hey there, let's break down why you're hitting this memory problem and how to fix it without relying on Glide or other image libraries.
First, the Root Cause of Your Problem
Your custom PhotoCache uses a static HashMap with strong references to Bitmap objects. Static references live as long as your app process does—so every bitmap you add to the cache stays in memory forever, even after the Activity that used it is destroyed. Switching between Activities 30 times just piles up more and more bitmaps until you hit the OutOfMemory exception.
On top of that, recycling bitmaps in onPause is too early: when you press back, the previous Activity is resumed, but its ImageViews are still holding references to those already-recycled bitmaps, which causes the crash.
Solutions to Try
1. Replace Strong References with Weak References in Your Cache
Change your cache to use WeakReference<Bitmap> instead of direct Bitmap references. Weak references let the garbage collector (GC) reclaim the bitmap memory when there are no other active strong references to it (like when the Activity using it is destroyed).
Here's the updated PhotoCache class:
public class PhotoCache { private static Map<String, WeakReference<Bitmap>> photos = new HashMap<>(); public static void addPhoto(String key, Bitmap val) { photos.put(key, new WeakReference<>(val)); } public static Bitmap getPhoto(String key) { WeakReference<Bitmap> ref = photos.get(key); return ref != null ? ref.get() : null; } public static boolean containsPhoto(String key) { WeakReference<Bitmap> ref = photos.get(key); return ref != null && ref.get() != null; } }
Now, when an Activity is destroyed and no longer references a bitmap, the GC can clean it up automatically.
2. Bind Bitmap Loading/Recycling to the Right Activity Lifecycle Methods
Instead of recycling in onPause, do it in onDestroy—this way, you only recycle bitmaps when the Activity is definitely going to be destroyed (not just paused). Then, in onResume, reload any bitmaps your ImageViews need:
@Override protected void onResume() { super.onResume(); // Reload your grid's bitmaps here using PhotoCache loadGridBitmaps(); } @Override protected void onDestroy() { super.onDestroy(); // Recycle bitmaps that are only used by this Activity for (ImageView imageView : yourGridImageViews) { Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap(); if (bitmap != null && !bitmap.isRecycled()) { bitmap.recycle(); } } // Optional: Remove this Activity's bitmaps from the cache if needed for (String key : activitySpecificBitmapKeys) { PhotoCache.photos.remove(key); } }
This way, when you press back to the previous Activity, onResume will reload the bitmaps, so ImageViews have valid, non-recycled bitmaps to display.
3. Add a Size Limit to Your Cache (LRU Strategy)
Even with weak references, you might want to prevent the cache from growing too large. Implement a simple LRU (Least Recently Used) cache using LinkedHashMap, which automatically removes the oldest entries when the cache hits a size limit:
public class PhotoCache { private static final int MAX_CACHE_SIZE = 20; // Adjust based on your needs private static Map<String, WeakReference<Bitmap>> photos = new LinkedHashMap<>(MAX_CACHE_SIZE, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry<String, WeakReference<Bitmap>> eldest) { return size() > MAX_CACHE_SIZE; } }; // ... keep the addPhoto, getPhoto, containsPhoto methods from solution 1 ... }
The true parameter in LinkedHashMap makes it track access order, so the least recently used entries get removed first when the cache is full.
4. Optimize Bitmap Loading Further
You're already using inSampleSize, but add these tweaks to save more memory:
- Use
Bitmap.Config.RGB_565instead of the defaultARGB_8888if your images don't need transparency. This cuts memory usage in half (2 bytes per pixel vs 4). - Always check if the bitmap is already recycled before using it, to avoid crashes.
Example of optimized bitmap loading:
public Bitmap loadBitmapFromFile(String filePath, int targetWidth, int targetHeight) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(filePath, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, targetWidth, targetHeight); // Set preferred config options.inPreferredConfig = Bitmap.Config.RGB_565; options.inJustDecodeBounds = false; return BitmapFactory.decodeFile(filePath, options); } private 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; }
Why These Fixes Work
- Weak references let GC clean up unused bitmaps without you having to manually track every reference.
- Binding lifecycle actions to
onResume/onDestroyensures bitmaps are only reloaded when needed and recycled when the Activity is gone for good. - LRU caching prevents the cache from becoming a memory hog even if weak references take time to be collected.
- Optimized bitmap loading reduces the memory footprint of each individual bitmap.
内容的提问来源于stack exchange,提问作者pumbosha

