Mobile Development 21 min read

Analysis of Glide Image Loading Cache Mechanisms on Android

The article dissects Glide’s five‑level caching system—active resources, memory cache, resource and data disk caches, and network cache—explaining how active weak references feed the LRU memory cache, how DiskCacheStrategy governs transformed and raw data storage, and how I/O and network tasks run on separate executors while network responses are cached before delivery.

vivo Internet Technology
vivo Internet Technology
vivo Internet Technology
Analysis of Glide Image Loading Cache Mechanisms on Android

Glide is the officially recommended image loading library on Android. It provides a simple API and a flexible architecture that allows replacement of the network layer and includes a comprehensive caching system consisting of active resources, memory cache, resource disk cache, data disk cache and network cache.

The article focuses on analyzing the cache mechanism rather than basic usage.

Overview

Before diving in, the following questions are posed:

How many cache levels does Glide have?

What is the relationship between Glide's memory caches?

Are local file I/O and network requests executed on the same thread?

Does Glide store the network response before delivering it to the caller?

The loading process starts at Engine.load(). The method first checks Active Resources, then Memory Cache, then any ongoing job, and finally creates a new job if needed.

/**
 * Starts a load for the given arguments.
 *
 * <p>Must be called on the main thread.
 *
 * <p>The flow for any request is as follows:
 * <ul>
 *   <li>Check the current set of actively used resources, return the active resource if present, and move any newly inactive resources into the memory cache.</li>
 *   <li>Check the memory cache and provide the cached resource if present.</li>
 *   <li>Check the current set of in‑progress loads and add the callback to the in‑progress load if one is present.</li>
 *   <li>Start a new load.</li>
 * </ul>
 */

1. Memory Cache

The code path labeled “focus 1” loads from ActiveResources. If a resource is found, it is returned immediately; otherwise the lookup proceeds to the memory cache.

private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
        return null;
    }
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
        active.acquire();
    }
    return active;
}
ActiveResources

stores resources with weak references and moves them to the memory cache when they become inactive.

final class ActiveResources {
    private final Handler mainHandler = new Handler(Looper.getMainLooper(), new Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == MSG_CLEAN_REF) {
                cleanupActiveReference((ResourceWeakReference) msg.obj);
                return true;
            }
            return false;
        }
    });
    final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
}

If the memory cache contains the requested resource, EngineResource is returned and its reference count is increased.

private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
        return null;
    }
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
        cached.acquire();
        activeResources.activate(key, cached);
    }
    return cached;
}

The default implementation of MemoryCache is LruResourceCache.

if (memoryCache == null) {
    memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
EngineResource

adds reference‑counting to a wrapped Resource and handles recycling when the count reaches zero.

class EngineResource<Z> implements Resource<Z> {
    private final boolean isCacheable;
    private final boolean isRecyclable;
    private Resource<Z> resource;
    private int acquired;
    private boolean isRecycled;

    void acquire() {
        if (isRecycled) {
            throw new IllegalStateException("Cannot acquire a recycled resource");
        }
        ++acquired;
    }

    void release() {
        if (acquired <= 0) {
            throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
        }
        if (--acquired == 0) {
            listener.onResourceReleased(key, this);
        }
    }

    @Override
    public void recycle() {
        if (acquired > 0) {
            throw new IllegalStateException("Cannot recycle a resource while it is still acquired");
        }
        if (isRecycled) {
            throw new IllegalStateException("Cannot recycle a resource that has already been recycled");
        }
        isRecycled = true;
        if (isRecyclable) {
            resource.recycle();
        }
    }
}

When release() is called and the reference count drops to zero, the resource is moved back to the memory cache or recycled.

@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    Util.assertMainThread();
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
        cache.put(cacheKey, resource);
    } else {
        resourceRecycler.recycle(resource);
    }
}

2. Disk Cache

Glide defines several DiskCacheStrategy options (ALL, NONE, DATA, RESOURCE, AUTOMATIC). The default AUTOMATIC caches transformed resources.

public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {
    @Override
    public boolean isDataCacheable(DataSource dataSource) {
        return dataSource == DataSource.REMOTE;
    }

    @Override
    public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,
                                      EncodeStrategy encodeStrategy) {
        return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)
                || dataSource == DataSource.LOCAL) && encodeStrategy == EncodeStrategy.TRANSFORMED;
    }

    @Override public boolean decodeCachedResource() { return true; }
    @Override public boolean decodeCachedData() { return true; }
};

The DecodeJob determines the next stage based on the strategy. Stages are defined as:

enum Stage {
    INITIALIZE,
    RESOURCE_CACHE,
    DATA_CACHE,
    SOURCE,
    ENCODE,
    FINISHED
}

During the RESOURCE_CACHE stage, ResourceCacheGenerator attempts to load a cached transformed resource. If it fails, the job proceeds to DATA_CACHE, then to SOURCE where a network request is performed.

Network requests are handled by HttpUrlFetcher. After a successful download, the data may be written to the data disk cache before being decoded.

@Override
public void loadData(@NonNull Priority priority,
                     @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
        InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
        callback.onDataReady(result);
    } catch (IOException e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Failed to load data for url", e);
        }
        callback.onLoadFailed(e);
    } finally {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
        }
    }
}

When the fetched data is cacheable, SourceGenerator stores it via DataCacheWriter and then triggers a second pass that reads the data from the cache.

private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
        Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
        DataCacheWriter<Object> writer = new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
        originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
        helper.getDiskCache().put(originalKey, writer);
    } finally {
        loadData.fetcher.cleanup();
    }
    sourceCacheGenerator = new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}

After decoding, notifyComplete() delivers the final Resource to the caller.

private void notifyComplete(Resource<R> resource, DataSource dataSource) {
    setNotifiedOrThrow();
    callback.onResourceReady(resource, dataSource);
}

Summary of Cache Levels

Active Resources (in‑use weak references)

Memory Cache (LRU cache)

Resource Disk Cache (cached transformed resources)

Data Disk Cache (raw source data)

Network Cache (handled by the HTTP client)

The article answers the initial questions, confirming that Glide employs five cache levels, explains the relationship between memory caches, clarifies that I/O and network operations run on different executors, and shows that network responses are cached before being returned to the user.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

CacheAndroidNetworkingGlideImage LoadingDisk CacheMemory Cache
vivo Internet Technology
Written by

vivo Internet Technology

Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.

0 followers
Reader feedback

How this landed with the community

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.