Efficient Bitmap Reuse in Android Using InBitmap and Reference Counting
The article presents a high‑efficiency Android bitmap‑reuse strategy that combines the inBitmap API with atomic reference counting and a three‑layer LRU cache (active, LruCache, InBitmapPool), dramatically lowering garbage‑collection pauses and achieving over 80 % reuse on pre‑Android 8 devices.
Android apps run on the Java Virtual Machine, which performs automatic garbage collection (GC). Frequent creation of large Bitmap objects can trigger GC pauses, especially on Android 3.0‑7.1 where Bitmap pixel data resides in the Java heap.
From Android 8.0 onward, Bitmap data is stored in native memory, so creating Bitmaps no longer impacts GC. However, on older versions a 500×500 ARGB8888 image occupies about 1 MB, and repeated creation/destruction of such Bitmaps can cause noticeable stutter.
Android provides the inBitmap mechanism to reuse Bitmaps, but the platform does not supply a complete solution for collecting unused Bitmaps or managing them efficiently.
This article proposes a high‑efficiency image reuse scheme that requires only a modest memory cache yet achieves a high reuse rate, thereby reducing GC pressure.
InBitmap Reuse Principle
When a Bitmap is no longer needed, it is cached. When a new Bitmap is required, instead of allocating fresh memory, a suitable cached Bitmap is retrieved, its pixel data is overwritten, and the resulting Bitmap is handed to the caller.
Below is a typical usage of the BitmapFactory.Options API for inBitmap :
public Bitmap decodeBitmap(byte[] data, Bitmap.Config config, int targetWidth, int targetHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
// Get image dimensions without allocating memory
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, options);
// Compute sampling rate
options.inSampleSize = calculateInSampleSize(options, targetWidth, targetHeight);
options.inJustDecodeBounds = false;
options.inMutable = true; // Allow bitmap modification
options.inPreferredConfig = config;
// Retrieve a reusable bitmap that matches the requirements
options.inBitmap = getReusableBitmap(options);
return BitmapFactory.decodeByteArray(data, 0, data.length, options);
}Version‑Specific Constraints
Android 3.0‑4.3
Bitmap must be JPG/PNG, width/height/ARGB must match exactly, and
inSampleSizemust be 1.
Android 4.4‑7.1
The byte‑count of the bitmap to be decoded must be smaller than the byte‑count of the reusable bitmap.
These stricter requirements on older versions lead to low hit rates, while the relaxed constraints on 4.4‑7.1 improve reuse effectiveness.
Collecting Unused Bitmaps
To reuse Bitmaps safely, the system must know when a Bitmap is truly no longer referenced. Because the same Bitmap can be cached and used by multiple views, a simple flag is insufficient. A reference‑counting approach is introduced:
Each time a component acquires a Bitmap, the count is incremented.
When the component releases the Bitmap, the count is decremented.
When the count reaches zero, the Bitmap is eligible for reuse.
The following class wraps a Bitmap with an atomic reference counter:
public class Resource {
// Atomic reference count (may be modified by multiple threads)
private final AtomicInteger acquired = new AtomicInteger(0);
// Unique key for the memory cache
private final String memoryCacheKey;
// The actual Bitmap
private final Bitmap bitmap;
// Increment reference count when a client acquires the resource
public void acquire() {
this.acquired.incrementAndGet();
}
// Decrement reference count when a client releases the resource
public void release() {
this.acquired.decrementAndGet();
}
}Hiding Reference Counting from the Client
The image loading library should encapsulate the acquire/release logic so that developers only call a simple load API. For example:
private ImageView imageView;
public void loadImage() {
String url1 = "http://image1.jpg";
ImageRequest request = new ImageRequest(url1);
ImageProviderApi.get().load(request).into(imageView);
// After 1 second load a second image; the first one will be released automatically
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
String url2 = "http://image2.jpg";
ImageRequest request = new ImageRequest(url2);
ImageProviderApi.get().load(request).into(imageView);
}
}, 1000);
}When the second image is loaded, the library decrements the reference count of the first Bitmap, making it reusable.
Memory Cache Architecture
To support efficient reuse, the cache is divided into three layers:
ActiveResource : a WeakReference<Resource> map holding Bitmaps currently in use.
LruCache : stores recently released Resources that may be reused soon.
InBitmapPool : holds Resources that have been evicted from LruCache and are available for in‑bitmap reuse.
Both LruCache and InBitmapPool organize Bitmaps into groups based on reuse criteria (size or byte‑count) and maintain an LRU linked list for each group.
InBitmapPool Implementations
For Android 3.0‑4.3 the pool groups Bitmaps by exact width‑height‑ARGB triple. A Map<SizeKey, Group> provides O(1) lookup, while a doubly‑linked LRU list orders groups by recent access.
For Android 4.4‑7.1 the pool groups Bitmaps by byte‑count using a TreeMap<Integer, Group> . The ceilingKey operation finds the smallest group whose size is ≥ the required size.
Both implementations share the same eviction strategy: when the pool exceeds its memory budget, the least‑recently‑used group (tail of the LRU list) is trimmed, and empty groups are removed from both the list and the lookup map.
Performance Impact
In real projects, treating InBitmapPool as an additional memory cache can significantly enlarge the effective cache size. On Android 4.4‑7.1 the reuse hit rate in list‑view scenarios can exceed 80 %, and overall GC frequency can be reduced by roughly 60 %.
Summary : By collecting unused Bitmaps with reference counting, encapsulating the counting logic inside the image‑loading library, and organizing reusable Bitmaps in an LRU‑based pool (grouped by size or byte‑count), developers can achieve high‑efficiency image reuse, lower GC pressure, and smoother UI performance on Android devices.
iQIYI Technical Product Team
The technical product team of iQIYI
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.