1. Use Gilde to display an image
(this). load("/sa/simg/hpb/") .into(imageView);
The above is the simplest way to display a picture of Glide. Although there are only three steps: with, load, and into, gilde is maintained through a large amount of code.
with: Return a RequestManager
load: Return a RequestBuilder
The following is analyzing these three parts through source code, focusing on the management and cache use of Glide's biotechnology cycle, as well as the sorting out the entire loading process.
with operation source code analysis
Glide's with function has multiple overloads due to parameter type. The following is to select the activity to pass it as the parameter.
public static RequestManager with(@NonNull Activity activity) { return getRetriever(activity).get(activity); }
This method is to get a RequestManager
Analyze getRetriever
private static RequestManagerRetriever getRetriever(@Nullable Context context) { ( context, "You cannot start a load on a not yet attached View or a Fragment where getActivity() " + "returns null (which usually occurs when getActivity() is called before the Fragment " + "is attached or after the Fragment is destroyed)."); return (context).getRequestManagerRetriever(); }
Get a RequestManagerRetriever, this manages RequestManager
RequestManagerRetriever get method
public RequestManager get(@NonNull Context context) { if (context == null) { throw new IllegalArgumentException("You cannot start a load on a null Context"); } else if (() && !(context instanceof Application)) { if (context instanceof FragmentActivity) { return get((FragmentActivity) context); } else if (context instanceof Activity) { return get((Activity) context); } else if (context instanceof ContextWrapper // Only unwrap a ContextWrapper if the baseContext has a non-null application context. // Context#createPackageContext may return a Context without an Application instance, // in which case a ContextWrapper may be used to attach one. && ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) { return get(((ContextWrapper) context).getBaseContext()); } } return getApplicationManager(context); }
Different code logic is executed according to different parameter types.
The parameter is: the source code of FragmentActivity
public RequestManager get(@NonNull FragmentActivity activity) { if (()) { return get(()); } else { assertNotDestroyed(activity); (activity); FragmentManager fm = (); return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity)); } }
Note that the thread is judged here. If it is a child thread, it will directly overload the function of ApplicationContext type.
SupportFragmentGet method:
private RequestManager supportFragmentGet( @NonNull Context context, @NonNull FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) { SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint); RequestManager requestManager = (); if (requestManager == null) { // TODO(b/27524013): Factor out this () call. Glide glide = (context); requestManager = ( glide, (), (), context); // This is a bit of hack, we're going to start the RequestManager, but not the // corresponding Lifecycle. It's safe to start the RequestManager, but starting the // Lifecycle might trigger memory leaks. See b/154405040 if (isParentVisible) { (); } (requestManager); } return requestManager; }
This method will create a blank SupportRequestManagerFragment. The role of this Fragment is very important and is also the core of Glide. The management of declaration cycles is monitored by this.
Monitored: onStart, onStop, onDestroy, so when Glide is used, we don’t have to care about the release of resources, because Glide will automatically release resources, and the basis for releasing resources is the management of this declaration cycle.
The get function parameter is: the source code of ApplicationContext
private RequestManager getApplicationManager(@NonNull Context context) { // Either an application context or we're on a background thread. if (applicationManager == null) { synchronized (this) { if (applicationManager == null) { Glide glide = (()); applicationManager = ( glide, new ApplicationLifecycle(), new EmptyRequestManagerTreeNode(), ()); } } } return applicationManager; }
It can be seen that when the parameter type is: ApplicationContext or the child thread calls the with method, it will not create a blank Fragmnet for life cycle monitoring, nor will it release resources, so the ApplicationContext cannot be transmitted when calling the with method.
The following analysis shows which logic Glide executes in onStart, onStop, and onDestroy respectively
RequestManager's onStart method
public synchronized void onStart() { resumeRequests(); (); }
Execute the refresh request and the onStart of the Tracker, resumeRequests, and the cache of the request.
RequestTracker resumeRequests method
public void resumeRequests() { isPaused = false; for (Request request : (requests)) { if (!() && !()) { (); } } (); }
All tasks that have not been completed and are in a stopped state will be executed. Requests is a running request cache collection, which is a set collection, and the request queue pendingRequests in a waiting state will be cleaned. This is what Glide senses the declaration cycle onStart.
RequestManager's onStop method
public synchronized void onStop() { pauseRequests(); (); }
RequestTracker's pauseRequests method
public void pauseRequests() { isPaused = true; for (Request request : (requests)) { if (()) { (); (request); } } }
(): Pause all requests in the running cache collection and add these requests to the waiting cache request collection pendingRequests
RequestManager's onDestroy
public synchronized void onDestroy() { (); for (Target<?> target : ()) { clear(target); } (); (); (this); (connectivityMonitor); (addSelfToLifecycle); (this); }
You can see that Glide senses that onDestroy will perform various resource releases and listening removal operations.
load operation
RequestManager load method
public RequestBuilder<Drawable> load(@Nullable Bitmap bitmap) { return asDrawable().load(bitmap); } public RequestBuilder<Drawable> asDrawable() { return as(); } public <ResourceType> RequestBuilder<ResourceType> as( @NonNull Class<ResourceType> resourceClass) { return new RequestBuilder<>(glide, this, resourceClass, context); }
The return value of the load method is RequestBuilder, which is created through new
RequestBuilder construction method
protected RequestBuilder( @NonNull Glide glide, RequestManager requestManager, Class<TranscodeType> transcodeClass, Context context) { = glide; = requestManager; = transcodeClass; = context; = (transcodeClass); = (); initRequestListeners(()); apply(()); }
initRequestListeners: Initialize listening
apply:Calibrate parameters
The load process is relatively simple, the key into will be very complicated
into process analysis
public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) { return into(target, /*targetListener=*/ null, ()); } <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener<TranscodeType> targetListener, Executor callbackExecutor) { return into(target, targetListener, /*options=*/ this, callbackExecutor); }
Another overload method into:
private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener<TranscodeType> targetListener, BaseRequestOptions<?> options, Executor callbackExecutor) { (target); if (!isModelSet) { throw new IllegalArgumentException("You must call #load() before calling #into()"); } Request request = buildRequest(target, targetListener, options, callbackExecutor); Request previous = (); if ((previous) && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) { if (!(previous).isRunning()) { (); } return target; } (target); (request); (target, request); return target; }
The buildRequest will be passed and a SingleRequest is created. This must be understood in advance and needs to be used in the subsequent process.
(target, request): This is the main process. Let's take a look at the track method of RequestManager.
RequestManager track method
synchronized void track(@NonNull Target<?> target, @NonNull Request request) { (target); (request); }
RequestTracker's runRequest method
public void runRequest(@NonNull Request request) { (request); if (!isPaused) { (); } else { (); if ((TAG, )) { (TAG, "Paused, delaying request"); } (request); } }
The Request here is an interface, and the begin method is called. This Request is the SingleRequest created earlier.
SingleRequest begin
public void begin() { synchronized (requestLock) { ... if (status == ) { onResourceReady( resource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false); return; } cookie = (TAG); status = Status.WAITING_FOR_SIZE; if ((overrideWidth, overrideHeight)) { onSizeReady(overrideWidth, overrideHeight); } else { (this); } if ((status == || status == Status.WAITING_FOR_SIZE) && canNotifyStatusChanged()) { (getPlaceholderDrawable()); } if (IS_VERBOSE_LOGGABLE) { logV("finished run method in " + (startTime)); } } }
If the width and height are not set, the call (this) will be called to get the size, and if the onSizeReady method is set, the onSizeReady method will be executed
SingleRequest's onSizeReady method
public void onSizeReady(int width, int height) { (); synchronized (requestLock) { if (IS_VERBOSE_LOGGABLE) { logV("Got onSizeReady in " + (startTime)); } if (status != Status.WAITING_FOR_SIZE) { return; } status = ; float sizeMultiplier = (); = maybeApplySizeMultiplier(width, sizeMultiplier); = maybeApplySizeMultiplier(height, sizeMultiplier); if (IS_VERBOSE_LOGGABLE) { logV("finished setup for calling load in " + (startTime)); } loadStatus = ( glideContext, model, (), , , (), transcodeClass, priority, (), (), (), (), (), (), (), (), (), this, callbackExecutor); if (status != ) { loadStatus = null; } if (IS_VERBOSE_LOGGABLE) { logV("finished onSizeReady in " + (startTime)); } } }
Next, call the load method of Engin
How to load the Engin
public <R> LoadStatus load( GlideContext glideContext, Object model, Key signature, int width, int height, Class<?> resourceClass, Class<R> transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map<Class<?>, Transformation<?>> transformations, boolean isTransformationRequired, boolean isScaleOnlyOrNoTransform, Options options, boolean isMemoryCacheable, boolean useUnlimitedSourceExecutorPool, boolean useAnimationPool, boolean onlyRetrieveFromCache, ResourceCallback cb, Executor callbackExecutor) { long startTime = VERBOSE_IS_LOGGABLE ? () : 0; EngineKey key = ( model, signature, width, height, transformations, resourceClass, transcodeClass, options); EngineResource<?> memoryResource; synchronized (this) { memoryResource = loadFromMemory(key, isMemoryCacheable, startTime); if (memoryResource == null) { return waitForExistingOrStartNewJob( glideContext, model, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, options, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache, cb, callbackExecutor, key, startTime); } } // Avoid calling back while holding the engine lock, doing so makes it easier for callers to // deadlock. ( memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false); return null; }
The loadFromMemory method is to search from the cache first. If there is any, you can use it directly.
loadFromMemory method
@Nullable private EngineResource<?> loadFromMemory( EngineKey key, boolean isMemoryCacheable, long startTime) { if (!isMemoryCacheable) { return null; } EngineResource<?> active = loadFromActiveResources(key); if (active != null) { if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return active; } EngineResource<?> cached = loadFromCache(key); if (cached != null) { if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return cached; } return null; }
loadFromActiveResources: Look up from the active cache. If there is a direct return to use, this cache is a Map collection.
loadFromCache: Look up from memory cache, if used directly, this is an LRU collection
The above two are all memory caches, and as long as they are displayed on the page, they will be cached in the active cache.
Engin's waitForExistingOrStartNewJob method
private <R> LoadStatus waitForExistingOrStartNewJob( GlideContext glideContext, Object model, Key signature, int width, int height, Class<?> resourceClass, Class<R> transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map<Class<?>, Transformation<?>> transformations, boolean isTransformationRequired, boolean isScaleOnlyOrNoTransform, Options options, boolean isMemoryCacheable, boolean useUnlimitedSourceExecutorPool, boolean useAnimationPool, boolean onlyRetrieveFromCache, ResourceCallback cb, Executor callbackExecutor, EngineKey key, long startTime) { EngineJob<?> current = (key, onlyRetrieveFromCache); if (current != null) { (cb, callbackExecutor); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Added to existing load", startTime, key); } return new LoadStatus(cb, current); } EngineJob<R> engineJob = //This is the thread pool manager. There are various management in it. ( key, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache); DecodeJob<R> decodeJob = //This is a specific task ( glideContext, model, key, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, onlyRetrieveFromCache, options, engineJob); (key, engineJob); (cb, callbackExecutor); (decodeJob); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Started new load", startTime, key); } return new LoadStatus(cb, engineJob); }
engineJob manages a thread pool, decodeJob is the abstraction of the task and is a Runable, so the following will execute the run method of the task.
DecodeJob run method
public void run() { ... try { if (isCancelled) { notifyFailed(); return; } runWrapped(); } catch (CallbackException e) { // If a callback not controlled by Glide throws an exception, we should avoid the Glide // specific debug logic below. throw e; } catch (Throwable t) { .. } finally { // Keeping track of the fetcher here and calling cleanup is excessively paranoid, we call // close in all cases anyway. if (localFetcher != null) { (); } (); } }
DecodeJob's runWrapped method
private void runWrapped() { switch (runReason) { case INITIALIZE: stage = getNextStage(); currentGenerator = getNextGenerator(); runGenerators(); break; case SWITCH_TO_SOURCE_SERVICE: runGenerators(); break; case DECODE_DATA: decodeFromRetrievedData(); break; default: throw new IllegalStateException("Unrecognized run reason: " + runReason); } }
By default, the runGenerators() of case INITIALIZE will be executed. The getNextGenerator gets SourceGenerator. This must be clear, otherwise you will not be able to continue.
SourceGenerator's startNext method
public boolean startNext() { ... if (sourceCacheGenerator != null && ()) { return true; } sourceCacheGenerator = null; loadData = null; boolean started = false; while (!started && hasNextModelLoader()) { loadData = ().get(loadDataListIndex++); if (loadData != null && (().isDataCacheable(()) || (()))) { started = true; startNextLoad(loadData); } } return started; }
DecodeHelper's getLoadData method
List<LoadData<?>> getLoadData() { if (!isLoadDataSet) { isLoadDataSet = true; (); List<ModelLoader<Object, ?>> modelLoaders = ().getModelLoaders(model); //noinspection ForLoopReplaceableByForEach to improve perf for (int i = 0, size = (); i < size; i++) { ModelLoader<Object, ?> modelLoader = (i); LoadData<?> current = (model, width, height, options); if (current != null) { (current); } } } return loadData; }
HttpGlideUrlLoader buildLoadData method
public LoadData<InputStream> buildLoadData( @NonNull GlideUrl model, int width, int height, @NonNull Options options) { // GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time // spent parsing urls. GlideUrl url = model; if (modelCache != null) { url = (model, 0, 0); if (url == null) { (model, 0, 0, model); url = model; } } int timeout = (TIMEOUT); return new LoadData<>(url, new HttpUrlFetcher(url, timeout)); }
You can see that the final return is loadData containing HttpUrlFetcher
SourceGenerator's startNextLoad method
private void startNextLoad(final LoadData<?> toStart) { ( (), new DataCallback<Object>() { @Override public void onDataReady(@Nullable Object data) { if (isCurrentRequest(toStart)) { onDataReadyInternal(toStart, data); } } @Override public void onLoadFailed(@NonNull Exception e) { if (isCurrentRequest(toStart)) { onLoadFailedInternal(toStart, e); } } }); }
The fetcher is HttpUrlFetcher
HttpUrlFetcher loadData method
public void loadData( @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) { long startTime = (); try { InputStream result = loadDataWithRedirects((), 0, null, ()); (result); } catch (IOException e) { if ((TAG, )) { (TAG, "Failed to load data for url", e); } (e); } finally { if ((TAG, )) { (TAG, "Finished http url fetcher fetch in " + (startTime)); } } }
loadDataWithRedirects method
private InputStream loadDataWithRedirects( URL url, int redirects, URL lastUrl, Map<String, String> headers) throws HttpException { if (redirects >= MAXIMUM_REDIRECTS) { throw new HttpException( "Too many (> " + MAXIMUM_REDIRECTS + ") redirects!", INVALID_STATUS_CODE); } else { // Comparing the URLs using .equals performs additional network I/O and is generally broken. // See /2006/11/. try { if (lastUrl != null && ().equals(())) { throw new HttpException("In re-direct loop", INVALID_STATUS_CODE); } } catch (URISyntaxException e) { // Do nothing, this is best effort. } } urlConnection = buildAndConfigureConnection(url, headers); try { // Connect explicitly to avoid errors in decoders if connection fails. (); // Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352. stream = (); } catch (IOException e) { throw new HttpException( "Failed to connect or obtain data", getHttpStatusCodeOrInvalid(urlConnection), e); } ... }
Here I see the InputStream where the urlConnection gets the picture. Finally, it requests from a network link to the stream, and then converts the stream into a bitmap display process. Those who are interested can continue to view it.
Declare cycle and cache summary Glide calls the with function. If the incoming ApplicationContext or the child thread will not create a blank fragment for us, it will not monitor the life cycle, and the resource will not be automatically released, so there is no need to pass the ApplicationContext. In other cases, glide will automatically create a blank Fragment for life cycle monitoring and automatically release resources.
Glide's cache is divided into 4 levels of cache: active cache, memory cache, disk cache, and network cache
- The active cache is a collection of maps. The image will be first searched from this cache.
- Memory cache is a collection of LRU caches. If the cache is not available, it will be searched from the memory cache.
- Disk cache is a disklrucache cache collection. If it is not found from the memory cache, it will be searched from the disk cache.
- Network cache. If the disk cache does not exist, you need to request to load from the network.
This is the end of this article about the in-depth analysis of the source code layer of the Android image loading framework Gilde. For more related Android Gilde content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!