Glide source code is based on the latest 4.11 version of the library, many of the following code did not write intermediate layers of call, because there are too many layers, interested in their own debug mode or view the source code.
The last article said that the picture cache LRU principle analysis, and simple said glide use, today to get straight to the point, first look at the most simple use example:
Glide.with(this)
.load(url).diskCacheStrategy(DiskCacheStrategy.ALL)
.error(R.drawable.icon).placeholder(R.drawable.frank).into(img);
Copy the code
General introduction
Let’s take a look at the overall code for version 4.11:
Code is still very much, to analyze one by one is not realistic, let’s grasp a few key points to understand it.
with()
Again from the original with method to analyze:
With can be passed in many types, such as activity, fragment, context, view, etc., all of which are essentially the same in order to bind to the life cycle of this parameter type.
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}
Copy the code
Let’s start with getRetriever(Activity) :
Private static RequestManagerRetriever getRetriever(@nullable Context Context) {// Check that the Context cannot be empty ··· return Glide.get(context).getRequestManagerRetriever(); }Copy the code
We’re making a non-null judgment on the context, so let’s move on:
Public static Glide get (@ NonNull Context Context) {if (Glide = = null) {/ / by reflection to GeneratedAppGlideModuleImpl, And then passed to the next method GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules(context.getApplicationContext()); Synchronized (Glide. Class) {if (Glide == null) {// Initialize the parameters of Glide annotationGeneratedModule); } } } return glide; {} private static GeneratedAppGlideModule getAnnotationGeneratedGlideModules Context (Context)... / / by reflection to GeneratedAppGlideModuleImpl Class < GeneratedAppGlideModule > clazz = (Class < GeneratedAppGlideModule >) Class.forName("com.bumptech.glide.GeneratedAppGlideModuleImpl"); result = clazz.getDeclaredConstructor(Context.class).newInstance(context.getApplicationContext()); ... the return result; }Copy the code
By reflecting got GeneratedAppGlideModuleImpl class, initialization glide, after get the glide, again to get RequestManagerRetriever. Once you have the RequestManagerRetriever in hand, you need to go to GET, which is essentially obtaining a requestManager.
Public RequestManager get (@ NonNull FragmentActivity activity) {if (Util. IsOnBackgroundThread ()) {/ / if it is in the child thread, Obtain the global context, and obtain requestManager return get (activity) getApplicationContext ()); } else {// If it is on the main thread, call supportFragmentGet to get requestManager assertNotDestroyed(activity); FragmentManager fm = activity.getSupportFragmentManager(); return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity)); }}Copy the code
To be more specific, there are two steps:
- If the requestManager is called in the child thread, it will get a global context, and then use the global context to get the requestManager through the factory mode, so try not to call in the non-main thread.
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 = Glide.get(context.getApplicationContext()); / / because under normal circumstances can we go to binding in fragments or activity lifecycle, / / but it is in the child thread or don't get the global context, ApplicationManager = Factory. Build (Glide, new ApplicationLifecycle()) new EmptyRequestManagerTreeNode(), context.getApplicationContext()); } } } return applicationManager; }Copy the code
- 2. If it is in the main thread:
- The ++with argument is fragmentActivity++ : Get a fragmentManager, create an empty fragment with the fragmentManager, add that fragment to your activity or fragment, and sense the host’s life cycle! Then, again, create a new requestManager using factory mode.
- The ++with argument is FragmentManager: Yes, get the childFragmentManager. The rest of the process remains the same.
- FragmentManager = fragmentManager = fragmentManager = requestManagerFragment = requestMangaer; The difference between getting a supportFragment and a fragment…
- The ++with argument is context or view++ : determine the type of context and proceed to the following flow;
The following code is an example when the with parameter is fragmentActivity:
private RequestManager supportFragmentGet( @NonNull Context context, @NonNull FragmentManager fm, @nullable Fragment parentHint, Boolean isParentVisible) {// Get a Fragment, The fragments as induction of perceptual life cycle SupportRequestManagerFragment current = getSupportRequestManagerFragment (FM, parentHint. isParentVisible); RequestManager requestManager = current.getRequestManager(); If (requestManager == null) {// Get glide glide glide = glide. Get (context); // Create a manager with factory mode. requestManager = factory.build( glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context); current.setRequestManager(requestManager); } return requestManager; } private SupportRequestManagerFragment getSupportRequestManagerFragment( @NonNull final FragmentManager fm, @Nullable Fragment parentHint, Boolean isParentVisible) {/ / get fragments SupportRequestManagerFragment current = (SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG); If (current = = null) {/ / whether inside fragments could have first fragments current = pendingSupportRequestManagerFragments. Get (FM); If (current = = null) {/ / no new a fragment current = new SupportRequestManagerFragment (); current.setParentFragmentHint(parentHint); If (isParentVisible) {// If the parent interface is visible, start the lifecycle and call onStart; current.getGlideLifecycle().onStart(); } / / and dealing with current pendingSupportRequestManagerFragments fragments added to the queue. The put (FM, current); fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); // This step is to remove the fragmentManager! handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget(); } } return current; }Copy the code
With subtotal
That’s what happens with Glide (), but a quick summary: The with method initializes some of the environment Glide needs, and then calls the GET of the requestManagerRetriever object to get the requestManager. If the object passed is a global context, there is no need to deal with the lifecycle; If the context is not passed in globally, a hidden fragment is added to sense the lifecycle.
Let’s continue with an important load() to see how resources are loaded:
load()
Load likewise, there are many types of arguments that can be passed:
As you can see, ultimately it’s all the RequestBuilder that comes back. Use file as an example:
public RequestBuilder<Drawable> load(@Nullable File file) {
return asDrawable().load(file);
}
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class);
}
public <ResourceType> RequestBuilder<ResourceType> as(
@NonNull Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}
public RequestBuilder<TranscodeType> load(@Nullable File file) {
return loadGeneric(file);
}
Copy the code
AsDrawable is to get a RequestBuilder and use that Builder to loadGeneric. Let’s see what this method does:
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
Copy the code
ok… The assignment of global variables is just a bit confusing at first glance. So let’s skip that and go straight to the final into(); The requestBuilder class will be covered more frequently later;
into()
Into (imageView) :
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) { Util.assertMainThread(); ... return into (glideContext buildImageViewTarget (view, transcodeClass), / * targetListener = * / null, requestOptions, Executors.mainThreadExecutor()); }Copy the code
Omitted in the middle of a lot of code, we focus on the final in the method called a glideContext. BuildImageViewTarget (view, transcodeClass), the return is DrawableImageViewTarget
public <X> ViewTarget<ImageView, X> buildImageViewTarget( @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) { return imageViewTargetFactory.buildTarget(imageView, transcodeClass); } public <Z> ViewTarget<ImageView, Z> buildTarget( @NonNull ImageView view, @NonNull Class<Z> clazz) { if (Bitmap.class.equals(clazz)) { return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view); } else if (Drawable.class.isAssignableFrom(clazz)) { return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view); }}Copy the code
Moving on to into():
private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener<TranscodeType> targetListener, BaseRequestOptions<? > options, Executor callbackExecutor) {··· // Initializes a request, which determines whether thumbnails are needed, Request Request = buildRequest(target, targetListener, options, callbackExecutor); Request previous = target.getrequest (); If (request.isequivalentto (previous) &&! IsSkipMemoryCacheWithCompletePreviousRequest (options, previous)) {/ / check whether the request is started, if began, is the new one, directly execute the begin; if (! Preconditions.checkNotNull(previous).isRunning()) { previous.begin(); } return target; Clear request.clear (target) = null requestManager. Clear (target); // setRequest to a newly initialized request. SetRequest is called view.setTag, which binds request to view. target.setRequest(request); // requestTracker implements request.track; // requestTracker implements request.track. requestManager.track(target, request); return target; }Copy the code
This is the core code for into, which looks pretty simple, but is actually quite complex. BuildRequest initializes a request. BuildRequest initializes a request.
manager.track()
synchronized void track(@NonNull Target<? > target, @NonNull Request request) { targetTracker.track(target); requestTracker.runRequest(request); } public void runRequest(@NonNull Request request) { requests.add(request); // Check whether the interface is in pause state, if yes, do not start loading, if not immediately start loading. isPaused) { request.begin(); } else { request.clear(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Paused, delaying request"); } pendingRequests.add(request); }}Copy the code
Glide checks whether the interface is in display. If it is, execute BEGIN immediately. If it is not, execute Clear and queue the request. This design is more clever, save power;
How do you start a request
buildRequest()
Private Request buildRequestRecursive(·· Executor callbackExecutor) {··· Request mainRequest = BuildThumbnailRequestRecursive (...); ... the Request errorRequest = errorBuilder. BuildRequestRecursive (...); errorRequestCoordinator.setRequests(mainRequest, errorRequest); return errorRequestCoordinator; }Copy the code
Layer by layer, the result is singleRequest.
Private SingleRequest(··Executor callbackExecutor) {this.requestLock = requestLock; ... this. Engine = engine; this.callbackExecutor = callbackExecutor; status = Status.PENDING; ...}Copy the code
Take a look at what begin, the very important method mentioned earlier, does:
Public void begin() {synchronized (requestLock) {··· if (model == null) {··· · GlideException("Received null model"), logLevel); return; } ··· if (status == Status.COMPLETE) {// Call onResourceReady onResourceReady(resource, DataSource.MEMORY_CACHE); return; } status = Status.WAITING_FOR_SIZE; if (Util.isValidDimensions(overrideWidth, overrideHeight)) { onSizeReady(overrideWidth, overrideHeight); } else { target.getSize(this); } if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE) && canNotifyStatusChanged()) { target.onLoadStarted(getPlaceholderDrawable()); }...}}Copy the code
Here are a few methods. (There are too many layers of code to look at, hahaha)
- 1. OnLoadFailed: when model is found to be null, set error image directly to you, model is the source, so the source is empty, error is certain.
- 2. OnResourceReady: the onResourceReady of ViewTarget is called, which is directly setImageDrawale, and finally executes the engine release method.
- 3. OnSizeReady: Execute engine.load code
- 4. GetSize: The final call is the view’s own calculated size
- 5. OnLoadStarted: View dependency change callback
To verify our conclusion, take a look at the onSizeReady source code
public void onSizeReady(int width, int height) { stateVerifier.throwIfRecycled(); Synchronized (requestLock) {··· loadStatus = engine. Load (glideContext, model, ··· priority, RequestOptions. GetDiskCacheStrategy (), requestOptions getTransformations (),...); ...}}Copy the code
Boy, there’s a new engine. Go out of your way to verify that engine is actually responsible for loading images. Look directly at what engine.Load does:
Public <R> LoadStatus load(···) {··· EngineResource<? > memoryResource; Synchronized (this) {loadFromMemory(key, isMemoryCacheable, startTime); synchronized (this) {loadFromMemory(key, isMemoryCacheable, startTime); If (memoryResource = = null) {return waitForExistingOrStartNewJob (glideContext, model,...). } // onResourceReady cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE); return null; }Copy the code
The OnResourceReady command is used to retrieve the file from the cache. No, is executed waitForExistingOrStartNewJob () :
Private < R > LoadStatus waitForExistingOrStartNewJob (...) {... EngineJob < R > EngineJob = engineJobFactory. Build (key, IsMemoryCacheable,...); DecodeJob<R> DecodeJob = decodeJobFactory. Build (model, key, ··· engineJob); jobs.put(key, engineJob); engineJob.addCallback(cb, callbackExecutor); engineJob.start(decodeJob); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Started new load", startTime, key); } return new LoadStatus(cb, engineJob); }Copy the code
DecodeJob is a runnable that does parsing, and engineJob manages callbacks during loading and so on. Let’s look at what engineJob. Start does:
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor =
decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
executor.execute(decodeJob);
}
Copy the code
GlideExecutor is a class that inherits From ExcutorService and is obviously a thread pool. DecodeJob is used to determine if it is resolved from cache. If it is resolved from cache, call diskCacheExecutor, otherwise call getActiveSourceExecutor. DecodeJob’s run method
Public void run() {··· wrapped (); ... the if (stage! = Stage.ENCODE) { throwables.add(t); notifyFailed(); } if (! isCancelled) { throw t; } throw t; }Copy the code
Continue to see runWrapped () :
private void runWrapped() { switch (runReason) { case INITIALIZE: stage = getNextStage(Stage.INITIALIZE); currentGenerator = getNextGenerator(); runGenerators(); break; case SWITCH_TO_SOURCE_SERVICE: runGenerators(); break; case DECODE_DATA: decodeFromRetrievedData(); break; default: throw new IllegalStateException("Unrecognized run reason: " + runReason); }}Copy the code
Initialize/source_service/decode_data/runGenerators()
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
while (!isCancelled
&& currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return;
}
···
}
}
Copy the code
A new class, currentGenerator, implements the DataFetcherGenerator interface, which is used to generate a series of ModelLoaders and Models. StreamFactory = streamFactory = streamFactory = streamFactory = streamFactory = streamFactory = streamFactory = streamFactory = streamFactory
There are three kinds of the generator in the glide version of the game: first dataCache, resourceCache, sourceCache, as the name implies, is based on three kinds of sources, first look at the source of startnext is how to implement:
public boolean startNext() { if (dataToCache ! = null) { Object data = dataToCache; dataToCache = null; cacheData(data); } if (sourceCacheGenerator ! = null && sourceCacheGenerator.startNext()) { return true; }... return started; }Copy the code
CacheData is called if the cache is not empty. Otherwise, get loadData and then startNextLoad(). What is loadData? The fetcher of loadData actually handles the loading of images:
Private void cacheData(Object dataToCache) {··· try {// Obtain sourceencoder 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); }Copy the code
A quick conclusion: calling cacheData cleans up the data and returns a generator; The finally method block finally calls loaddata.fetcher.cleanup (); LoadData, loadData, loadData, loadData, loadData, loadData Here’s a snippet of the code:
LoadData and model are bound to each other by entry. LoadData and model are bound to each other by entry.
LoadData Registration time
GetSourceEncoder appears in cacheData to get the encoder, starting here:
<X> Encoder<X> getSourceEncoder(X data) throws Registry.NoSourceEncoderAvailableException {
return glideContext.getRegistry().getSourceEncoder(data);
}
Copy the code
Get the Registry by context, and then get the Encoder;
public synchronized <T> Encoder<T> getEncoder(@NonNull Class<T> dataClass) { for (Entry<? > entry : encoders) { if (entry.handles(dataClass)) { return (Encoder<T>) entry.encoder; } } return null; }Copy the code
What is registry? Let’s look at the two methods append and prepend in this class: see who is calling it, and after a few layers of investigation, it turns out to be called here: Glide’s constructor! A lot of appends to register!
registry
.append(ByteBuffer.class, new ByteBufferEncoder())
.append(InputStream.class, new StreamEncoder(arrayPool))
/* Bitmaps */
.append(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class, byteBufferBitmapDecoder)
.append(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder);
Copy the code
So get back to getSourceEncoder, which is one encoder for each data type;
ModelLoader,LoadData
ModelLoader converts various types of data into resources; LoadData is the inner class of modelLoader;
class LoadData<Data> { public final Key sourceKey; public final List<Key> alternateKeys; public final DataFetcher<Data> fetcher; ...}Copy the code
Fetcher () : fetcher (); fetcher () : fetcher ();
HttpUrlFetcher
Public void loadData (... DataCallback <? super InputStream> callback) { try { InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders()); callback.onDataReady(result); } finally {···}}Copy the code
LoadData calls loadDataWithRedirects to get an inputStream.
Private InputStream loadDataWithRedirects(URL URL,···, Map<String, String> headers) throws IOException {··· urlConnection = connectionFactory.build(URL); ... urlConnection. SetConnectTimeout (timeout); ... the final int statusCode = urlConnection. GetResponseCode (); if (isHttpOk(statusCode)) { return getStreamForSuccessfulRequest(urlConnection); }...}Copy the code
Save a lot of network traffic and know that HttpUrlConnection was called to fetch the stream. After taking the stream, it performs a series of operations to set up the image. Code is particularly much, an article can not finish, this article source level analysis on the first to this.
conclusion
Glide’s design is very perfect, very classic. Glide in general is not a framework that just wants to be an image, it’s a framework that can transform a resource into different forms. The next article will examine glide’s caching mechanism.