Mobile Development 20 min read

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.

iQIYI Technical Product Team
iQIYI Technical Product Team
iQIYI Technical Product Team
Efficient Bitmap Reuse in Android Using InBitmap and Reference Counting

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

inSampleSize

must 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.

memory managementAndroidbitmapReference CountingImage ReuseInBitmap
iQIYI Technical Product Team
Written by

iQIYI Technical Product Team

The technical product team of iQIYI

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.