Personal blog portal
Structure of the overview
- The memory cache is composed of LruResourceCache and activeResources. The cache is of the EngineResource type
LruResourceCache is a LinkedHashMap that implements Lru, storing cache resources that are not used by the interface, and Lru controls the size of the cache: ActiveResources is a Map
Search time: a Key(determined by 10 + parameters) is generated by Engine. Load for cache matching, where key. getOriginalKey is called when the raw unprocessed resource is retrieved to produce a Key with fewer parameters that matches the raw unprocessed resource
Do you rememberAndroid Glide 3.7.0 source parsing (two), from a picture loading process to see the source codeMentioned in the article
- The initialization of the cache occurs when the Glide singleton is created
- Memory cache matching is done in the engine.load method with a Key passed in, LruResourceCache(level 1) and activeResources(level 2)
The difference between LruResourceCache(level 1) and activeResources(level 2) :
- LruResourceCache(level 1) stores resource instances that are not used by the interface
- ActiveResources (level 2) stores resource instances that are being used by interface components (multiple interface components can use the same active cache)
ActiveResources (Level 2) exists for:
- Different from LruResourceCache(level 1) using Lru algorithm strategy is that the cache has no capacity limit, internal reference count is used to determine whether it is being used by the outside world, when the reference is 0 will be removed from the cache, added to the memory cache, the role of the cache is to protect the resources in use and reuse. Imagine if there were no LruResourceCache and only LruResourceCache it would be possible to remove images in use when the LruResourceCache cache reached its maximum capacity. Glide will not find the resource object in memory when the same image resource needs to be displayed at the same time in another part of the application and will rebuild a new one.
4. A change in behavior
- Glide 4.x.x version, matches first from activeResources(level 2) and then from LruResourceCache(level 1)
- DiskLruCacheWrapper(data level 3) is used to match the disk cache before enginerFunctionable. run decode. Engine. Load cache is stored in disk with data that has not been trancoded or graphed. The non-main thread (transcoding/graphics conversion time) opened by EnginerFunction.run needs to use EngineJob’s decode() method to transcode/graphics conversion of cached data, while the memory cache does not have this problem. The in-memory cache caches the data after processing (transcoding/graphics conversion), which is the EngineResource
- The disk cache is stored in the EnginerFunction. run method after EngineJob is decode. When parsing SourceData, which is the raw image data, The cacheAndDecodeSourceData() method joins the DiskLruCacheWrapper(Source level 4) cache, In the writeTransformedToCache() method, the transformed graph data (non-raw) is added to the DiskLruCacheWrapper(data level 3) cache
- Memory cache into In the Engine. The cache onEngineJobComplete method to activeResources (2) are used by interface, in EngineResource release, Determines if the reference count reaches 0 (activeResources(level 2)), and if it does not, tries to put it into LruResourceCache(level 1)
The above is a flow chart of cache usage to explain
- Engine.load checks the LruResourceCache(level 1) and activeResources(level 2) caches
- Switch the thread to the place where the download is performed and start checking the DiskLruCacheWrapper(data level 3) and DiskLruCacheWrapper(Source level 4) caches
- After downloading, save to DiskLruCacheWrapper(Source level 4), save to DiskLruCacheWrapper(data level 3).
- Save to activeResources(level 2) when calling back
- When the interface is released, call Request’s (real) clear function to see if activeResources(level 2) has a cache with a count of 0 and save it to LruResourceCache(level 1)
Request(real) behavior when interface is released is mentioned in this article
The framework logic has been conceptually, then read the source code will not get lost
The cache created
LruResourceCache(level 1) is created when Glide singleton is initialized. Glidebuild.createglide ()
// GlideBuilder
Glide createGlide(a) {...if (memoryCache == null) {
// An instance of LruResourceCache was created
memoryCache = newLruResourceCache(calculator.getMemoryCacheSize()); }... }Copy the code
The activeResources(level 2) cache is created, also when glideBuilder.createglide () is initialized with the Glide singleton, creating an Engine instance, And initializes the activeResources weak reference array in the constructor of this instance
ActiveResources is a Map<Key, WeakReference<EngineResource<? The >>> Type Engine instance is a member variable of the Glide singleton, that is, it is also globally unique
// GlideBuilder
Glide createGlide(a) {...if (engine == null) {
engine = newEngine(memoryCache, diskCacheFactory, diskCacheService, sourceService); }... }// Engine
public Engine(MemoryCache memoryCache, DiskCache.Factory diskCacheFactory, ExecutorService diskCacheService, ExecutorService sourceService) {
this(memoryCache, diskCacheFactory, diskCacheService, sourceService, null.null.null.null.null); } Engine(MemoryCache cache, DiskCache.Factory diskCacheFactory, ExecutorService diskCacheService, ExecutorService sourceService, Map<Key, EngineJob> jobs, EngineKeyFactory keyFactory, Map<Key, WeakReference<EngineResource<? >>> activeResources, EngineJobFactory engineJobFactory, ResourceRecycler resourceRecycler) { ...if (activeResources == null) {
// Finally create a HashMap
activeResources = newHashMap<Key, WeakReference<EngineResource<? > > > (); }this.activeResources = activeResources; . }Copy the code
DiskLruCacheWrapper(Data level 3) and DiskLruCacheWrapper(Source level 4) caches are created
// GlideBuilder
Glide createGlide(a) {...if (diskCacheFactory == null) {
diskCacheFactory = newInternalCacheDiskCacheFactory(context); }... }// InternalCacheDiskCacheFactory
public final class InternalCacheDiskCacheFactory extends DiskLruCacheFactory {}
// DiskLruCacheFactory
@Override
public DiskCache build(a) {...return DiskLruCacheWrapper.get(cacheDir, diskCacheSize);
}
// DiskLruCacheWrapper
public static synchronized DiskCache get(File directory, int maxSize) {...if (wrapper == null) {
// Finally a DiskLruCacheWrapper instance is created
wrapper = new DiskLruCacheWrapper(directory, maxSize);
}
return wrapper;
}
Copy the code
Use of cache
Follow the framework flow chart above to look directly at Engine.Load
// Engine
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
final String id = fetcher.getId();
// You can see that a Key value is packaged here, which uniquely identifies a cache instance in memory
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
// Start with LruResourceCacheEngineResource<? > cached = loadFromCache(key, isMemoryCacheable);if(cached ! =null) {
// The data in the match is directly called back up
cb.onResourceReady(cached);
return null;
}
// Search from activeResources againEngineResource<? > active = loadFromActiveResources(key, isMemoryCacheable);if(active ! =null) {
cb.onResourceReady(active);
return null; }...// If no match is found, perform the download. engineJob.start(runnable); . }privateEngineResource<? > loadFromCache(Key key,boolean isMemoryCacheable) {
if(! isMemoryCacheable) {return null;
}
// Try to get/remove from LruResourceCache(level 1)EngineResource<? > cached = getEngineResourceFromCache(key);if(cached ! =null) {
cached.acquire();
// Successful, add to Activity Resources(level 2)
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
}
return cached;
}
privateEngineResource<? > loadFromActiveResources(Key key,boolean isMemoryCacheable) {
if(! isMemoryCacheable) {return null; } EngineResource<? > active =null;
// try to match from activeResources(level 2)WeakReference<EngineResource<? >> activeRef = activeResources.get(key);if(activeRef ! =null) {
active = activeRef.get();
if(active ! =null) {
// If it matches, the reference count is increased, indicating how many elements on the interface are using it
active.acquire();
} else {
// If the match is empty, it may be freed. Remove the weak reference to the freed resourceactiveResources.remove(key); }}return active;
}
Copy the code
- Match from LruResourceCache(level 1), and then from activeResources(level 2)
- If not, download it
Follow along to see the download process
// EngineRunnable
public void run(a) {...// Perform the downloadresource = decode(); .// Download successful callbackonLoadComplete(resource); . }privateResource<? > decode()throws Exception {
if (isDecodingFromCache()) {
// First match [level 3/4 cache]
return decodeFromCache();
} else {
// If you can't match it, download it
returndecodeFromSource(); }}privateResource<? > decodeFromCache()throwsException { Resource<? > result =null;
try {
// match DiskLruCacheWrapper(data level 3)
result = decodeJob.decodeResultFromCache();
} catch (Exception e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Exception decoding result from cache: "+ e); }}if (result == null) {
DiskLruCacheWrapper(Source 四级)
result = decodeJob.decodeSourceFromCache();
}
return result;
}
Copy the code
- See DiskLruCacheWrapper(data level 3) and DiskLruCacheWrapper(Source level 4) start matching after thread switch
No match, what do you do after downloading?
// EngineRunnable
private void onLoadComplete(Resource resource) {
manager.onResourceReady(resource);
}
// EngineJob
public void onResourceReady(finalResource<? > resource) {
this.resource = resource;
MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}
private static class MainThreadCallback implements Handler.Callback {
@Override
public boolean handleMessage(Message message) {
if (MSG_COMPLETE == message.what || MSG_EXCEPTION == message.what) {
EngineJob job = (EngineJob) message.obj;
if (MSG_COMPLETE == message.what) {
// Start processing the logic of loading the image data into the interface
job.handleResultOnMainThread();
} else {
job.handleExceptionOnMainThread();
}
return true;
}
return false; }}private void handleResultOnMainThread(a) {...// Listener is an instance of Engine
listener.onEngineJobComplete(key, engineResource);
// cb is the GenericRequest instance passed in engine.load
for (ResourceCallback cb : cbs) {
if(! isInIgnoredCallbacks(cb)) { engineResource.acquire(); cb.onResourceReady(engineResource); }}... }// Engine
public void onEngineJobComplete(Key key, EngineResource
resource) {...// Add resources here to Activity Resources(level 2)
activeResources.put(key, newResourceWeakReference(key, resource, getReferenceQueue())); . }Copy the code
It can be seen that in the process of casting image data to the interface, it is cached to activeResources(level 2) and finally to see when it is cached to LruResourceCache(level 1). As we know from the lifecycle binding article, GenericRequest’s clear method is triggered when the interface is released
// GenericRequest
public void clear(a) {
Util.assertMainThread();
if (status == Status.CLEARED) {
return;
}
cancel();
// Resource must be released before canNotifyStatusChanged is called.
if(resource ! =null) {
// The point here is that you need to release the [activeResources(level 2)] that the interface is using
releaseResource(resource);
}
if (canNotifyStatusChanged()) {
target.onLoadCleared(getPlaceholderDrawable());
}
// Must be after cancel().
status = Status.CLEARED;
}
private void releaseResource(Resource resource) {
// Go to the Engine instance
engine.release(resource);
this.resource = null;
}
// Engine
public void release(Resource resource) {... ((EngineResource) resource).release(); . }// EngineResource
void release(a) {
if (acquired <= 0) {
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
}
if(! Looper.getMainLooper().equals(Looper.myLooper())) {throw new IllegalThreadStateException("Must call release on the main thread");
}
if (--acquired == 0) {
// Reduce the reference count and trigger a listener when it reaches 0 (in this case, an Engine instance)
listener.onResourceReleased(key, this); }}// Engine
public void onResourceReleased(Key cacheKey, EngineResource resource) {
Util.assertMainThread();
// remove from activeResources(level 2)
activeResources.remove(cacheKey);
if (resource.isCacheable()) {
// Store to DiskLruCacheWrapper(data level 3)
cache.put(cacheKey, resource);
} else{ resourceRecycler.recycle(resource); }}Copy the code
When the interface releases a resource, if the resource reference count for activeResources(level 2) returns to zero, remove the resource from activeResources(level 2) and try to place the removed resource to LruResourceCache(level 1). Source code analysis completed!