preface
Since the previous project was built with MVP architecture, which is a combination of RxJava + Glide + OKHttp + Retrofit and other open source frameworks, it was just on the use level without in-depth research. Recently, we plan to attack all of them. Students who have not paid attention to them can pay attention to a wave first. After reading this series of articles, you’ll be much more comfortable handling questions (whether in an interview or on the job) if you know how they work.
Android picture loading framework Glide 4.9.0 (a) From the perspective of source code analysis Glide execution process
Android Image loading framework Glide 4.9.0 (2) Analysis of Glide cache strategy from the perspective of source code
From the perspective of source code analysis of Rxjava2 basic execution process, thread switching principle
OKHttp3 (a) synchronous and asynchronous execution process is analyzed from the perspective of source code
Analyze the charm of OKHttp3 (ii) interceptor from the source point of view
OKHttp3 (iii) cache strategy is analyzed from a source code perspective
Analyze Retrofit network request from source code point of view, including RxJava + Retrofit + OKhttp network request execution process
An overview of the
I believe that everyone in the project should be useful or know Glide picture loading framework, so in the use of time is not found a line of Glide code can download the picture -> cache -> display, so Glide inside it exactly how to achieve? The following we will analyze the Glide execution process, head there is a general understanding of Glide source code execution process, we will start from the following the most simple code analysis:
Glide.with(Activity activity).load(String url).into(ImageView imageView);
Copy the code
This simple line of code is extremely complex inside.
Ps: The recommended reading time is 30 minutes, I believe you will have a harvest after reading.
with
Before analyzing the source code, let’s have a look at the sequence diagram of Glide. With execution, first have an understanding of the execution process, we are to analyze the code to do what?
The above is only Glide. With (Activity Act) process, so these classes involved above, what are their responsibilities, let’s have a look.
- Glide
- Do some init work, such as caching, thread pool, reuse pool construction, etc.
- RequestManagerRetriever
- The main thing is getting one
RequestManager
Request management class, then bind a Fragment.
- The main thing is getting one
- SupportRequestManagerFragment :
- Used to manage the life cycle of requests.
- RequestManager
- Mainly used for administrative encapsulation of requests.
Process and each role we introduced, now introduce the code concrete implementation;
-
Call with
Glide.with(Activity); Copy the code
If we look at the source function with, there are a lot of overloads.
@NonNull public static RequestManager with(@NonNull Context context) { return getRetriever(context).get(context); } @NonNull public static RequestManager with(@NonNull Activity activity) { return getRetriever(activity).get(activity); } @NonNull public static RequestManager with(@NonNull FragmentActivity activity) { return getRetriever(activity).get(activity); } @NonNull public static RequestManager with(@NonNull Fragment fragment) { return getRetriever(fragment.getActivity()).get(fragment); } / * *@deprecated* / @Deprecated @NonNull public static RequestManager with(@NonNull android.app.Fragment fragment) { return getRetriever(fragment.getActivity()).get(fragment); } @NonNull public static RequestManager with(@NonNull View view) { return getRetriever(view.getContext()).get(view); } Copy the code
In fact, there are three commonly used forms of Activity, Fragment, and Context. Here we will use Activity as the main form.
-
getRetriever(activity)
@NonNull private static RequestManagerRetriever getRetriever(@Nullable Context context) { Preconditions.checkNotNull(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 get(context).getRequestManagerRetriever(); } Copy the code
Continue to look at the get (context)
@NonNull public static Glide get(@NonNull Context context) { if (glide == null) { Class var1 = Glide.class; synchronized(Glide.class) { if (glide == null) { checkAndInitializeGlide(context); }}}return glide; } Copy the code
Glide Get (Context) is a double detection singleton mode (DCL), to ensure the safety of multithreading, if the singleton mode is not clear can see between me to write a singleton design mode details, So let’s look at checkAndInitializeGlide(Context); What did it do?
private static void checkAndInitializeGlide(@NonNull Context context) { if (isInitializing) { // A message is thrown to initialize the exception } else { // Whether to initialize flags isInitializing = true; // Start initialization initializeGlide(context); isInitializing = false; }}Copy the code
Continue to see initializeGlide (context);
private static void initializeGlide(@NonNull Context context) { // instantiate a GlideBuilder for initialization // Some default configuration information for GlideBuilder initializeGlide(context, new GlideBuilder()); } Copy the code
Continue to follow
private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) { //1. Get the context at the application level, which can avoid memory leaks, and we can also get the context in this way in actual development. Context applicationContext = context.getApplicationContext(); / / 2. Here to get the @ the annotation processor generated GeneratedAppGlideModuleImpl GlideModule logo, GeneratedAppGlideModuleFactory... And so on.GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules(); .//3. Get the code generated by the annotations to RequestManagerFactoryRequestManagerFactory factory = annotationGeneratedModule ! =null ? annotationGeneratedModule.getRequestManagerFactory() : null; //4. Add the factory to GlideBuilderbuilder.setRequestManagerFactory(factory); .//5. Glide instance object is built through Builder mode Glide glide = builder.build(applicationContext); Iterator var13 = manifestModules.iterator(); //6. Start registering component callback while(var13.hasNext()) { GlideModule module = (GlideModule)var13.next(); module.registerComponents(applicationContext, glide, glide.registry); } if(annotationGeneratedModule ! =null) { annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry); } applicationContext.registerComponentCallbacks(glide); // Assign the built Glide to the static variable of glide Glide.glide = glide; } Copy the code
I’m sure it’s easy for you to understand from the comments above, but look at comment 5, where we know that it’s generated by the builder, so let’s see how it’s implemented internally.
package com.bumptech.glide; /** * A builder class for setting default structural classes for Glide to use. */ public final class GlideBuilder { // Manage thread pools private Engine engine; // Object pool (share mode), this avoids creating objects repeatedly, which has some effect on memory overhead private BitmapPool bitmapPool; private ArrayPool arrayPool; / / GlideExecutor thread pool private GlideExecutor sourceExecutor; private GlideExecutor diskCacheExecutor; // Local disk cache private DiskCache.Factory diskCacheFactory; // Memory cache private MemorySizeCalculator memorySizeCalculator; private MemoryCache memoryCache; private ConnectivityMonitorFactory connectivityMonitorFactory; private int logLevel = Log.INFO; private RequestOptions defaultRequestOptions = new RequestOptions(); @Nullable private RequestManagerFactory requestManagerFactory; private GlideExecutor animationExecutor; private boolean isActiveResourceRetentionAllowed; @Nullable private List<RequestListener<Object>> defaultRequestListeners; private boolean isLoggingRequestOriginsEnabled; // All configuration information, using the open and close principle..// Start building @NonNull Glide build(@NonNull Context context) { // Instantiate a thread pool for a network request if (sourceExecutor == null) { sourceExecutor = GlideExecutor.newSourceExecutor(); } // Instantiate a thread pool for the local disk cache if (diskCacheExecutor == null) { diskCacheExecutor = GlideExecutor.newDiskCacheExecutor(); } // instantiate a thread pool for loading picture animations if (animationExecutor == null) { animationExecutor = GlideExecutor.newAnimationExecutor(); } // instantiate a calculation of images loaded into memory if (memorySizeCalculator == null) { memorySizeCalculator = new MemorySizeCalculator.Builder(context).build(); } // Instantiate a factory for default network connection monitoring if (connectivityMonitorFactory == null) { connectivityMonitorFactory = new DefaultConnectivityMonitorFactory(); } // Instantiate a Bitmap object pool if (bitmapPool == null) { int size = memorySizeCalculator.getBitmapPoolSize(); // If there is any available in the pool, add it directly to the least recently used LruBitmap container if (size > 0) { bitmapPool = new LruBitmapPool(size); } else { // If the pool is full, the BitmapPoolAdapter is installed bitmapPool = newBitmapPoolAdapter(); }}// instantiate an array object pool if (arrayPool == null) { arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes()); } // Resource memory cache if (memoryCache == null) { memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize()); } // The factory for disk caching if (diskCacheFactory == null) { diskCacheFactory = new InternalCacheDiskCacheFactory(context); } // Build an engine that implements caching policies and thread pools if (engine == null) { engine = new Engine( memoryCache, diskCacheFactory, diskCacheExecutor, sourceExecutor, GlideExecutor.newUnlimitedSourceExecutor(), GlideExecutor.newAnimationExecutor(), isActiveResourceRetentionAllowed); } if (defaultRequestListeners == null) { defaultRequestListeners = Collections.emptyList(); } else { defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners); } // Instantiate a RequestManagerRetriever request management class RequestManagerRetriever requestManagerRetriever = new RequestManagerRetriever(requestManagerFactory); // Instantiate Glide return newGlide( context, engine, memoryCache, bitmapPool, arrayPool, requestManagerRetriever, connectivityMonitorFactory, logLevel, defaultRequestOptions.lock(), defaultTransitionOptions, defaultRequestListeners, isLoggingRequestOriginsEnabled); }}Copy the code
Build a thread pool, a reuse pool, a cache, a cache, and a Glide instance.
Glide( @NonNull Context context, @NonNull Engine engine, @NonNull MemoryCache memoryCache, @NonNull BitmapPool bitmapPool, @NonNull ArrayPool arrayPool, @NonNull RequestManagerRetriever requestManagerRetriever, @NonNull ConnectivityMonitorFactory connectivityMonitorFactory, int logLevel, @NonNull RequestOptions defaultRequestOptions, @NonNullMap<Class<? >, TransitionOptions<? ,? >> defaultTransitionOptions,@NonNull List<RequestListener<Object>> defaultRequestListeners, boolean isLoggingRequestOriginsEnabled) { // Save the thread pool, object pool, cache pool built by Builder to Glide this.engine = engine; this.bitmapPool = bitmapPool; this.arrayPool = arrayPool; this.memoryCache = memoryCache; this.requestManagerRetriever = requestManagerRetriever; this.connectivityMonitorFactory = connectivityMonitorFactory; // Get Glide required codec DecodeFormat decodeFormat = defaultRequestOptions.getOptions().get(Downsampler.DECODE_FORMAT); bitmapPreFiller = new BitmapPreFiller(memoryCache, bitmapPool, decodeFormat); final Resources resources = context.getResources(); registry = new Registry(); registry.register(new DefaultImageHeaderParser()); // Ignore some configuration information.// The factory used to display the corresponding image ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory(); // Build a Glide specific context glideContext = new GlideContext( context, arrayPool, registry, imageViewTargetFactory, defaultRequestOptions, defaultTransitionOptions, defaultRequestListeners, engine, isLoggingRequestOriginsEnabled, logLevel); } Copy the code
Here GlideContext is the Context at the Context level.
public class GlideContext extends ContextWrapper{... }Copy the code
Now that we know how to build the cache policy, Glide, GlideContext, let’s see how to get the RequestManager class
-
RequestManager getRetriever(activity).get(activity);
Here get also has a lot of overloaded functions, let’s look at the overloaded Activity parameter.
public class RequestManagerRetriever implements Handler.Callback { @NonNull public RequestManager get(@NonNull Context context) { if (context == null) { throw new IllegalArgumentException("You cannot start a load on a null Context"); // If it is in the main thread and not executed for an application-level Context } else if(Util.isOnMainThread() && ! (contextinstanceof Application)) { if (context instanceof FragmentActivity) { return get((FragmentActivity) context); } else if (context instanceof Activity) { return get((Activity) context); } else if (context instanceof ContextWrapper) { // all the way to BaseContext returnget(((ContextWrapper) context).getBaseContext()); }}// Execute directly if not in the main thread or if it is Application return getApplicationManager(context); } @NonNull public RequestManager get(@NonNull FragmentActivity activity) {... }@NonNull public RequestManager get(@NonNull Fragment fragment) {... }// Get the RequestManager from the Activity @SuppressWarnings("deprecation") @NonNull public RequestManager get(@NonNull Activity activity) { // Determine whether a task is currently requested in a child thread if (Util.isOnBackgroundThread()) { // Load through the application-level Context return get(activity.getApplicationContext()); } else { // Check whether the Activity has been destroyed assertNotDestroyed(activity); // Get the FragmentManager of the current Activity android.app.FragmentManager fm = activity.getFragmentManager(); // Generate a Fragment and bind it to a RequestManager return fragmentGet( activity, fm, /*parentHint=*/ null, isActivityVisible(activity)); }}}Copy the code
Let’s look at the fragmentGet implementation
private RequestManager fragmentGet(@NonNull Context context, @NonNull android.app.FragmentManager fm, @Nullable android.app.Fragment parentHint, boolean isParentVisible) { //1. Add a Fragment on the current Acitivty to manage the request life cycle RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible); // Get the admin class for the current request RequestManager requestManager = current.getRequestManager(); // If not, create a request manager to remain in the Fragment for the current management life cycle, equivalent to binding the two to avoid memory leaks. if (requestManager == null) { Glide glide = Glide.get(context); requestManager = factory.build( glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context); current.setRequestManager(requestManager); } // Returns the manager of the current request return requestManager; } Copy the code
As you can see from the code above, this is used to manage the life cycle of a Fragment request, so let’s look at how a Fragment can be added to an Activity.
private RequestManagerFragment getRequestManagerFragment( @NonNull final android.app.FragmentManager fm, @Nullable android.app.Fragment parentHint, boolean isParentVisible) { // Get an instantiated Fragment with a TAG, equivalent to the same Activity Glide. With.. Multiple times, then there is no need to create multiple. RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG); // If you don't have a Fragment in your current Activity that manages the request lifecycle, check it from the cache if (current == null) { current = pendingRequestManagerFragments.get(fm); // Instantiate a Fragment if the cache does not have one if (current == null) { current = new RequestManagerFragment(); current.setParentFragmentHint(parentHint); // Start if there is already a request for execution if (isParentVisible) { current.getGlideLifecycle().onStart(); } // Add to the Map cache pendingRequestManagerFragments.put(fm, current); // Add a Fragment container from the current Activity's FragmentManager fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); // Add to FragmentManager successfully, send clear cache.handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(); }}return current; } Copy the code
Finally, it’s time to bind the request management class to the Fragment.
private RequestManager fragmentGet(@NonNull Context context, @NonNull android.app.FragmentManager fm, @Nullable android.app.Fragment parentHint, boolean isParentVisible) {...// If not, create a request manager to remain in the Fragment for the current management life cycle, equivalent to binding the two to avoid memory leaks. if (requestManager == null) { // Get the single Glide Glide glide = Glide.get(context); / / build request management, current getGlideLifecycle (), we'll talk about this class is ActivityFragmentLifecycle behind requestManager = factory.build( glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context); // Bind the constructed request management to the Fragment. current.setRequestManager(requestManager); } // Returns the manager of the current request return requestManager; } Copy the code
How to build a request management class
private static final RequestManagerFactory DEFAULT_FACTORY = new RequestManagerFactory() { @NonNull @Override public RequestManager build(@NonNull Glide glide, @NonNull Lifecycle lifecycle, @NonNull RequestManagerTreeNode requestManagerTreeNode, @NonNull Context context) { / / instantiate return newRequestManager(glide, lifecycle, requestManagerTreeNode, context); }};Copy the code
public RequestManager( @NonNull Glide glide, @NonNull Lifecycle lifecycle, @NonNull RequestManagerTreeNode treeNode, @NonNull Context context) { this( glide, lifecycle, treeNode, new RequestTracker(), glide.getConnectivityMonitorFactory(), context); } // Our usage is safe here. @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") RequestManager( Glide glide, Lifecycle lifecycle, RequestManagerTreeNode treeNode, RequestTracker requestTracker, ConnectivityMonitorFactory factory, Context context) { this.glide = glide; this.lifecycle = lifecycle; this.treeNode = treeNode; this.requestTracker = requestTracker; this.context = context; connectivityMonitor = factory.build( context.getApplicationContext(), new RequestManagerConnectivityListener(requestTracker)); // Add life cycle listener, Fragment pass if (Util.isOnBackgroundThread()) { mainHandler.post(addSelfToLifecycle); } else { lifecycle.addListener(this); } // Add a listener for network changes lifecycle.addListener(connectivityMonitor); defaultRequestListeners = new CopyOnWriteArrayList<>(glide.getGlideContext().getDefaultRequestListeners()); setRequestOptions(glide.getGlideContext().getDefaultRequestOptions()); glide.registerRequestManager(this); } Copy the code
The RequestManager + Fragment class has been bound successfully, and the declaration cycle listening has been set.
So how do they keep each other alive? We’re looking at the Fragment life cycle method
// Monitor the life cycle of a Fragment. // Monitor the life cycle of an Activity. // Monitor the life cycle of an Activity. public class RequestManagerFragment extends Fragment { // Equivalent to a lifecycle callback private finalActivityFragmentLifecycle lifecycle; .@Override public void onStart(a) { super.onStart(); lifecycle.onStart(); } @Override public void onStop(a) { super.onStop(); lifecycle.onStop(); } @Override public void onDestroy(a) { super.onDestroy(); lifecycle.onDestroy(); }... }Copy the code
What is Lifecycle here that we are going to take a look at
class ActivityFragmentLifecycle implements Lifecycle { private final Set<LifecycleListener> lifecycleListeners = Collections.newSetFromMap(new WeakHashMap<LifecycleListener, Boolean>()); private boolean isStarted; private boolean isDestroyed; @Override public void addListener(@NonNull LifecycleListener listener) { lifecycleListeners.add(listener); if (isDestroyed) { listener.onDestroy(); } else if (isStarted) { listener.onStart(); } else{ listener.onStop(); }}@Override public void removeListener(@NonNull LifecycleListener listener) { lifecycleListeners.remove(listener); } void onStart(a) { isStarted = true; for(LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) { lifecycleListener.onStart(); }}void onStop(a) { isStarted = false; for(LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) { lifecycleListener.onStop(); }}void onDestroy(a) { isDestroyed = true; for(LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) { lifecycleListener.onDestroy(); }}}Copy the code
It implements the Lifecycle Lifecycle interface in Glide, registers the RequestManager that was instantiated in the RequestManagerFactory we just explained and then adds Lifecycle callback listeners to the constructor, Let’s see.
public class RequestManager implements LifecycleListener.ModelTypes<RequestBuilder<Drawable>> {...@Override public synchronized void onStart(a) { resumeRequests(); targetTracker.onStart(); } @Override public synchronized void onStop(a) { pauseRequests(); targetTracker.onStop(); } @Override public synchronized void onDestroy(a) { targetTracker.onDestroy(); for(Target<? > target : targetTracker.getAll()) { clear(target); } targetTracker.clear(); requestTracker.clearRequests(); lifecycle.removeListener(this); lifecycle.removeListener(connectivityMonitor); mainHandler.removeCallbacks(addSelfToLifecycle); glide.unregisterRequestManager(this); }... }Copy the code
These three callbacks are passed by the Fragment and are used to monitor the status of the request in real time.
With the conclusion:
According to the source code analysis, we know that Glide. With (Activity) mainly do thread pool + cache + request management and life cycle binding + other configuration initialization construction, the internal code is actually very large, everywhere is the design mode, If you don’t know anything about design patterns, check out the design patterns article I wrote earlier. Glide. With our analysis done, what does the load function mainly do?
load
Here we take load(String URL) network image address as an example to explain the load process.
Glide.with(this).load("Https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3638429004, & FM = 26 & gp = 0. 1717840478 JPG")
Copy the code
As usual, a sequence diagram first.
- RequestBuilder: this is a common RequestBuilder class that handles setup options and startup loads for common resource types.
The load function is relatively easy to load. Let’s look at the actual code implementation
public class RequestManager implements LifecycleListener.ModelTypes<RequestBuilder<Drawable>> {...public RequestBuilder<Drawable> load(@Nullable String string) {
// Here we call the Drawable image load requester to load it
return asDrawable().load(string);
}
public RequestBuilder<Drawable> asDrawable(a) {
return as(Drawable.class);
}
@NonNull
@CheckResult
@Override
public RequestBuilder<Drawable> load(@Nullable Uri uri) {
return asDrawable().load(uri);
}
@NonNull
@CheckResult
@Override
public RequestBuilder<Drawable> load(@Nullable File file) {
return asDrawable().load(file);
}
public <ResourceType> RequestBuilder<ResourceType> as( @NonNull Class
resourceClass)
{
return new RequestBuilder<>(glide, this, resourceClass, context); }}Copy the code
You can see that load also has a lot of overloaded functions, but it’s really powerful, just give an image resource and it’ll load it for you.
public class RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>>
implements Cloneable.ModelTypes<RequestBuilder<TranscodeType>> {
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
// Describe the loaded data source - here it can be seen as the http://xxxx.png we just passed in
@Nullable private Object model;
// Describe whether the request has added a loaded data source
private boolean isModelSet;
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this; }}Copy the code
Ok, now that the RequestBuilder is built, it’s time to wait for the request to be executed. Let’s look at its RequestBuilder into method.
into
We have finally got into the main character, everyone can’t wait, so now let’s follow into together, as usual, first up a sequence diagram.
After 2 hours of work, the into sequence diagram was finally drawn. If you feel unclear, you can download the original version. There are too many classes and callbacks involved.
Glide.with(activity).load(http://xxxx.png).into(imageView);
Copy the code
@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
Rebuild requestOptions based on the scaleType in the ImageView layoutBaseRequestOptions<? > requestOptions =this;
if(! requestOptions.isTransformationSet() && requestOptions.isTransformationAllowed() && view.getScaleType() ! =null) {
// If scaleType is not set in the XML ImageView node, it is initialized in the constructor by default to mScaleType = scaletype.fit_center;
switch (view.getScaleType()) {
.....
case FIT_CENTER:
case FIT_START:
case FIT_END:
// Clone (prototype design mode) is used here, and choose a solution that is properly centered for display
requestOptions = requestOptions.clone().optionalFitCenter();
break; . }}// Call into overload to create a ViewTarget
return into(
// Call buildImageViewTarget to build a Target of type ImageView (Bitmap/Drawable)
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions,
Executors.mainThreadExecutor());
}
Copy the code
According to the above code and comments:
- Get the current ImageView getScaleType property and re-clone it.
- Call the into overload to continue building;
Let’s look at glideContext buildImageViewTarget is how to build up ImageViewTarget
@NonNull
public <X> ViewTarget<ImageView, X> buildImageViewTarget( @NonNull ImageView imageView, @NonNull Class
transcodeClass)
{
// Call the factory schema to generate a corresponding ImageViewTarget based on transcodeClass
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
Copy the code
public class ImageViewTargetFactory {
@NonNull
@SuppressWarnings("unchecked")
public <Z> ViewTarget<ImageView, Z> buildTarget(@NonNull ImageView view, @NonNull Class
clazz)
{
// Create a Bitmap ImageViewTarget if the encoding type of the target is Bitmap
if (Bitmap.class.equals(clazz)) {
return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
//// Create an ImageViewTarget of Drawable if the encoding type of the target is Drawable
} else if (Drawable.class.isAssignableFrom(clazz)) {
return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException(
"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)"); }}}Copy the code
Note that BitmapImageViewTarget is produced only when asBitmap is called, so let’s focus on Drawable. Let’s take a quick look at how this Target is implemented internally. Because we’ll get to that at the end, just to give you an idea.
public class DrawableImageViewTarget extends ImageViewTarget<Drawable> {
public DrawableImageViewTarget(ImageView view) {
super(view);
}
@SuppressWarnings({"unused"."deprecation"})
@Deprecated
public DrawableImageViewTarget(ImageView view, boolean waitForLayout) {
super(view, waitForLayout);
}
@Override
protected void setResource(@Nullable Drawable resource) { view.setImageDrawable(resource); }}Copy the code
DrawableImageViewTarget inherits the setResource function overwritten by ImageViewTarget, which implements the logic for displaying a Drawable image. Continue into reload.
private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener
targetListener, BaseRequestOptions
options, Executor callbackExecutor)
{
Preconditions.checkNotNull(target);
// isModelSet is set to true during load, so no exceptions are thrown
if(! isModelSet) {throw new IllegalArgumentException("You must call #load() before calling #into()");
}
// Generate a Glide Request request for this http://xxx.png
Request request = buildRequest(target, targetListener, options, callbackExecutor);
// Get the last request
Request previous = target.getRequest();
// The following lines indicate whether the request conflicts with the previous one
if(request.isEquivalentTo(previous) && ! isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) { request.recycle();if(! Preconditions.checkNotNull(previous).isRunning()) { previous.begin(); }return target;
}
// Clear up the target request management
requestManager.clear(target);
// Re-set a Glide Request for the target
target.setRequest(request);
// Finally, the track of the RequestManager is called to execute the Glide Request of the target
requestManager.track(target, request);
return target;
}
Copy the code
Here are two important points:
- Build a Glide Request request for Target buildRequest;
- Submit the constructed Request to the RequestManager for execution.
Let’s take a quick look at how to construct a Request. Okay
private Request buildRequest( Target
target, @Nullable RequestListener
targetListener, BaseRequestOptions
requestOptions, Executor callbackExecutor)
{
return buildRequestRecursive(
target,
targetListener,
/*parentCoordinator=*/ null,
transitionOptions,
requestOptions.getPriority(),
requestOptions.getOverrideWidth(),
requestOptions.getOverrideHeight(),
requestOptions,
callbackExecutor);
}
Copy the code
Continue to follow
private Request obtainRequest( Target<TranscodeType> target, RequestListener<TranscodeType> targetListener, BaseRequestOptions<? > requestOptions, RequestCoordinator requestCoordinator, TransitionOptions<? ,?super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
Executor callbackExecutor) {
return SingleRequest.obtain(
context,
glideContext,
model,
transcodeClass,
requestOptions,
overrideWidth,
overrideHeight,
priority,
target,
targetListener,
requestListeners,
requestCoordinator,
glideContext.getEngine(),
transitionOptions.getTransitionFactory(),
callbackExecutor);
}
Copy the code
Finally, SingleRequest. Obtain builds the Request object for us, starting with the initialization of some configuration properties. Let’s look at the track function execution where begin begins.
// Add a synchronization lock to the current class to avoid thread-induced security
synchronized void track(@NonNull Target
target, @NonNull Request request) {
// Add a target task
targetTracker.track(target);
// Execute Glide Request
requestTracker.runRequest(request);
}
Copy the code
public void runRequest(@NonNull Request request) {
// Add a request
requests.add(request);
// Whether to pause
if(! isPaused) {// Without pausing, Request BEGIN is invoked
request.begin();
} else {
// If pause is called, clean up the requestrequest.clear(); pendingRequests.add(request); }}Copy the code
The logic is to add a request for Requests, see if it is stopped, and call Request.begin () if not; The execution.
So Request is an interface, and the buildRequest function that we talked about earlier is called SingleRequest so let’s just look at its begin function.
@Override
public synchronized void begin(a) {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
if (model == null) {
// Check that the size of the external call is valid
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
// Failed callback
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
if (status == Status.COMPLETE) {
// Indicates that the resource is ready
onResourceReady(resource, DataSource.MEMORY_CACHE);
return;
}
status = Status.WAITING_FOR_SIZE;
// This indicates that the size is ready
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
/ /
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
// This is just the beginning of the callback, which shows the progress of the beginning
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished run method in "+ LogTime.getElapsedMillis(startTime)); }}Copy the code
Let’s go straight to onSizeReady
public synchronized void onSizeReady(int width, int height) { stateVerifier.throwIfRecycled(); .// These are some initialization states, configuration properties, we do not care.
loadStatus =
/ / load
engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.isScaleOnlyOrNoTransform(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getUseAnimationPool(),
requestOptions.getOnlyRetrieveFromCache(),
this,
callbackExecutor);
}
Copy the code
Keep tracking
public synchronized <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
intheight, 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) {
// Get the cache or request key
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
// Retrieve the resources in the active cache by keyEngineResource<? > active = loadFromActiveResources(key, isMemoryCacheable);// Call back the activity if it is in the cache
if(active ! =null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
return null;
}
// Try to find this resource from LruResourceCacheEngineResource<? > cached = loadFromCache(key, isMemoryCacheable);if(cached ! =null) {
// If there is a callback in the memory cache Lru
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
return null;
}
/ / -- -- -- -- -- -- -- -- -- -- -- -- -- go to the activities described here with memory cache cache didn't find -- -- -- -- -- -- -- -- -- -- -
// See if the cache is executing based on the KeyEngineJob<? > current = jobs.get(key, onlyRetrieveFromCache);if(current ! =null) {
// Call back the data if it is running
current.addCallback(cb, callbackExecutor);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
// -------------- is a new task ---------------
// -------------- Build a new request task ---------------
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
// Add the current key to the cache
jobs.put(key, engineJob);
// Perform the task callback
engineJob.addCallback(cb, callbackExecutor);
// Start executing.
engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
}
Copy the code
Using the logic in engine.load, we can summarize a few points:
- Build the request or cache KEY first;
- Find the corresponding resource data (ActiveResources,LruResourceCache) from the memory cache based on the KEY, and call the corresponding onResourceReady to indicate that the data is ready.
- Find the corresponding key from the execution cache
- If yes, the command is being executed.
- If no, run enginejob. start to start a new request task.
EngineJob. Start = engineJob.
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
// Get the thread pool for Glide execution
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
// Start executing
executor.execute(decodeJob);
}
Copy the code
DecodeJob is a Runnable interface, where GlideExecutor thread pool starts execution, the run function will start DecodeJob, we track the implementation of run.
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback.Runnable.Comparable<DecodeJob<? > >,Poolable {
// The thread calls run
@Override
public void run(a) {
GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", model); DataFetcher<? > localFetcher = currentFetcher;try {
// Whether the current request was cancelled
if (isCancelled) {
notifyFailed();
return;
}
/ / execution
runWrapped();
} catch (CallbackException e) {
.....// Some error callbacks}}Copy the code
Tracking runWrapped
private void runWrapped(a) {
switch (runReason) {
case INITIALIZE:
// Get the resource status
stage = getNextStage(Stage.INITIALIZE);
// Obtain the resource actuator based on the current resource status
currentGenerator = getNextGenerator();
/ / execution
runGenerators();
break; . }}private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
// If the resource caching policy is configured for the external call, return stage.resource_cache
// Otherwise proceed with stage.resource_cache execution.
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
// If the source data cache is configured externally, return stage.data_cache
// Otherwise proceed with getNextStage(stage.data_cache)
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
Return FINISHED if only data can be retrieved from the cache; otherwise, return SOURCE.
// this means a new resource
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: "+ current); }}Copy the code
As you can see from the code above, we are looking for the executor of the resource. In this case, since we have no external caching policy, we load directly from the source data. See the code below.
private DataFetcherGenerator getNextGenerator(a) {
switch (stage) {
// From the resource cache executor
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
// Source data disk cache executor
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
// Nothing is configured, source data executor
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: "+ stage); }}Copy the code
// Return the SourceGenerator source data actuator. Continue with the following code execution
private void runGenerators(a) {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
// Determine whether to cancel, whether to start
/ / call DataFetcherGenerator. StartNext () to judge whether belong to start executing task
while(! isCancelled && currentGenerator ! =null
&& !(isStarted = currentGenerator.startNext())) {
....
}
Copy the code
. Here we see first currentGenerator startNext () this code, DataFetcherGenerator is an abstract class, then which perform the implementation of the class is here, you can refer to the following form;
Resource state | describe | Resource executor |
---|---|---|
Stage.RESOURCE_CACHE | Retrieves cached resource data from disk. | ResourceCacheGenerator |
Stage.DATA_CACHE | Retrieves cached source data from disk. | DataCacheGenerator |
Stage.SOURCE | A new request task | SourceGenerator |
Since we are not configuring caching here, look directly at the SourceGenerator
@Override
public boolean startNext(a) {... loadData =null;
boolean started = false;
while(! started && hasNextModelLoader()) {// Get a ModelLoad loader
loadData = helper.getLoadData().get(loadDataListIndex++);
if(loadData ! =null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
// Use fetcher in the loader to load data according to priority
loadData.fetcher.loadData(helper.getPriority(), this); }}return started;
}
Copy the code
Helper.getloaddata () gets an HTTP request from helper.getloadData (). It is not cached, so we can guess the HTTP request.
List<LoadData<? >> getLoadData() {if(! isLoadDataSet) { isLoadDataSet =true;
loadData.clear();
// Get the loader from the Model registered by Glide (registration is done through Registry when Glide is initialized)
//.append ()List<ModelLoader<Object, ? >> modelLoaders = glideContext.getRegistry().getModelLoaders(model);for (int i = 0, size = modelLoaders.size(); i < size; i++) { ModelLoader<Object, ? > modelLoader = modelLoaders.get(i); LoadData<? > current =// Start building the loader
modelLoader.buildLoadData(model, width, height, options);
// If the shelf is not empty, add temporary cache
if(current ! =null) { loadData.add(current); }}}return loadData;
}
Copy the code
Start by getting a container for the loader that was added via Registry.append() on Glide initialization, because we used the network link example. So, the implementation class for ModelLoad is the HttpGlideUrlLoader. Let’s look at the implementation
@Override
public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height,
@NonNull Options options) {
GlideUrl url = model;
if(modelCache ! =null) {
url = modelCache.get(model, 0.0);
if (url == null) {
modelCache.put(model, 0.0, model); url = model; }}int timeout = options.get(TIMEOUT);
return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
}
Copy the code
Here you see an HttpUrlFetcher returned to the loader. We have the loader, now start loading, return to the source code, look at the following:
class DataCacheGenerator implements DataFetcherGenerator.DataFetcher.DataCallback<Object> {
// Select important code
@Override
public boolean startNext(a) {...while(! started && hasNextModelLoader()) { ModelLoader<File, ? > modelLoader = modelLoaders.get(modelLoaderIndex++); loadData = modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());if(loadData ! =null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
// Start loading data through the loader
loadData.fetcher.loadData(helper.getPriority(), this); }}returnstarted; }}Copy the code
Because we just saw that the loader we got here is HttpUrlFetcher so let’s just look at the loadData implementation
@Override
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
// HTTP request that returns an InputStream InputStream
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0.null, glideUrl.getHeaders());
// Call InputStream back as a callback
callback.onDataReady(result);
} catch (IOException e) {
callback.onLoadFailed(e);
} finally{... }}Copy the code
How does the loadDataWithRedirects function produce an InputStream
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
Map<String, String> headers) throws IOException {
if (redirects >= MAXIMUM_REDIRECTS) {
throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
try {
if(lastUrl ! =null && url.toURI().equals(lastUrl.toURI())) {
throw new HttpException("In re-direct loop"); }}catch (URISyntaxException e) {
// Do nothing, this is best effort.
}
}
urlConnection = connectionFactory.build(url);
for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
}
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
urlConnection.setInstanceFollowRedirects(false);
urlConnection.connect();
stream = urlConnection.getInputStream();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (isHttpOk(statusCode)) {
returngetStreamForSuccessfulRequest(urlConnection); }...// Let's ignore the exception for now
}
Copy the code
HttpURLConnection is a web request made by HttpURLConnection as a Glide. A successful request returns an input stream that is finally called to the DecodeJob onDataFetcherReady via the onDataReady callback. Let’s follow the next callback
First, call back to SourceGenerator
@Override
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if(data ! =null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
cb.reschedule();
} else{ cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher, loadData.fetcher.getDataSource(), originalKey); }}Copy the code
We’re going to have an else here because we didn’t configure the cache, so we’re going to call back.
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback.Runnable.Comparable<DecodeJob<? > >,Poolable {...@Override
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher
fetcher, DataSource dataSource, Key attemptedKey) {
this.currentSourceKey = sourceKey; // The key of the currently returned data
this.currentData = data; // The data returned
this.currentFetcher = fetcher; HttpUrlFetcher (HttpUrlFetcher)
this.currentDataSource = dataSource; // Data source URL
this.currentAttemptingKey = attemptedKey;
if(Thread.currentThread() ! = currentThread) { runReason = RunReason.DECODE_DATA; callback.reschedule(this);
} else {
GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
try {
// Parse the returned data
decodeFromRetrievedData();
} finally{ GlideTrace.endSection(); }}}... }// Parse the returned data
private void decodeFromRetrievedData(a) {
Resource<R> resource = null;
try {
// call decodeFrom to parse data; HttpUrlFetcher , InputStream , currentDataSource
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
// When parsing is complete, notify
if(resource ! =null) {
notifyEncodeAndRelease(resource, currentDataSource);
} else{ runGenerators(); }}Copy the code
Continue to follow decodeFromData to see how to parse into Resource
private <Data> Resource<R> decodeFromData(DataFetcher
fetcher, Data data, DataSource dataSource) throws GlideException {... Resource<R> result = decodeFromFetcher(data, dataSource); .return result;
} finally{ fetcher.cleanup(); }}@SuppressWarnings("unchecked")
private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
throws GlideException {
// Get the parser LoadPath for the current data classLoadPath<Data, ? , R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());// Data is parsed through the LoadPath parser
return runLoadPath(data, dataSource, path);
}
private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource, LoadPath
path)
,> throws GlideException {
Options options = getOptionsWithHardwareConfig(dataSource);
InputStreamRewinder = InputStreamRewinder = InputStreamRewinder
DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data);
try {
// Move the task of parsing resources to the load. path method
return path.load(
rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
} finally{ rewinder.cleanup(); }}Copy the code
To parse the data, we first build a LoadPath, then create a DataRewinder of type InputStreamRewinder, and finally put the data parsing operations into the loadPath. load method. Loadpath. load: loadpath. load: loadpath. load
public Resource<Transcode> load(DataRewinder<Data> rewinder, @NonNull Options options, int width,
int height, DecodePath.DecodeCallback<ResourceType> decodeCallback) throws GlideException {
try {
return loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables);
} finally{ listPool.release(throwables); }}private Resource<Transcode> loadWithExceptionList(DataRewinder<Data> rewinder,
@NonNull Options options,
int width, int height, DecodePath.DecodeCallback<ResourceType> decodeCallback,
List<Throwable> exceptions) throws GlideException {
Resource<Transcode> result = null;
// Walk through the DecodePath collection stored internally to parse data through them
for (int i = 0, size = decodePaths.size(); i < size; i++) {
DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);
try {
// This is where the data is actually parsed
result = path.decode(rewinder, width, height, options, decodeCallback);
} catch(GlideException e) { ... }...return result;
}
Copy the code
With path. Decode
public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height,
@NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException {
// Call decodeResourec to parse the data into an intermediate resource
Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
// Call back the data after parsing
Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
// Convert a resource to a target resource
return transcoder.transcode(transformed, options);
}
Copy the code
How does decodeResource resolve to an intermediate resource
@NonNull
private Resource<ResourceType> decodeResource(DataRewinder<DataType> rewinder, int width,
int height, @NonNull Options options) throws GlideException {...try {
return decodeResourceWithList(rewinder, width, height, options, exceptions);
} finally{... }}@NonNull
private Resource<ResourceType> decodeResourceWithList(DataRewinder<DataType> rewinder, int width,
int height, @NonNull Options options, List<Throwable> exceptions) throws GlideException {
Resource<ResourceType> result = null;
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0, size = decoders.size(); i < size; i++) {
ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i);
try {
DataType data = rewinder.rewindAndGet();
if (decoder.handles(data, options)) {
data = rewinder.rewindAndGet();
// Call resourcedecoder. decode to parse the dataresult = decoder.decode(data, width, height, options); }}catch (IOException | RuntimeException | OutOfMemoryError e) {
...
}
return result;
}
Copy the code
As you can see, the data parsing task is ultimately performed through DecodePath, which has three internal actions
-
DeResource parses source data into resources
- Source data: InputStream
- Intermediate: Bitmap
-
Call DecodeCallback. OnResourceDecoded processing resources
-
Call ResourceTranscoder. Transcode resources into the target resource
- Target resource type: Drawable
Decode through the above decoder. Decode source code, it is an interface, because we here the source data is InputStream, so, its implementation class is StreamBitmapDecoder, we will look at its internal decoding process
@Override
public Resource<Bitmap> decode(@NonNull InputStream source, int width, int height,
@NonNull Options options)
throws IOException {
// Use to fix the mark limit to avoid allocating buffers that fit entire images.
final RecyclableBufferedInputStream bufferedStream;
final booleanownsBufferedStream; .try {
return downsampler.decode(invalidatingStream, width, height, options, callbacks);
} finally{... }}Copy the code
Now we have a Bitmap data, and we need to call it back. Please look at the following code
public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height,
@NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException {
//1. Call decodeResourec to parse data into an intermediate resource Bitmap
Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
//2. Call back the data after parsing
Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
//3. Convert the resource to the target resource Bitmap to Drawable
return transcoder.transcode(transformed, options);
}
Copy the code
Call back to DecodeJob (DecodeJob
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback.Runnable.Comparable<DecodeJob<? > >,Poolable {...@Override
public Resource<Z> onResourceDecoded(@NonNull Resource<Z> decoded) {
return DecodeJob.this.onResourceDecoded(dataSource, decoded); }... }Copy the code
To continue with
@Synthetic
@NonNull
<Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource
decoded)
{
@SuppressWarnings("unchecked")
// Get the resource type
Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
Transformation<Z> appliedTransformation = null;
Resource<Z> transformed = decoded;
// If it is not obtained from disk resources, perform the transform operation
if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
transformed = appliedTransformation.transform(glideContext, decoded, width, height);
}
...
// Build the data encoding strategy
final EncodeStrategy encodeStrategy;
final ResourceEncoder<Z> encoder;
if (decodeHelper.isResourceEncoderAvailable(transformed)) {
encoder = decodeHelper.getResultEncoder(transformed);
encodeStrategy = encoder.getEncodeStrategy(options);
} else {
encoder = null;
encodeStrategy = EncodeStrategy.NONE;
}
// Build the cache Key according to the encoding policy
Resource<Z> result = transformed;
booleanisFromAlternateCacheKey = ! decodeHelper.isSourceKey(currentSourceKey);if (diskCacheStrategy.isResourceCacheable(isFromAlternateCacheKey, dataSource,
encodeStrategy)) {
if (encoder == null) {
throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());
}
final Key key;
switch (encodeStrategy) {
case SOURCE:
// Source data key
key = new DataCacheKey(currentSourceKey, signature);
break; . }// Initialize the code manager to commit the memory cache
LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
deferredEncodeManager.init(key, encoder, lockedResult);
result = lockedResult;
}
// Return the transformed Bitmap
return result;
}
Copy the code
As you can see, onResourceDecoded mainly does the following operations for intermediate resources
- A resource conversion operation was performed. Examples include Fit_Center and CenterCrop, which are configured at request time.
- The key to build the disk cache.
The last step is to convert a Bitmap to a Drawable, as shown in the following code
public class DecodePath<DataType.ResourceType.Transcode> {...Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height,
@NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException {
//1. Call decodeResourec to parse data into an intermediate resource Bitmap
Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
//2. Call back the data after parsing
Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
//3. Convert the resource to the target resource Bitmap to Drawable
returntranscoder.transcode(transformed, options); }... }Copy the code
The ResourceTranscoder is an interface, and since the resolved data is a Bitmap, its implementation class is BitmapDrawableTranscoder. Finally, look at the specific implementation of its Transcode
public class BitmapDrawableTranscoder implements ResourceTranscoder<Bitmap.BitmapDrawable> {
@Nullable
@Override
public Resource<BitmapDrawable> transcode(@NonNull Resource
toTranscode, @NonNull Options options)
{
returnLazyBitmapDrawableResource.obtain(resources, toTranscode); }}Copy the code
Specific we see LazyBitmapDrawableResource. Obtain
public final class LazyBitmapDrawableResource implements Resource<BitmapDrawable>,
Initializable {
private final Resources resources;
private final Resource<Bitmap> bitmapResource;
@Deprecated
public static LazyBitmapDrawableResource obtain(Context context, Bitmap bitmap) {
return
(LazyBitmapDrawableResource)
obtain(
context.getResources(),
BitmapResource.obtain(bitmap, Glide.get(context).getBitmapPool()));
}
@Deprecated
public static LazyBitmapDrawableResource obtain(Resources resources, BitmapPool bitmapPool, Bitmap bitmap) {
return
(LazyBitmapDrawableResource) obtain(resources, BitmapResource.obtain(bitmap, bitmapPool));
}
@Nullable
public static Resource<BitmapDrawable> obtain( @NonNull Resources resources, @Nullable Resource
bitmapResource)
{
if (bitmapResource == null) {
return null;
}
return new LazyBitmapDrawableResource(resources, bitmapResource);
}
private LazyBitmapDrawableResource(@NonNull Resources resources, @NonNull Resource
bitmapResource)
{
this.resources = Preconditions.checkNotNull(resources);
this.bitmapResource = Preconditions.checkNotNull(bitmapResource);
}
@NonNull
@Override
public Class<BitmapDrawable> getResourceClass(a) {
return BitmapDrawable.class;
}
// Get returns a BitmapDrawable object
@NonNull
@Override
public BitmapDrawable get(a) {
return new BitmapDrawable(resources, bitmapResource.get());
}
@Override
public int getSize(a) {
return bitmapResource.getSize();
}
@Override
public void recycle(a) {
bitmapResource.recycle();
}
@Override
public void initialize(a) {
if (bitmapResource instanceofInitializable) { ((Initializable) bitmapResource).initialize(); }}}Copy the code
Also completed the transformation, we resolve to store bitmap to LazyBitmapDrawableResource inside, and then the outside world through the get method can get a BitmapDrawable object.
After parsing, it’s time to display the data. Look at the following code:
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback.Runnable.Comparable<DecodeJob<? > >,Poolable {
// Parse the returned data
private void decodeFromRetrievedData(a) {
Resource<R> resource = null;
try {
//1. Call decodeFrom to parse data; HttpUrlFetcher , InputStream , currentDataSource
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
//2. After parsing, notify
if(resource ! =null) {
notifyEncodeAndRelease(resource, currentDataSource);
} else{ runGenerators(); }}Copy the code
Note 1 has parsed the data, now execute notifyEncodeAndRelease
private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {...// Notify the calling layer that the data is ready
notifyComplete(result, dataSource);
stage = Stage.ENCODE;
try {
// Cache the resource disk
if(deferredEncodeManager.hasResourceToEncode()) { deferredEncodeManager.encode(diskCacheProvider, options); }}finally{... }/ / finish
onEncodeComplete();
}
private void notifyComplete(Resource<R> resource, DataSource dataSource) {
setNotifiedOrThrow();
// In the DecodeJob build, we know that the Callback is a EngineJob
callback.onResourceReady(resource, dataSource);
}
Copy the code
Can see DecodeJob. Mainly in decodeFromRetrievedData made several operations
- Parse the returned resources.
- Get parsed resources and, if local caching is configured, cache them to disk.
- Notify the upper layer that the resource is ready for use.
Let’s look directly at the EngineJob’s onResourceReady callback
@Override
public void onResourceReady(Resource<R> resource, DataSource dataSource) {
synchronized (this) {
this.resource = resource;
this.dataSource = dataSource;
}
notifyCallbacksOfResult();
}
@Synthetic
void notifyCallbacksOfResult(a) { ResourceCallbacksAndExecutors copy; Key localKey; EngineResource<? > localResource;synchronized (this) {
stateVerifier.throwIfRecycled();
if (isCancelled) {
resource.recycle();
release();
return;
} else if (cbs.isEmpty()) {
...
}
engineResource = engineResourceFactory.build(resource, isCacheable);
hasResource = true;
copy = cbs.copy();
incrementPendingCallbacks(copy.size() + 1);
localKey = key;
localResource = engineResource;
}
// The callback to the upper Engine is complete
listener.onEngineJobComplete(this, localKey, localResource);
// Traverse the resource callback to ImageViewTarget
for (final ResourceCallbackAndExecutor entry : copy) {
entry.executor.execute(new CallResourceReady(entry.cb));
}
decrementPendingCallbacks();
}
Copy the code
The onResourceReady callback for EngineJob does two things
- Notify upper management of task completion.
- The ImageViewTarget callback is used to display the data.
We look at the listener. OnEngineJobComplete concrete realization
@SuppressWarnings("unchecked")
@Override
public synchronized void onEngineJobComplete( EngineJob
engineJob, Key key, EngineResource
resource) {
if(resource ! =null) {
resource.setResourceListener(key, this);
// The resource returned by the downstream is added to the active cache
if (resource.isCacheable()) {
activeResources.activate(key, resource);
}
}
jobs.removeIfCurrent(key, engineJob);
}
Copy the code
Finally, call ImageViewTarget. Let’s see what happens:
// Traverse the resource callback to ImageViewTarget
for (final ResourceCallbackAndExecutor entry : copy) {
entry.executor.execute(new CallResourceReady(entry.cb));
}
Copy the code
private class CallResourceReady implements Runnable {
private final ResourceCallback cb;
CallResourceReady(ResourceCallback cb) {
this.cb = cb;
}
@Override
public void run(a) {
synchronized (EngineJob.this) {
if (cbs.contains(cb)) {
...
// Returns the prepared resourcecallCallbackOnResourceReady(cb); removeCallback(cb); } decrementPendingCallbacks(); }}}Copy the code
Through code can see CallResourceReady implement Runnable them, when entry. The executor, execute the thread pool implementation is invoked when the run, finally, we continue with callCallbackOnResourceReady
@Synthetic
synchronized void callCallbackOnResourceReady(ResourceCallback cb) {
try {
// callback to SingleRequest
cb.onResourceReady(engineResource, dataSource);
} catch (Throwable t) {
throw newCallbackException(t); }}Copy the code
Follow the SingleRequest onResourceReady callback implementation
public synchronized void onResourceReady(Resource
resource, DataSource dataSource) {
stateVerifier.throwIfRecycled();
loadStatus = null; . Object received = resource.get();if (received == null| |! transcodeClass.isAssignableFrom(received.getClass())) { releaseResource(resource); . onLoadFailed(exception);return;
}
if(! canSetResource()) { releaseResource(resource); status = Status.COMPLETE;return;
}
// When the resource is ready
onResourceReady((Resource<R>) resource, (R) received, dataSource);
}
private synchronized void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
...
anyListenerHandledUpdatingTarget |=
targetListener != null
&& targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);
if(! anyListenerHandledUpdatingTarget) { Transition<?super R> animation =
animationFactory.build(dataSource, isFirstResource);
// Callback to target ImageViewTarget resource readytarget.onResourceReady(result, animation); }}finally {
isCallingCallbacks = false;
}
// The load succeeded
notifyLoadSuccess();
}
Copy the code
This step calls back the prepared resource to the display layer, as shown in the following code
public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView.Z>
implements Transition.ViewAdapter {...@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
if (transition == null| |! transition.transition(resource,this)) {
setResourceInternal(resource);
} else{ maybeUpdateAnimatable(resource); }}protected abstract void setResource(@Nullable Z resource); . }private void setResourceInternal(@Nullable Z resource) {
// Call the setResource function to display the resourcesetResource(resource); . }Copy the code
At the beginning of the build, we know that the implementation class is BitmapImageViewTarget only when we call asBitmap. In this case, the test does not call this function, so its implementation class is DrawableImageViewTarget.
public class DrawableImageViewTarget extends ImageViewTarget<Drawable> {
public DrawableImageViewTarget(ImageView view) {
super(view);
}
// Public API.
@SuppressWarnings({"unused"."deprecation"})
@Deprecated
public DrawableImageViewTarget(ImageView view, boolean waitForLayout) {
super(view, waitForLayout);
}
@Override
protected void setResource(@Nullable Drawable resource) { view.setImageDrawable(resource); }}Copy the code
Here we see that the abstract class calls setResource, and the subclass implements and calls View.setimagedRawable (Resource); The picture is actually showing up now. Glide. With (activity). Load (http://xxx.png).into(imageView) this process is finished. Here’s a summary of the process.
conclusion
By this Glide. With (activity).load(http://xxx.png).into(imageView) a short code, the whole Glide picture load display process we have been familiar with, and look at the source code can not be a line by line to understand its internal specific implementation, We just have to pick a point, say, with and then we just focus on the implementation of with, and we don’t care about anything else. The following is a simple talk about the whole process of Glide:
In the process of reading, I hope you can point out any questions or mistakes in the description of the article.
Thank you for reading, thank you!
reference
- The official Glide
- Guo Lin Daishen Glide source code analysis
- Glide 4.9 source code analysis
- Glide source code analysis