This article Glide source code based on 4.9, version download address is as follows: Glide 4.9
preface
Glide source code is really complex, so this article only analyzes and posts functions and code related to the image loading process. In addition, this article Glide source code based on 4.9, and 3.x source code or there are differences, but the overall process changes little.
Glide for this powerful Android picture loading open source framework, I believe that we are not unfamiliar with it, anyway, the author’s words, the normal project with the picture loading framework is mostly it, because it is really very convenient and fast, with convenient, but it really shows that its source is so simple? So today want to uncover the mystery of Glide, Glide from the source to analyze the picture loading process.
In most cases, we want to load and display an image in the interface with only one line of code, as follows:
Glide.with(this).load(url).into(imageView);
Copy the code
So we Glide picture loading process of the source analysis can be divided into three:
- with
- load
- into
Let’s play the trilogy together!
One, with
1. The role
- Get the RequestManager object
- An object that performs a series of operations (load, codec, transcode) on an image is pre-created and added to Glide’s registry
- The life cycle for the image will Glide and Appliction/Activity/fragments binding
2. Process
2.1 a sequence diagram
First of all, we know from the use, the first song we call is Glide with method, so let’s have a look at this method first
Glide#with
/** * Application type */
public static RequestManager with(@NonNull Context context) {
//getRetriever returns the singleton object in the RequestManagerRetriever
/ / RequestManagerRetriever get returns RequestManager objects and bind the image loaded life cycle
return getRetriever(context).get(context);
}
/**
* 非Application类型
*/
public static RequestManager with(@NonNull Activity activity) {
// As with the Application type, the RequestManagerRetriever get is called to obtain the RequestManager object
// Note that the parameter passed here is Activity
return getRetriever(activity).get(activity);
}
public static RequestManager with(@NonNull FragmentActivity activity) {
returngetRetriever(activity).get(activity); }...Copy the code
Can be found that with the method is a set of static methods on the Glide class, there are a lot of overloaded methods in the Glide, can be introduced into the Context, the Activity, fragments, etc., and then with the implementation of the inside is very simple, just a code, the return type, you will know its function is doing, Return a RequestManager object. So how exactly to get this object, let’s see!
2.2 Obtaining a RequestManagerRetriever Object
The RequestManagerRetriever object will be returned before you return it. Regardless of the parameter in the With retriever, the getRetriever method will be called and no methods will be overloaded. So the steps to get the RequestManagerRetriever object are the same, so let’s track down exactly how you got it.
private static RequestManagerRetriever getRetriever(@Nullable Context context) {...Call Glide. Get to obtain the Glide object, in which the RequestManagerRetriever object is encapsulated
/ / 2. Through the Glide getRequestManagerRetriever () to obtain RequestManagerRetriever object
return Glide.get(context).getRequestManagerRetriever();
}
public static Glide get(@NonNull Context context) {
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
// Focus oncheckAndInitializeGlide(context); }}}return glide;
}
private static void checkAndInitializeGlide(@NonNull Context context) {
if (isInitializing) {
// This exception will be thrown if two initializations are performed simultaneously
throw new IllegalStateException("You cannot call Glide.get() in registerComponents(),"
+ " use the provided Glide instance instead");
}
isInitializing = true;
// Perform initialization
initializeGlide(context);
isInitializing = false;
}
private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) {...GlideBuilder = GlideBuilder; GlideBuilder = GlideBuilderGlide glide = builder.build(applicationContext); . }Copy the code
First of all, the getRetriever method looks very similar to the code in with, but it does two things:
- Obtain the Glide object through Glide. Get
- Invoke the Glide getRequestManagerRetriever () get to RequestManagerRetriever object
Glide’s GET method is a simple standard singleton implementation. GlideBuilder: GlideBuilder: GlideBuilder: GlideBuilder: GlideBuilder: GlideBuilder: GlideBuilder: GlideBuilder: GlideBuilder
@NonNull
Glide build(@NonNull Context context) {...// Build an execution engine that manages thread pools and caches
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
GlideExecutor.newAnimationExecutor(),
isActiveResourceRetentionAllowed);
}
// A RequestManagerRetriever object is built
RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory);
// Build the Glide object and encapsulate the many thread pools and RequestManagerRetriever objects above
return newGlide( context, engine, memoryCache, bitmapPool, arrayPool, requestManagerRetriever, connectivityMonitorFactory, logLevel, defaultRequestOptions.lock(), defaultTransitionOptions, defaultRequestListeners, isLoggingRequestOriginsEnabled); }}public class Glide implements ComponentCallbacks2 {
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) {
...
// Assign the RequestManagerRetriever object to a member variable
this.requestManagerRetriever = requestManagerRetriever; ./ / decoder
StreamBitmapDecoder streamBitmapDecoder = new StreamBitmapDecoder(downsampler, arrayPool);
// Add to the registry
registry
.append(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder)
....
/* Models */
// Focus on InputStreamRewinder
.register(new InputStreamRewinder.Factory(arrayPool))
....
/ / focus on StringLoader. StreamFactory ()
.append(String.class, InputStream.class, new StringLoader.StreamFactory())
....
// Focus on httpuriloader.factory ()
.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
....
// Focus on HttpGlideUrlLoader
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
....
/* Transcoders */
// Focus on BitmapDrawableTranscoder
.register(
Bitmap.class,
BitmapDrawable.class,
newBitmapDrawableTranscoder(resources)) ..... }}Copy the code
Glide object creation from the above can be seen to do a lot of things, is also extremely complex, in general its responsibilities are as follows:
- Create the RequestManagerRetriever object
- Create an execution Engine object that manages thread pools and caches (needed below)
- Build Registry and register many codecs (needed below)
In The registry, the above code lists only a few codecs that will be used below, but there are many more things in the registry. Let’s reconfirm that our goal is to get the RequestManagerRetriever object. In the code above, we have created a New RequestManagerRetriever object and assigned it to a Member variable of the Glide. Can then be obtained by Glide getRequestManagerRetriever to this RequestManagerRetriever object.
2.3 Obtaining a RequestManager Object
Let’s revisit one of the with methods
RequestManagerRetriever#with
public static RequestManager with(@NonNull Context context) {
return getRetriever(context).get(context);
}
Copy the code
Once you have obtained the RequestManagerRetriever object, you need to obtain the RequestManager object using the Get method in the RequestManagerRetriever retriever. In the RequestManagerRetriever class, get, like Glide’s with, also has a number of overloaded methods. Overloaded methods handle different parameters differently and can be divided into two types of parameters based on the type of parameters involved:
- The Application type
- Non-application type (Activity/Fragment)
2.3.1 Obtaining the RequestManager object of Application Type
RequestManagerRetriever#get
/ / Application type
public RequestManager get(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} else if(Util.isOnMainThread() && ! (contextinstanceof Application)) {
// If context is in the main thread and not of Application type
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper) {
returnget(((ContextWrapper) context).getBaseContext()); }}// Get a RequestManager object if not in the main thread or for a call to getApplicationManager of Application type
return getApplicationManager(context);
}
Copy the code
You can see that get with context is handled in two ways:
- If you’re on the main thread and the context is not of type Application you call a get method that is not of type Application
- Call getApplicationManager to get the RequestManager object if it is not on the main thread or of Application type
Here we are looking at the Application type, so look directly at the getApplicationManager method
RequestManagerRetriever#getApplicationManager
private RequestManager getApplicationManager(@NonNull Context context) {
if (applicationManager == null) {
synchronized (this) {
if (applicationManager == null) {
// The get method is to get the singleton object of Glide,
// It is not necessary to create a singleton object for Glide because the singleton object for Glide is already created
Glide glide = Glide.get(context.getApplicationContext());
applicationManager =
factory.build(
glide,
new ApplicationLifecycle(),
newEmptyRequestManagerTreeNode(), context.getApplicationContext()); }}}return applicationManager;
}
public interface RequestManagerFactory {
@NonNull
RequestManager build( @NonNull Glide glide, @NonNull Lifecycle lifecycle, @NonNull RequestManagerTreeNode requestManagerTreeNode, @NonNull Context context);
}
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) {
return newRequestManager(glide, lifecycle, requestManagerTreeNode, context); }}; }Copy the code
Glide get is a singleton implementation, so get the Glide object directly and new an ApplicationLifecycle. It then passes Glide and ApplicationLifecycle objects, etc., and creates a RequestManager object to bind to the Application lifecycle.
So why is Glide directly tied to the Application lifecycle?
This is because the life cycle of the Application object is the life cycle of the App, so the life cycle of Glide load image is directly tied to the life cycle of the Application, no special processing is required.
2.3.2 A Non-Application RequestManager object is Obtained
We will only use the Activity type as the parameter here, because other non-application types are basically similar to activities.
RequestManagerRetriever#get
/**
* 非Application类型
*/
public RequestManager get(@NonNull FragmentActivity activity) {
if (Util.isOnBackgroundThread()) {
// Call Aplication GET in child threads
return get(activity.getApplicationContext());
} else {
// Determine whether the Activity is destroyed
assertNotDestroyed(activity);
// Get the FragmentManager object
FragmentManager fm = activity.getSupportFragmentManager();
// Return RequestManager with a call to supportFragmentGet
return supportFragmentGet(
activity, fm, /*parentHint=*/ null, isActivityVisible(activity)); }}Copy the code
For non-application types, the first step is to determine whether the request is in the main thread or in a child thread. If it is in a child thread, the get method of the Application type is called, which is understandable because the Glide life cycle in the child thread should be the same as the Application life cycle.
If it is in the main thread, the supportFragmentGet method is called to bind to the Activity’s life cycle.
RequestManagerRetriever#supportFragmentGet
private RequestManager supportFragmentGet(
@NonNull Context context,
@NonNull FragmentManager fm,
@Nullable Fragment parentHint,
boolean isParentVisible) {
/ / get SupportRequestManagerFragment
SupportRequestManagerFragment current =
getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
// Implement creation, add Fragment
RequestManager requestManager = current.getRequestManager();
// Initialize the requestManager if it is loaded for the first time
if (requestManager == null) {
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
/ / set to SupportRequestManagerFragment
current.setRequestManager(requestManager);
}
return requestManager;
}
Copy the code
How does supportFragmentGet bind to an Activity? The process is as follows:
- Create a hidden Fragment
- Add a hidden Fragment to the current Activity
- Create a RequestManager object and bind the RequestManager to the hidden Fragment’s life cycle
Why, you might ask, can you bind an Activity to its hidden Fragment life cycle? This is because the Fragment life cycle is synchronized with the Activity, so the hidden Fragment bound to it can listen to the Activity life cycle, so that it can synchronize the life cycle of Glide loading image with the Activity. It also avoids memory leaks when Glide holds an instance of an Activity.
3. Summary
This concludes the work of WITH, and lets summarize the main work of with
- Building a Glide Object
- Create an execution Engine object that manages thread pools and caches
- Registry is built and many codecs are registered
- Build the RequestManagerRetriever object
- Build the RequestManager object
- If the parameter in the child thread loading picture or with is of Application type, the Glide picture loading life cycle is bound to the Application life cycle
- Otherwise, Glide image load life cycle is bound to the Activity or Fragment life cycle by adding a hidden Fragment to the current Activity/Fragment and binding the hidden Fragment life cycle.
Second, the load
1. The role
Create an image load request with a Drawable target, passing in the resources to be loaded (String,URL,URI, etc.)
2. Process
2.1 a sequence diagram
2.2 Creating a RequestBuilder Object
From the analysis of with, we know that with eventually returns a RequestManager object, so the second part begins with a Load method of the RequestManager.
RequestManager#load
public RequestBuilder<Drawable> load(@Nullable String string) {
//1. AsDrawable creates an image loading request whose target is Drawable
//2. Call load to pass in the loaded resource
return asDrawable().load(string);
}
public RequestBuilder<Drawable> load(@Nullable Uri uri) {
return asDrawable().load(uri);
}
public RequestBuilder<Drawable> load(@Nullable URL url) {
returnasDrawable().load(url); }...Copy the code
Load can also be overloaded in RequestManager, but we will only examine the most common load parameter for loading images, load(String URL).
In the Load method of the RequestManager, asDrawable is called first, so let’s look at asDrawable
public RequestBuilder<Drawable> asDrawable(a) {
return as(Drawable.class);
}
public <ResourceType> RequestBuilder<ResourceType> as( @NonNull Class
resourceClass)
{
return new RequestBuilder<>(glide, this, resourceClass, context);
}
Copy the code
The above code is simple, creating a picture load RequestBuilder whose target is Drawable.
2.3 Importing the IMAGE URL
Since asDrawable returns the RequestBuilder object, the load method of RequesBuilder will be called next
RequesBuilder#load
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
// Assign data to static member variables of the RequestBuilder
this.model = model;
isModelSet = true;
return this;
}
Copy the code
The code above is easy to understand. Load calls the loadGeneric method, which takes the data and assigns the Model of type String to the static member variable of the RequestBuilder.
3. Summary
Load is probably the simplest song in the trilogy, with simple code and easy to understand. This part is also one of the differences between 3.x and 4.9. In the 3.x source code, load should also perform a task, that is, pre-create an object that performs a series of operations on the image (load, codec, transcode). Through the above analysis of with, we know that in the source code of 4.9, this work has been given to with, so load compared with the other two, its work is relatively simple.
Three, into
!!!!!!!!! High warning, into source code analysis will take a long, long time
1. The role
In the child thread, the network requests the image to be parsed and returned to the main thread to display the image
2. Process
The following source code is based on the load parameter String, do not take memory cache, disk cache case
Load returns a RequestBuilder, so the entry to into is a RequestBuilder
RequestBuilder#into
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {...// Return the ViewTarget object
return into(
//glideContext Is of the glideContext type
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions,
// A thread pool with bound main thread handlers
Executors.mainThreadExecutor());
}
Copy the code
In the above code, we know that the parameters to be passed are obtained before calling into, so we focus on the first and fourth parameters.
2.1 Creating a ViewTarget Object
Let’s first analyze GlideContext’s buildImageViewTarget method.
GlideContext#buildImageViewTarget
public <X> ViewTarget<ImageView, X> buildImageViewTarget( @NonNull ImageView imageView, @NonNull Class
transcodeClass)
{
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
Copy the code
The transcodeClass passed in is actually the Drawable. Class we passed in the asDrawable we analyzed in part 2, and we continue to call the buildTarget method of ImageViewTargetFactory.
ImageViewTargetFactory#buildTarget
public <Z> ViewTarget<ImageView, Z> buildTarget(@NonNull ImageView view, @NonNull Class
clazz)
{
if (Bitmap.class.equals(clazz)) {
// If the asBitmap method is called
return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
/ / otherwise
return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException(
"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)"); }}Copy the code
Since we are not calling the asBitmap method and are passing in a Drawable, the ViewTarget object returned should be a DrawableImageViewTarget, which we will use when displaying images.
!!!!!!!!! Note: All targets that appear in the code below are DrawableImageViewTarget objects unless otherwise specified.
2.2 create MAIN_THREAD_EXECUTOR
Let’s go back to the into method.
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {...// Return the ViewTarget object
return into(
BuildImageViewTarget Creates a ViewTarget object
//transcodeClass calls asBitmap and returns BitmapImageViewTarget
// Otherwise transcodeClass is of type Drawable and returns DrawableImageViewTarget
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions,
// A thread pool with bound main thread handlers
Executors.mainThreadExecutor());
}
Copy the code
Let’s move on to the fourth parameter
Executors#mainThreadExecutor
private static final Executor MAIN_THREAD_EXECUTOR =
new Executor() {
// Bind the main thread Looper
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(@NonNull Runnable command) { handler.post(command); }};public static Executor mainThreadExecutor(a) {
return MAIN_THREAD_EXECUTOR;
}
Copy the code
MainThreadExecutor returns MAIN_THREAD_EXECUTOR, which declares a Handler bound to the main thread Looper. The thread pool’s execute method then executes handler’s POST method, which is equivalent to executing Command’s Run method on the main thread. (This is a thread pool, because it will be reanalyzed when the main thread is displayed in the main thread.)
After analyzing the two arguments to into, let’s look at the overloaded into method
RequestBuilder#into
private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener
targetListener, BaseRequestOptions
options, Executor callbackExecutor)
{
Preconditions.checkNotNull(target);
if(! isModelSet) {throw new IllegalArgumentException("You must call #load() before calling #into()");
}
// Build the Requset object and issue a request to load the image
// Notice that the fourth argument is passed in the thread pool containing the Handler bound to the main thread
Request request = buildRequest(target, targetListener, options, callbackExecutor);
// Release the target object's existing request before starting
Request previous = target.getRequest();
if(request.isEquivalentTo(previous) && ! isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) { request.recycle();if(! Preconditions.checkNotNull(previous).isRunning()) { previous.begin(); }return target;
}
requestManager.clear(target);
// Set the request to target
target.setRequest(request);
// Distribute and execute network Request requests, in which case the requestManager is the Request Manager
requestManager.track(target, request);
return target;
}
Copy the code
We can find that in this method, in fact, there are two main tasks: one is to construct the Request of the network Request, and the other is to execute the Request object of the network Request. Next, we will analyze these two tasks respectively.
2.3 Constructing a Network Request Object Request
RequestBuilder#buildRequest
private Request buildRequest( Target
target, @Nullable RequestListener
targetListener, BaseRequestOptions
requestOptions, Executor callbackExecutor)
{
// Focus on the buildRequestRecursive method
return buildRequestRecursive(
target,
targetListener,
/*parentCoordinator=*/ null,
transitionOptions,
requestOptions.getPriority(),
requestOptions.getOverrideWidth(),
requestOptions.getOverrideHeight(),
requestOptions,
callbackExecutor);
}
private Request buildRequestRecursive(
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
intoverrideHeight, BaseRequestOptions<? > requestOptions, Executor callbackExecutor) {.../ / focus on buildThumbnailRequestRecursive method
Request mainRequest =
buildThumbnailRequestRecursive(
target,
targetListener,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
requestOptions,
callbackExecutor);
if (errorRequestCoordinator == null) {
returnmainRequest; }... }private Request buildThumbnailRequestRecursive(
Target<TranscodeType> target,
RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
intoverrideHeight, BaseRequestOptions<? > requestOptions, Executor callbackExecutor) {...// Focus on key codeRequest fullRequest = obtainRequest( target, targetListener, requestOptions, coordinator, transitionOptions, priority, overrideWidth, overrideHeight, callbackExecutor); . }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) {
// The Obtain method of SingleRequest is called to assemble all the API parameters called in load into the Request object
// The callbackExecutor is a thread pool with a bound main thread Handler
return SingleRequest.obtain(
context,
glideContext,
model,
transcodeClass,
requestOptions,
overrideWidth,
overrideHeight,
priority,
target,
targetListener,
requestListeners,
requestCoordinator,
glideContext.getEngine(),
transitionOptions.getTransitionFactory(),
callbackExecutor);
}
Copy the code
Step by step, the Obtain method of SingleRequest will eventually be executed, so let’s continue with this method
SingleRequest# obtain method
public static <R> SingleRequest<R> obtain( Context context, GlideContext glideContext, Object model, Class<R> transcodeClass, BaseRequestOptions<? > requestOptions,int overrideWidth,
int overrideHeight,
Priority priority,
Target<R> target,
RequestListener<R> targetListener,
@Nullable List<RequestListener<R>> requestListeners,
RequestCoordinator requestCoordinator,
Engine engine,
TransitionFactory<? super R> animationFactory,
Executor callbackExecutor) {
@SuppressWarnings("unchecked") SingleRequest<R> request =
(SingleRequest<R>) POOL.acquire();
if (request == null) {
// Create SingleRequest object
request = new SingleRequest<>();
}
// Assign the API arguments passed in load to the SingleRequest member variable
// The last argument is the thread pool for the master thread
request.init(
context,
glideContext,
model,
transcodeClass,
requestOptions,
overrideWidth,
overrideHeight,
priority,
target,
targetListener,
requestListeners,
requestCoordinator,
engine,
animationFactory,
callbackExecutor);
return request;
}
// Assign to a member variable
private synchronized void init( Context context, GlideContext glideContext, Object model, Class<R> transcodeClass, BaseRequestOptions<? > requestOptions,int overrideWidth,
int overrideHeight,
Priority priority,
Target<R> target,
RequestListener<R> targetListener,
@Nullable List<RequestListener<R>> requestListeners,
RequestCoordinator requestCoordinator,
Engine engine,
TransitionFactory<? super R> animationFactory,
Executor callbackExecutor) {
this.context = context;
this.glideContext = glideContext;
this.model = model;
this.transcodeClass = transcodeClass;
this.requestOptions = requestOptions;
this.overrideWidth = overrideWidth;
this.overrideHeight = overrideHeight;
this.priority = priority;
this.target = target;
this.targetListener = targetListener;
this.requestListeners = requestListeners;
this.requestCoordinator = requestCoordinator;
this.engine = engine;
this.animationFactory = animationFactory;
this.callbackExecutor = callbackExecutor;
status = Status.PENDING;
if (requestOrigin == null && glideContext.isLoggingRequestOriginsEnabled()) {
requestOrigin = new RuntimeException("Glide request origin trace"); }}Copy the code
The Obtain method essentially creates the SingleRequest object and then calls the init method to assign values to the member variables, so the network request object is the SingleRequest object.
2.4 Executing the Network Request object Request
Let’s go back to the into method
private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener
targetListener, BaseRequestOptions
options, Executor callbackExecutor)
{
// Build the Requset object and issue a request to load the image
// Finally build the SingleRequest objectRequest request = buildRequest(target, targetListener, options, callbackExecutor); .// Distribute and execute network Request requests, in which case the requestManager is the requestManager object
//target is the DrawableImageViewTarget created above (refer back to 2.1 if you forget)
// Request is the completed singleRequest object
requestManager.track(target, request);
return target;
}
Copy the code
At this point we have successfully built the SingleRequest object and then called RequestManager’s Track method to distribute and execute the request
Against 2.4.1 before loading
RequestManager#track
synchronized void track(@NonNull Target
target, @NonNull Request request) {
targetTracker.track(target);
// Execute the network request
requestTracker.runRequest(request);
}
Copy the code
RequestTracker#runRequest
public void runRequest(@NonNull Request request) {
// Manage requests by adding each submitted request to a set
requests.add(request);
// Check whether Glide is currently paused
if(! isPaused) {// If not paused, the begin method of SingleRequest is called to execute the request
request.begin();
} else {
request.clear();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
// If the request is paused, the current request is added to the queue to wait for the suspension to be completedpendingRequests.add(request); }}Copy the code
Before loading the picture, that is, before starting the network request, we need to add each request to the set to manage the request, and also need to judge the current state of Glide, because we are now analyzing the picture load process, obviously this is not a suspended state, so we will execute the begin method of request. Since we have analyzed the network request object as SingleRequest above, the request here is the SingleRequest object.
2.4.2 loads
Next, let’s look at the begin method of SingleRequest
SingleRequest#begin
public synchronized void begin(a) {...//model is the image URL passed to load
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
// If the incoming URL address is empty, onLoadFailed is called
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
// Focus on
onSizeReady(overrideWidth, overrideHeight);
} else {
 // getSize calculates the width and height and executes the onSizeReady method
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
Loading placeholder is used to replace the final image display before the image request is successfultarget.onLoadStarted(getPlaceholderDrawable()); }... }Copy the code
1. onLoadFailed
We can see from the above code that the onLoadFailed method is called when model is null, i.e. the image address passed in by load is empty
SingleRequest#onLoadFailed
private synchronized void onLoadFailed(GlideException e, int maxLogLevel) {... loadStatus =null;
status = Status.FAILED;
isCallingCallbacks = true;
try {
//TODO: what if this is a thumbnail request?
boolean anyListenerHandledUpdatingTarget = false;
if(requestListeners ! =null) {
for(RequestListener<R> listener : requestListeners) { anyListenerHandledUpdatingTarget |= listener.onLoadFailed(e, model, target, isFirstReadyResource()); } } anyListenerHandledUpdatingTarget |= targetListener ! =null
&& targetListener.onLoadFailed(e, model, target, isFirstReadyResource());
if(! anyListenerHandledUpdatingTarget) {// Focus on this methodsetErrorPlaceholder(); }}finally {
isCallingCallbacks = false;
}
notifyLoadFailed();
}
private synchronized void setErrorPlaceholder(a) {
if(! canNotifyStatusChanged()) {return;
}
Drawable error = null;
// Get the image of fallback
if (model == null) {
error = getFallbackDrawable();
}
// If the fallback graph is not set, the error graph is obtained
if (error == null) {
error = getErrorDrawable();
}
// If there is no error graph, get another loading placeholder map
if (error == null) {
error = getPlaceholderDrawable();
}
/ / target for DrawableImageViewTarget
target.onLoadFailed(error);
}
Copy the code
In the onLoadFailed method we just need to focus on the setErrorPlaceholder method, and the main logic in setErrorPlaceholder is to get the picture that you need to show when you get an error, Press Fallback > Error > Loading’s priority to get the picture at the time of the error and then call the onLoadFailed method of DrawableImageViewTarget. By looking at the DrawableImageViewTarget, we can see that there is no onLoadFailed method in this class, so we naturally look for the parent class ImageViewTarget to see if this method exists.
ImageViewTarget#onloadFailed
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
setResourceInternal(null);
// Call setDrawable to display the image
setDrawable(errorDrawable);
}
public void setDrawable(Drawable drawable) {
// View is an ImageView that displays the image
view.setImageDrawable(drawable);
}
Copy the code
It can be seen that the principle of Glide display wrong picture is: when the url of incoming picture is null, placeholder map of fallback/error/loading will be used instead.
2. onLoadStarted
After analyzing onLoadFailed, we go back to SingleRequest’s begin method. It should be onSizeReady next, but since this method is complicated and onLoadStarted is similar to onLoadStarted, So let’s analyze onLoadStarted and leave onSizeReady to the end.
SingleRequest#begin code related to onLoadStarted
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
Loading placeholder is used to replace the final image display before the image request is successful
target.onLoadStarted(getPlaceholderDrawable());
}
Copy the code
The onLoadStarted method of DrawableImageViewTarget is executed while the image is being requested or waiting for the onSizeReady method to be executed. The onLoadFailed method is in the DrawableImageViewTarget parent class ImageViewTarget, so onLoadStarted is in the ImageViewTarget parent class.
ImageViewTarget#onLoadStarted
public void onLoadStarted(@Nullable Drawable placeholder) {
super.onLoadStarted(placeholder);
setResourceInternal(null);
// Loading placeholder is used to replace the final image display before the image request starts
setDrawable(placeholder);
}
public void setDrawable(Drawable drawable) {
// Display the image
view.setImageDrawable(drawable);
}
Copy the code
Then the loading placeholder is displayed, that is, the loading placeholder is used to replace the final image display before the image request is successful. This is a feature we use a lot.
3. onSizeReady
Now that we’re ready to analyze onSizeReady, let’s post the code
SingleRequest#begin code related to onSizeReady
// There are two ways to load images:
Override () API is used to specify a fixed width and height for the image
/ / 2. No use
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
// In the first case, onSizeReady is called if the width and height are specified
onSizeReady(overrideWidth, overrideHeight);
} else {
 // getSize calculates the width and height and executes the onSizeReady method
//(Trace up from DrawableImageViewTarget and find this method in ViewTarget)
target.getSize(this);
}
Copy the code
We’ll just analyze onSizeReady for this, because the getSize method will eventually call onSizeReady as well.
SingleRequest#onSizeReady
@Override
public synchronized void onSizeReady(int width, int height) {... status = Status.RUNNING; loadStatus =// Focus on calling Engine's load build task
// Focus on the penultimate argument, passing itself SingleRequest, which will be used during the callback
// Focus on the penultimate argument, passing the thread pool callbackExectuter with the Handler bound to the main thread
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
The onSizeReady implementation is passed to the load method of Engine, which is the execution Engine used by Glide in The first track with. The last two parameters passed to load need special attention because they will be used later in the analysis.
Build tasks
Engine#load
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) {...// Find the task corresponding to the key from the cacheEngineJob<? > current = jobs.get(key, onlyRetrieveFromCache);if(current ! =null) {
// If you go to this point, the task is already being executed, no need to build again
// You can go back and look at this after analyzing it from the back
current.addCallback(cb, callbackExecutor);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
// This is a new assignment
// Create EngineJob object to start the thread (asynchronously loading images)
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
// Create DecodeJob object to decode images
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
// Add to the task cache
jobs.put(key, engineJob);
// For now
// This method is reanalyzed when the data is retrieved for the photo display callback
engineJob.addCallback(cb, callbackExecutor);
// Execute the taskengineJob.start(decodeJob); .return new LoadStatus(cb, engineJob);
}
Copy the code
Combined with the above code and comments, we can see what Engine.Load does:
- Create EngineJob object to start the thread
- Create a DeodeJob object that decodes the photo
- Add callback objects and thread pools
- Perform a task
Perform a task
EngineJob#start
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
// Get the thread pool
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
// Execute DecodeJob's run method
executor.execute(decodeJob);
}
Copy the code
The execute method of the thread pool is called, so the run method of the DecodeJob is executed next
DecodeJob#run
public void run(a) {...try {
if (isCancelled) {
notifyFailed();
return;
}
// Focus, call runWrappedrunWrapped(); }... }private void runWrapped(a) {
switch (runReason) {
case INITIALIZE:
// Get the task scenario
stage = getNextStage(Stage.INITIALIZE);
// Get the executor of the scene
currentGenerator = getNextGenerator();
// The executor performs the task
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: "+ runReason); }}// Get the task scenario
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
Return stage.resource_cache if the configured cache policy allows reading from the resource cache
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
Stage.data_cache is returned if the configured cache policy allows reading from the source data cache
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
// If only data can be read from the cache, FINISH, otherwise return stage.source to load the new resource
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: "+ current); }}// Get the executor of the scene
private DataFetcherGenerator getNextGenerator(a) {
switch (stage) {
case RESOURCE_CACHE:
// Executor of the resource disk cache
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
// Executor of the source data disk cache
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
// No cache, the executor of the source of the data
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: "+ stage); }}private void runGenerators(a) {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
/ / call DataFetcherGenerator. StartNext () to perform the requested action
// DataFetcherGenerator should be SourceGenerator
while(! isCancelled && currentGenerator ! =null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return; }}... }Copy the code
How to carry out the mission? Basically, it can be divided into three steps:
- Obtaining the Task Scenario
- Gets the actor of the task scenario
- The executor performs the task
There is a one-to-one mapping between the scenario and the executor. Since we are analyzing the first loading image and no caching policy is configured, the corresponding task scenario is no caching, and the corresponding executor is the SourceGenerator object. So the startNext method of SourceGenerator is called when the task is executed
SourceGenerator#startNext
public boolean startNext(a) {...boolean started = false;
while(! started && hasNextModelLoader()) {// Get a data loader from DecodeHelper's data loader collection
loadData = helper.getLoadData().get(loadDataListIndex++);
if(loadData ! =null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
// Use the fetcher loader to perform data loading
// This fetcher is an HttpUrlFetcher object
loadData.fetcher.loadData(helper.getPriority(), this); }}return started;
}
Copy the code
Get the data loader
How do you get a collection of data loaders
DecodeHelper#getLoadData
List<LoadData<? >> getLoadData() {if(! isLoadDataSet) { isLoadDataSet =true;
loadData.clear();
// Get modelLoaders from the Glide registerList<ModelLoader<Object, ? >> modelLoaders = glideContext.getRegistry().getModelLoaders(model);/ / traverse modelLoaders
for (int i = 0, size = modelLoaders.size(); i < size; i++) {
// One of the implementation classes is StringLoaderModelLoader<Object, ? > modelLoader = modelLoaders.get(i);LoadData is constructed with StringLoader
// The HttpGlideUrlLoader buildLoadData method is finally executed after Glide registry analysis
// The final loadData encapsulates the HttpUrlFetcher objectLoadData<? > current = modelLoader.buildLoadData(model, width, height, options);if(current ! =null) {
// Add to the loadData collectionloadData.add(current); }}}// Returns a loadData collection containing HttpUrlFetcher objects
return loadData;
}
Copy the code
Let’s break down the method step by step, first getting the modelLoaders from Glide Registered Registry, and since we use String as our entire example, the model here will be of type String.
!!!!!!!!! Note: The static factory class of ModelLoaderFactory implemented by ModelLoader is registered in the Registry. When the getModelLoaders of Registry is called, the build method in the factory class will be called. The process will not be posted here. Now we just need to know that the build method of the corresponding factory class in the registry is called when the getModelLoaders method is called. Now we need to go back to the Glide build registry and look at the static factory classes for ModelLoader when Model was String. Here are just a few:
Glide# Glide constructor
registry
/ / focus on StringLoader. StreamFactory ()
.append(String.class, InputStream.class, new StringLoader.StreamFactory())
.append(String.class, ParcelFileDescriptor.class, new StringLoader.FileDescriptorFactory())
.append(
String.class, AssetFileDescriptor.class, new StringLoader.AssetFileDescriptorFactory())
Copy the code
. Here we use StringLoader StreamFactory for example, by calling getModelLoaders method, so does StringLoader. StreamFactory build method
StringLoader.StreamFactory()
public static class StreamFactory implements ModelLoaderFactory<String.InputStream> {
@NonNull
@Override
public ModelLoader<String, InputStream> build( @NonNull MultiModelLoaderFactory multiFactory) {
// It is known from the Models registry of Glide's Registry
// Httpuriloader.factory ()
// The final argument returns an HttpGlideUrlLoader object
return newStringLoader<>(multiFactory.build(Uri.class, InputStream.class)); }... }Copy the code
From the build method, we build the StringLoader object, but the argument calls another MultiModelLoaderFactory, so we need to look at the Glide register, Then find the MultiModelLoaderFactory object built with uri. class, inputStream. class
Glide#Glide constructor
registry
// Focus on httpuriloader.factory ()
.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
Copy the code
The MultiModelLoaderFactory object will be of type Httpuriloader.factory (), so we need to look at the build method as well
HttpUriLoader.Factory#build
public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {
// See the Models registry in Glide Registry
/ / this time multiFactory for HttpGlideUrlLoader. Factory ()
return new HttpUriLoader(multiFactory.build(GlideUrl.class, InputStream.class));
}
Copy the code
Continue with the same steps as above. Go to Glide’s registry and find the MultiModelLoaderFactory object with glideurl.class, inputStream.class
registry
// Focus on HttpGlideUrlLoader
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
Copy the code
Look again at HttpGlideUrlLoader. The build method of the Factory
HttpGlideUrlLoader.Factory#build
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
// The HttpGlideUrlLoader object is returned
return new HttpGlideUrlLoader(modelCache);
}
Copy the code
The build method here returns the HttpGlideUrlLoader type, so the parameters in the StringLoader object will eventually be of the HttpGlideUrlLoader type. So let’s look at the implementation of the StringLoader constructor.
StringLoader# StringLoader constructor
public StringLoader(ModelLoader<Uri, Data> uriLoader) {
// The uriLoader is an HttpGlideUrlLoader object assigned to a static member variable
this.uriLoader = uriLoader;
}
Copy the code
The builder simply assigns a value to a member variable, in which case the uriLoader is the HttpGlideUrlLoader object. This is what getModelLoaders does. We continue to analyze the getLoadData method of DecodeHelper, which iterates through each modelLoader when it gets a String of modelLoaders. We then call modelLoader’s buildLoadData to construct a loadData object. Here we directly use StringLoader as an example. Let’s look at the implementation of buildLoadData for StringLoader
StringLoader#buildLoadData
public LoadData<Data> buildLoadData(@NonNull String model, int width, int height,
@NonNull Options options) {
Uri uri = parseUri(model);
if (uri == null| |! uriLoader.handles(uri)) {return null;
}
// uriLoader is the HttpGlideUrlLoader object
return uriLoader.buildLoadData(uri, width, height, options);
}
Copy the code
HttpGlideUrlLoader: HttpGlideUrlLoader: HttpGlideUrlLoader: HttpGlideUrlLoader: HttpGlideUrlLoader: HttpGlideUrlLoader
HttpGlideUrlLoader
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 = modelCache.get(model, 0.0);
if (url == null) {
modelCache.put(model, 0.0, model); url = model; }}int timeout = options.get(TIMEOUT);
// Creates a LoadData object and encapsulates an HttpUrlFetcher object
return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
}
Copy the code
We don’t need to pay too much attention to the rest of the code. We just need to pay attention to the return value. We can see that the LoadData object that is returned is the LoadData object that encapsulates HttpUrlFetcher. Let’s return to the startNext method of SourceGenerator.
SourceGenerator#startNext
public boolean startNext(a) {...boolean started = false;
while(! started && hasNextModelLoader()) {// The final object is the LoadData object that encapsulates HttpUrlFetcher
loadData = helper.getLoadData().get(loadDataListIndex++);
if(loadData ! =null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
// Use the fetcher loader to perform data loading
// This fetcher is an HttpUrlFetcher object
loadData.fetcher.loadData(helper.getPriority(), this); }}return started;
}
Copy the code
LoadData is a loadData object that encapsulates HttpUrlFetcher, so loading data is actually calling the loadData method of HttpUrlFetcher.
Perform data loading
HttpUrlFetcher#loadData
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
// Get the input stream of the network image
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0.null, glideUrl.getHeaders());
// Call inputStream back, callback to DataCallbackcallback.onDataReady(result); }... }// Network request code, using HttpURLConnection for network request
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
Map<String, String> headers) throws IOException {...// Static factory creates an HttpUrlConnection object
urlConnection = connectionFactory.build(url);
for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
}
// Set the connection timeout to 2500ms
urlConnection.setConnectTimeout(timeout);
// Set the read timeout to 2500ms
urlConnection.setReadTimeout(timeout);
// Do not use HTTP caching
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
// Stop the urlConnection instance of HttpUrlConnection from following redirects so that
// redirects will be handled by recursive calls to this method, loadDataWithRedirects.
urlConnection.setInstanceFollowRedirects(false);
// Connect explicitly to avoid errors in decoders if connection fails.
urlConnection.connect();
// Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
stream = urlConnection.getInputStream();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (isHttpOk(statusCode)) {
// The request succeeded
returngetStreamForSuccessfulRequest(urlConnection); }... }private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)
throws IOException {
if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
int contentLength = urlConnection.getContentLength();
stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding());
}
stream = urlConnection.getInputStream();
}
// Finally returns an InputStream of the image, and has not yet started reading data
return stream;
}
Copy the code
As you can see, there are two tasks to perform data loading. The first one is to fetch the InputStream of the data, which takes an HttpURLConnection for the network request. The last one is to fetch the InputStream object of the data.
Return the data
Once you get the input stream, you need to return the input stream. How do you return it?
callback.onDataReady(result);
Copy the code
You can see that a callback method is used to call back and forth the input stream of data. Callbak is a DataCallback object. The next step is to find a class that implements the DataCallback interface. The loadData method is called by the startNext method in SourceGenerator, so our preferred target is the SourceGenerator class
SourceGenerator#onDataReady
class SourceGenerator implements DataFetcherGenerator.DataFetcher.DataCallback<Object>,
DataFetcherGenerator.FetcherReadyCallback {...public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if(data ! =null&& diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) { dataToCache = data; . }else {
// Call the onDataFetcherReady method of FetcherReadyCallback to call back datacb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher, loadData.fetcher.getDataSource(), originalKey); }}}Copy the code
Resourceful as we are! The SourceGenerator class implements DataFetcher.DataCallback, and the onDataReady method is found in the SourceGenerator class. The onDataFetcherReady method of FetcherReadyCallback is called, so we look back and ask ourselves: in which class did SourceGenerator’s startNext method get called? You will see that the startNext method is called in the run method of the DecodeJob, and immediately see if the DecodeJob implements onDataFetcherReady!
DecodeJob#onDataFetcherReady
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback.Runnable.Comparable<DecodeJob<? > >,Poolable {...public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher
fetcher, DataSource dataSource, Key attemptedKey) {...if(Thread.currentThread() ! = currentThread) { runReason = RunReason.DECODE_DATA; callback.reschedule(this);
} else {
GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
try {
// Parse the obtained data
decodeFromRetrievedData();
} finally{ GlideTrace.endSection(); }}}private void decodeFromRetrievedData(a) {...try {
// Get the parse
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
if(resource ! =null) {
// Notifies the external world that the resource is successfully obtained
notifyEncodeAndRelease(resource, currentDataSource);
} else{ runGenerators(); }}}Copy the code
A: wow! Super god! Sure enough! The onDataFetcherReady method does two main things:
- Parse the obtained data
- Back to image resources
Let’s see how we parse the data first, okay
2.5 Parsing Data
DecodeJob
private <Data> Resource<R> decodeFromData(DataFetcher
fetcher, Data data, DataSource dataSource) throws GlideException {
try{...// Focus on decodeFromFetcher methods
Resource<R> result = decodeFromFetcher(data, dataSource);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Decoded result " + result, startTime);
}
returnresult; }... }private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
throws GlideException {
// Get the parser LoadPath for the current data class, where data is an InputStream objectLoadPath<Data, ? , R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());// Data is parsed through a parser
return runLoadPath(data, dataSource, path);
}
private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource, LoadPath
path)
,> throws GlideException {
Options options = getOptionsWithHardwareConfig(dataSource);
// Data is InputStream, rewinder is InputStreamRewinder
DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data);
try {
// Move data parsing to the loadpath. load method
return path.load(
rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
} finally{ rewinder.cleanup(); }}Copy the code
Retrieving the Rewinder, like retrieving the modelLoaders, requires revisiting the Glide build registry, which is not detailed here because data is an InputStream object. So rewinder is an InputStreamRewinder object, and then the load method of LoadPath is called to implement parsing the data
LoadPath
public Resource<Transcode> load(DataRewinder<Data> rewinder, @NonNull Options options, int width,
int height, DecodePath.DecodeCallback<ResourceType> decodeCallback) throws GlideException {
List<Throwable> throwables = Preconditions.checkNotNull(listPool.acquire());
try {
// Focus on
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
for (int i = 0, size = decodePaths.size(); i < size; i++) {
DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);
try {
// call DecodePath. Decode to actually parse the data
result = path.decode(rewinder, width, height, options, decodeCallback);
} catch (GlideException e) {
exceptions.add(e);
}
if(result ! =null) {
break; }}if (result == null) {
throw new GlideException(failureMessage, new ArrayList<>(exceptions));
}
return result;
}
Copy the code
DecodePath
public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height,
@NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException {
// Get the Resource
object
Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
// Convert resources to target effects, such as CenterCrop set when building request
Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
/ / target format, data can be converted to the Resource < Bitmap > convert LazyBitmapDrawableResource object
/ / by LazyBitmapDrawableResource get access to a BitmapDrawable object
// The transcoder is BitmapDrawableTranscoder
return transcoder.transcode(transformed, options);
}
Copy the code
LoadPath’s load method will eventually parse the data by calling DecodePath’s decode, whose main job is to fetch Resource objects, Then the Resource object into LazyBitmapDrawableResource. Considering the length problem, we will not analyze how to get the Resource object here, but only how to convert the data into the target format. We can find the BitmapDrawable transcoder in the Glide construction registry. So we’re actually calling BitmapDrawableTranscoder’s Transcode to do the conversion
BitmapDrawableTranscoder#transcode
public Resource<BitmapDrawable> transcode(@NonNull Resource
toTranscode, @NonNull Options options)
{
/ / get LazyBitmapDrawableResource object
return LazyBitmapDrawableResource.obtain(resources, toTranscode);
}
Copy the code
LazyBitmapDrawableResource
public static Resource<BitmapDrawable> obtain( @NonNull Resources resources, @Nullable Resource
bitmapResource)
{
if (bitmapResource == null) {
return null;
}
/ / create a LazyBitmapDrawableResource object
return new LazyBitmapDrawableResource(resources, bitmapResource);
}
public BitmapDrawable get(a) {
// Return a BitmapDrawable
return new BitmapDrawable(resources, bitmapResource.get());
}
Copy the code
Tracking down can find transcode will eventually get a encapsulates the Resource object, then watch LazyBitmapDrawableResource the get method, can get a BitmapDrawable object, namely target format. Here is the data successfully parsed into LazyBitmapDrawableResource object.
2.6 Display images in the main thread
Now that the data is parsed, all that remains is to display the data, so we have to go back to the decodeFromRetrievedData method of the DecodeJob
DecodeJob
private void decodeFromRetrievedData(a) {...try {
After the success of the / / parse the resource to encapsulate the resource < Bitmap > LazyBitmapDrawableResource object
// The BitmapDrawable object can be obtained by the get method
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
if(resource ! =null) {
// Notifies the external world that the resource is successfully obtained
notifyEncodeAndRelease(resource, currentDataSource);
} else{ runGenerators(); }}private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {...// Focus onnotifyComplete(result, dataSource); . }private void notifyComplete(Resource<R> resource, DataSource dataSource) {
setNotifiedOrThrow();
// callback to Engine job (DecodeJob)
callback.onResourceReady(resource, dataSource);
}
Copy the code
2.6.1 Callback data
Callback () callback (); callback (); callback (); Don’t worry, let’s break it down step by step.
First make sure that notifyComplete is in the DecodeJob class, so callback should be a member variable, and then figure out where to assign the value
// Focus on the penultimate argument, callback of type callback
DecodeJob<R> init(
GlideContext glideContext,
Object model,
EngineKey loadKey,
Key signature,
int width,
intheight, Class<? > resourceClass, Class<R> transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map<Class<? >, Transformation<? >> transformations,boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
boolean onlyRetrieveFromCache,
Options options,
Callback<R> callback,
int order) {
decodeHelper.init(
glideContext,
model,
signature,
width,
height,
diskCacheStrategy,
resourceClass,
transcodeClass,
priority,
options,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
diskCacheProvider);
this.glideContext = glideContext;
this.signature = signature;
this.priority = priority;
this.loadKey = loadKey;
this.width = width;
this.height = height;
this.diskCacheStrategy = diskCacheStrategy;
this.onlyRetrieveFromCache = onlyRetrieveFromCache;
this.options = options;
this.callback = callback;
this.order = order;
this.runReason = RunReason.INITIALIZE;
this.model = model;
return this;
}
Copy the code
It is easy to see that the init method assigns a value to the callback, so remember that the position of the callback argument is the second to last. Now you’re thinking: where is the init method of DecodeJob called? Then try to figure out: since it is an assignment estimate will be called when building DecodeJob. The question then becomes: where is DecodeJob built above? Then silently: DecodeJob is used to execute tasks, so it should be called when building tasks! (Most of the time, though, my mind goes blank and I can’t think of anything, and I can’t think of anything here. You can then go directly to where DecodeJob first appeared) and eventually find the DecodeJob build in Engine load
Engine#load
public synchronized <R> LoadStatus load(...).{
// Focus on the last to last parameter
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
}
// Focus on the last parameter
<R> DecodeJob<R> build(GlideContext glideContext,
Object model,
EngineKey loadKey,
Key signature,
int width,
intheight, Class<? > resourceClass, Class<R> transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map<Class<? >, Transformation<? >> transformations,boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
boolean onlyRetrieveFromCache,
Options options,
DecodeJob.Callback<R> callback) {
DecodeJob<R> result = Preconditions.checkNotNull((DecodeJob<R>) pool.acquire());
returnresult.init( glideContext, model, loadKey, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, onlyRetrieveFromCache, options, callback, creationOrder++); }}Copy the code
In the code above the first call DecodeJobFactory DecodeJob, build method to construct the DecodeJobFactory is Engine inner class, then see DecodeJobFactory build method, wow! It’s exactly what we thought! Build call DecodeJob init method, do not forget our task is what! Find the value of callback, go back to the build callback argument, and then go back to the load of Engine to call the last build argument! EngineJob! Callback (DecodeJob) : DecodeJob (callback) : DecodeJob (callback) : DecodeJob (callback) : DecodeJob (callback) : DecodeJob (callback) So the onResourceReady method of the EngineJob is called back
EngineJob#onResourceReady
public void onResourceReady(Resource<R> resource, DataSource dataSource) {
synchronized (this) {
this.resource = resource;
this.dataSource = dataSource;
}
// Focus on
notifyCallbacksOfResult();
}
void notifyCallbacksOfResult(a) { ResourceCallbacksAndExecutors copy; Key localKey; EngineResource<? > localResource;synchronized (this) {...// Focus on the type of CBS
// Find the type in CBScopy = cbs.copy(); . }// Notify the upper-layer Engine that the task is complete
listener.onEngineJobComplete(this, localKey, localResource);
for (final ResourceCallbackAndExecutor entry : copy) {
// Call back to ImageViewTarget to display the resource
entry.executor.execute(new CallResourceReady(entry.cb));
}
decrementPendingCallbacks();
}
Copy the code
2.6.2 Returning to the main thread
It’s time to confirm the parameter type. Call Holmes online! Let’s start by identifying the most important piece of code in the onResourceReady method of EngineJob
for (final ResourceCallbackAndExecutor entry : copy) {
// Call back to ImageViewTarget to display the resource
entry.executor.execute(new CallResourceReady(entry.cb));
}
Copy the code
Before we can determine the execute method for the thread pool, we need to do the following:
- Determine the entry. Executor type
- Determine the entry. Cb type
Now we know that entry for ResourceCallbackAndExecutor method, so let’s take a look at the class and constructor
ResourceCallbackAndExecutor
static final class ResourceCallbackAndExecutor {
final ResourceCallback cb;
final Executor executor;
ResourceCallbackAndExecutor(ResourceCallback cb, Executor executor) {
this.cb = cb;
this.executor = executor; }}Copy the code
Can be found in the executor and cb are ResourceCallbackAndExecutor member variables, assigned during construction, so we need to find a place to construct ResourceCallbackAndExecutor object, Naturally we lock the copy variable up there
EngineJob
final ResourceCallbacksAndExecutors cbs = new ResourceCallbacksAndExecutors();
void notifyCallbacksOfResult(a) { ResourceCallbacksAndExecutors copy; Key localKey; EngineResource<? > localResource;synchronized (this) {...// Focus on the type of CBS
// Find the type in CBScopy = cbs.copy(); . }... }ResourceCallbacksAndExecutors copy(a) {
return new ResourceCallbacksAndExecutors(new ArrayList<>(callbacksAndExecutors));
}
// where CBS is assigned
synchronized void addCallback(final ResourceCallback cb, Executor callbackExecutor) {
stateVerifier.throwIfRecycled();
// The cb type is singleRequest, which implements ResourceCallback interface
//callbackExecutor is a thread pool bound to the main thread Handler
/ / type for ResourceCallbacksAndExecutors CBS
/ / add the internal implementation is to create ResourceCallbacksAndExecutor and cb, callbackExecutor assignment to its member variables
// Then add it to CBScbs.add(cb, callbackExecutor); . }void add(ResourceCallback cb, Executor executor) {
callbacksAndExecutors.add(new ResourceCallbackAndExecutor(cb, executor));
}
Copy the code
Call, let’s take a look at the copy assignment is called ResourceCallbacksAndExecutors copy method types of CBS, the copy is created ResourceCallbacksAndExecutor collection, the collection is in fact CBS, We also need to find the CBS assignment, and after a while you’ll find the CBS Add method in the addCallback method, The add method of the internal implementation is actually create ResourceCallbacksAndExecutor and cb, callbackExecutor assignment to its member variables, so we have to determine what is the add method of two parameters? If you remember, the addCallback method was specifically mentioned during the build task, so let’s revisit Engine’s load method.
Engine#load
.// Call addCallback() to register a ResourceCallback
// where cb is the penultimate argument to load, which is called in singleRequest onSizeReady()
// The cb type is singleRequest
// Go back to the EngineJob addCallback method
engineJob.addCallback(cb, callbackExecutor);
// Execute DecodeJob's run in the child thread
engineJob.start(decodeJob);
Copy the code
To determine the types of CB and callbackExecutor, we need to step back
// Pay special attention to the last two parameters
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)
Copy the code
SingleRequest#onSizeReady
loadStatus =
// Focus on the penultimate parameter, passing in this, the SingleRequest object that implements the ResourceCallback interface
// Focus on the penultimate argument, passing the r thread pool callbackExectuter with the Handle bound to the main thread
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
In SingleRequest onSizeReady, we confirm that cb is the SingleRequest object. For another parameter, we will not post the code one by one because of the space. You can directly look back from the onSizeReady method. As mentioned in the comments above, you will eventually see that the callbackExecutor is actually the pool of threads that we mentioned earlier that contains the bound main thread Handler. Let’s go back to where we started
EngineJob#onResourceReady
for (final ResourceCallbackAndExecutor entry : copy) {
// Call back to ImageViewTarget to display the resource
//entry.cb indicates the singleRequest type
//entry.executor is a thread pool containing MAIN_THREAD_EXECUTOR handlers bound to the main thread
entry.executor.execute(new CallResourceReady(entry.cb));
}
Copy the code
So take a look at Executors’ mainThreadExecutor method (rewatch 2.2 above if you forgot)
private static final Executor MAIN_THREAD_EXECUTOR =
new Executor() {
// Bind the main thread Looper
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(@NonNull Runnable command) { handler.post(command); }};public static Executor mainThreadExecutor(a) {
return MAIN_THREAD_EXECUTOR;
}
Copy the code
After the execute method of MAIN_THREAD_EXECUTOR is called, the run method of the CallResourceReady object is executed in the main thread. So let’s look at the Run method of CallResourceReady
2.6.3 Displaying pictures
EngineJob.CallResourceReady#run
public void run(a) {
synchronized (EngineJob.this) {
if (cbs.contains(cb)) {
// Acquire for this particular callback.
engineResource.acquire();
// The cb is SingleRequestcallCallbackOnResourceReady(cb); removeCallback(cb); } decrementPendingCallbacks(); }}}synchronized void callCallbackOnResourceReady(ResourceCallback cb) {
try {
// Call back the target data
// The cb type is singleRequest
cb.onResourceReady(engineResource, dataSource);
} catch (Throwable t) {
throw newCallbackException(t); }}Copy the code
Isn’t it nice to see this (actual scalp tingling)! Here comes the familiar callback, where cb is of type SingleRequest, which we analyzed above. So the onResourceReady method of SingleRequest is called
SingleRequest#onResourceReady
public synchronized void onResourceReady(Resource
resource, DataSource dataSource) {...// Focus on
onResourceReady((Resource<R>) resource, (R) received, dataSource);
}
private synchronized void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
// First load
boolean isFirstResource = isFirstReadyResource();
status = Status.COMPLETE;
this.resource = resource;
if (glideContext.getLogLevel() <= Log.DEBUG) {
Log.d(GLIDE_TAG, "Finished loading " + result.getClass().getSimpleName() + " from "
+ dataSource + " for " + model + " with size [" + width + "x" + height + "] in "
+ LogTime.getElapsedMillis(startTime) + " ms");
}
isCallingCallbacks = true;
try {
boolean anyListenerHandledUpdatingTarget = false;
// If the listener is set at use, onResourceReady is called back
if(requestListeners ! =null) {
for(RequestListener<R> listener : requestListeners) { anyListenerHandledUpdatingTarget |= listener.onResourceReady(result, model, target, dataSource, isFirstResource); } } anyListenerHandledUpdatingTarget |= targetListener ! =null
&& targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);
if(! anyListenerHandledUpdatingTarget) { Transition<?super R> animation =
animationFactory.build(dataSource, isFirstResource);
// Display photos
// The target is DrawableImageViewTargettarget.onResourceReady(result, animation); }}finally {
isCallingCallbacks = false;
}
// The notification was successfully loaded
notifyLoadSuccess();
}
Copy the code
OnResourceReady (result, animation), target object is DrawableImageViewTarget, So the onResourceReady method of DrawableImageViewTarget is called, but since DrawableImageViewTarget doesn’t have onResourceReady, So it should be in its parent class, ImageViewTarget
ImageViewTarget
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
// Is there any animation effect
if (transition == null| |! transition.transition(resource,this)) {
// Focus on the static graph
setResourceInternal(resource);
} else {
//gif maybeUpdateAnimatable(resource); }}private void setResourceInternal(@Nullable Z resource) {
// Call setResource to display the photo
setResource(resource);
maybeUpdateAnimatable(resource);
}
DrawableImageViewTarget (DrawableImageViewTarget); DrawableImageViewTarget (DrawableImageViewTarget); DrawableImageViewTarget (DrawableImageViewTarget)
protected abstract void setResource(@Nullable Z resource);
Copy the code
Here we use a normal static image, so we call setResourceInternal(Resource), and then setResource(resource) to show the image, SetResource in ImageViewTarget is an abstract method, so let’s go back to the implementation of subclass DrawableImageViewTarget
DrawableImageViewTarget#setResource
protected void setResource(@Nullable Drawable resource) {
// Successfully display the photo
view.setImageDrawable(resource);
}
Copy the code
A: wow! The setResource is very simple, which is to display the photo directly!
3. Summary
Into method is the whole Glide picture loading process of the most complex logic of a song, the amount of code, the corresponding workload is also super much, both when the father and mother, both to the network to obtain data, but also to parse and display data. The main work is as follows:
conclusion
It took me a long time to read the Glide3.x version and the source code for Glide3.7, and then read the Glide4.9 article and source code, and finally summarize myself. After reading the source code of the Glide4.9 loading process, I feel that this call is really many, and it is quite time-consuming to retrieve the parameters of the call. But as a whole, there is only one word in my heart, “Glide!” With only one line of code, the actual internal processing logic is how complex and in place, also enough to see how powerful Glide function. But the power of Glide is not only shown in the image loading process, but also in its powerful caching strategy. Let’s continue to appreciate the power of Glide: Glide 4.9 source parse-caching strategy
Reference blog:
- Glide 4.9 source code analysis (a) – a complete loading process
- Android source code analysis: this is a detailed picture loading library Glide source code explain walkthroughs
- Android picture loading framework the most complete analysis (two), from the perspective of the source understanding Glide’s execution process