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.
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.
*
*
Must be called on the main thread.
*
*
The flow for any request is as follows:
*
*
Check the current set of actively used resources, return the active resource if present, and move any newly inactive resources into the memory cache.
*
Check the memory cache and provide the cached resource if present.
*
Check the current set of in‑progress loads and add the callback to the in‑progress load if one is present.
*
Start a new load.
*
*/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
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
implements Resource
{
private final boolean isCacheable;
private final boolean isRecyclable;
private Resource
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
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
encoder = helper.getSourceEncoder(dataToCache);
DataCacheWriter
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
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.
vivo Internet Technology
Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.
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.