Glide Picture loading framework in the last article, we introduced the use of Glide picture loading framework, through previous learning, we may be able to proficiently use the Glide picture loading framework into our project, but if someone asks you how it is loaded, how does it work? Why does a custom GlideModule just need to add meta-data to the Manifest file? And so on a lot of loading processes and use considerations. Of course, in order to understand these problems, we need to Glide source code to have a general understanding, to analyze the mystery of the deep source. Let’s go into the world of Glide source code, this article analysis is Glide 3.7.0 version. Special reminder, before reading this article to the use of Glide to have a first understanding, you can first read the last article, detail picture loading framework Glide – application.
This article is a record of my own learning, and I am very glad if it is helpful to you. Of course, if there are deficiencies or mistakes in the article, please correct them so as to avoid me giving wrong guidance to other readers.
If you’ve read the last article, or if you’ve used Glide, you know that the easiest way to load an image is to Glide
Glide. With (context). The load (url). The placeholder (R.d rawable. The placeholder). Into (imageView).Copy the code
So this article with this simple code as the main line, step by step into the source Glide.
Glide.with(context)
// Get the RequestManager object, which implements the LifeCycleListener interface and binds the Activity/Fragment lifecycle to suspend, resume, and clear requests
public static RequestManager with(Context context) {
/ / get RequestManagerRetriever instance, the class will note RequestManager and custom fragments (such as RequestManagerFragment SupportRequestManagerFragment) binding, Manage callbacks in the life cycle
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(context);
}Copy the code
Glide has four static overloaded methods with(), each of which internally obtains a RequestManager object from the corresponding RequestManagerRetriever retriever get overloaded method. The benefit of providing various overloaded methods is that you can tie Glide’s load request to the Activity/Fragment lifecycle to automatically execute the request and suspend the operation.
Next, we use the Activity parameter to analyze how Glide requests and bind life cycles automatically request, suspend, and destroy.
public RequestManager get(Activity activity) {
if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return get(activity.getApplicationContext());
} else {
// Determine whether the activity is already destroyed
assertNotDestroyed(activity);
// Get the FragmentManager object
android.app.FragmentManager fm = activity.getFragmentManager();
// Create a Fragment, RequestManager and bind it
returnfragmentGet(activity, fm); }}Copy the code
AssertNotDestroyed mainly asserts whether the Activity has been Destroyed. If there is no destruction, or the Activity’s FragmentManager, then return the RequestManager via fragmentGet.
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {
//* Obtain a RequestManagerFragment, using Frament to manage the request lifecycle
RequestManagerFragment current = getRequestManagerFragment(fm);
RequestManager requestManager = current.getRequestManager();
// If the requestManager is empty, the initial requestManager is loaded and setRequestManager is called to set the fragment to the RequestManagerFragment
if (requestManager == null) {
requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}
// Get the Fragment object
RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm) {
RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
current = pendingRequestManagerFragments.get(fm);
if (current == null) {
current = newRequestManagerFragment(); pendingRequestManagerFragments.put(fm, current); fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(); }}return current;
}Copy the code
Finally through getRequestManagerFragment () method to obtain a RequestManagerFragment object.
public class RequestManagerFragment extends Fragment {
private final ActivityFragmentLifecycle lifecycle;
// omit some code...
@Override
public void onStart(a) {
super.onStart();
Lifecycle is associated with the corresponding onStart method
lifecycle.onStart();
}
@Override
public void onStop(a) {
super.onStop();
Lifecycle is associated with the corresponding onStop method
lifecycle.onStop();
}
@Override
public void onDestroy(a) {
super.onDestroy();
Lifecycle is associated with the corresponding onDestroy methodlifecycle.onDestroy(); }}Copy the code
At this point we see that the RequestManagerFragment inherits the Fragment. And in its life cycle onStart (), onStop (), onDestory (), call the ActivityFragmentLifecycle corresponding method, ActivityFragmentLifecycle implements the Lifecycle interface, LifecycleListener Listener) callback corresponding cycle methods (LifecycleListener onStart(),onStop(),onDestory()). LifecycleListener is the interface that listens on the lifecycle time. Back to the next line of code in the fragmentGet method
/ / create RequestManager incoming Lifecycle implementation class, such as ActivityFragmentLifecycle
requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());Copy the code
For the RequestManager class, which implements LifecycleListener, the following code
/** * A class for managing and starting requests for Glide. Can use activity, fragment and connectivity lifecycle events to * intelligently stop, start, and restart requests. Retrieve either by instantiating a new object, or to take advantage * built in Activity and Fragment lifecycle handling, use the static Glide.load methods with your Fragment or Activity. */
public class RequestManager implements LifecycleListener {
//An interface for listening to Activity/Fragment lifecycle events.
private final Lifecycle lifecycle;
public RequestManager(Context context, Lifecycle lifecycle, RequestManagerTreeNode treeNode) {
this(context, lifecycle, treeNode, new RequestTracker(), new ConnectivityMonitorFactory());
}
RequestManager(Context context, final Lifecycle lifecycle, RequestManagerTreeNode treeNode,
RequestTracker requestTracker, ConnectivityMonitorFactory factory) {
this.context = context.getApplicationContext();
this.lifecycle = lifecycle;
this.treeNode = treeNode;
//A class for tracking, canceling, and restarting in progress, completed, and failed requests.
this.requestTracker = requestTracker;
// Obtain a Glide instance through the static method of Glide. The singleton pattern
this.glide = Glide.get(context);
this.optionsApplier = new OptionsApplier();
/ / by the factory class ConnectivityMonitorFactory obtained the build ConnectivityMonitor (an interface used to monitor the network connection events)
ConnectivityMonitor connectivityMonitor = factory.build(context,
new RequestManagerConnectivityListener(requestTracker));
// If we're the application level request manager, we may be created on a background thread. In that case we
// cannot risk synchronously pausing or resuming requests, so we hack around the issue by delaying adding
// ourselves as a lifecycle listener by posting to the main thread. This should be entirely safe.
if (Util.isOnBackgroundThread()) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run(a) {
lifecycle.addListener(RequestManager.this); }}); }else {
// Set the listener
lifecycle.addListener(this);
}
lifecycle.addListener(connectivityMonitor);
}
/** * Lifecycle callback that registers for connectivity events (if the android.permission.ACCESS_NETWORK_STATE * permission is present) and restarts failed or paused requests. */
@Override
public void onStart(a) {
// onStart might not be called because this object may be created after the fragment/activity's onStart method.
resumeRequests();
}
/** * Lifecycle callback that unregisters for connectivity events (if the android.permission.ACCESS_NETWORK_STATE * permission is present) and pauses in progress loads. */
@Override
public void onStop(a) {
pauseRequests();
}
/** * Lifecycle callback that cancels all in progress requests and clears and recycles resources for all completed * requests. */
@Override
public void onDestroy(a) { requestTracker.clearRequests(); }}Copy the code
It will pass lifeCycle of the fragment that has just been created and add the RequestManager listener to lifeCycle to implement the binding. You see requestTracker in the constructor of the RequestManager, which tracks requests canceled, restarted, completed, and failed. The RequestManagerFragment is used to connect to lifecycle methods, the RequestManager is used to implement the lifecycle request methods, and the RequestManagerRetriever is bound to the RequestManager.
GlideModule implementation
Glide. Get (context) is also initialized in the constructor; The Glide object is initialized
/**
* Get the singleton.
*
* @return the singleton
*/
public static Glide get(Context context) {
if (glide == null) {
/ / synchronize Glide
synchronized (Glide.class) {
if (glide == null) {
Context applicationContext = context.getApplicationContext();
// Parse the metadata tag of the custom GlideModule configured in the manifest file to return a GlideModule collection
List<GlideModule> modules = new ManifestParser(applicationContext).parse();
GlideBuilder builder = new GlideBuilder(applicationContext);
// A collection of loops that execute methods in the GlideModule implementation class
for (GlideModule module : modules) {
module.applyOptions(applicationContext, builder);
}
glide = builder.createGlide();
for (GlideModule module : modules) {
// Register the componentmodule.registerComponents(applicationContext, glide); }}}}return glide;
}Copy the code
The instance is obtained singletons using the GET method, and the GlideModule configuration is implemented during initialization. How do you do that? On initialization, a ManifestParser object is new and the parse () method is called to return a List of type GlideModule.
Parse the metadata implementation
public List<GlideModule> parse(a) {
List<GlideModule> modules = new ArrayList<GlideModule>();
try {
// The PackageManager obtains all metadata information
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
context.getPackageName(), PackageManager.GET_META_DATA);
// Manifest file contains metadata
if(appInfo.metaData ! =null) {
// Metadata is traversed by key (for GlideModule, key is the full path class name of GlideModule's implementation class)
for (String key : appInfo.metaData.keySet()) {
// Filter key value equal to GLIDE_MODULE_VALUE (string GlideModule)
if (GLIDE_MODULE_VALUE.equals(appInfo.metaData.get(key))) {
// Add to setmodules.add(parseModule(key)); }}}}catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException("Unable to find metadata to parse GlideModules", e);
}
return modules;
}Copy the code
The parse () method uses the getApplicationInfo method to obtain metaData information. If metaData data (appInfo.metadata! If the metaData is GlideModule, parseModule (key) is called. The method returns GlideModule and adds GlideModule to the returned List.
Look at the parseModule(String className) method
// Get the GlideModule instance through reflection
private static GlideModule parseModule(String className) { Class<? > clazz;try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Unable to find GlideModule implementation", e);
}
Object module;
try {
module = clazz.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Unable to instantiate GlideModule implementation for " + clazz, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to instantiate GlideModule implementation for " + clazz, e);
}
if(! (moduleinstanceof GlideModule)) {
throw new RuntimeException("Expected instanceof GlideModule, but found: " + module);
}
return (GlideModule) module;
}Copy the code
Here we see the custom GlideModule object we declared in the manifest being retrieved by reflection. After getting the GlideModule collection, the collection is iterated over and the corresponding applyOptions and registerComponents methods are called. The Glide object generation is created through the createGlide method of GlideBuilder.
Glide createGlide(a) {
if (sourceService == null) {
final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());
// Initialize the thread pool
sourceService = new FifoPriorityThreadPoolExecutor(cores);
}
if (diskCacheService == null) {
diskCacheService = new FifoPriorityThreadPoolExecutor(1);
}
MemorySizeCalculator calculator = new MemorySizeCalculator(context);
// Set the Bitmap pool
if (bitmapPool == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
int size = calculator.getBitmapPoolSize();
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = newBitmapPoolAdapter(); }}if (memoryCache == null) {
memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
}
if (diskCacheFactory == null) {
// Internal disk cache
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
if (engine == null) {
// Initialize the engine class
engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService);
}
if (decodeFormat == null) {
decodeFormat = DecodeFormat.DEFAULT;
}
return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
}Copy the code
See this is all doing some initialization and passing the parameters to the Glide constructor. Glide construction method to do are some default initialization operations, you can go to view the source code, no longer posted here.
The metaData key is the full path name of the GlideModule. The metaData key is the default path name of the GlideModule. Value The value must be GlideModule. You’ll see that when we don’t want our custom GlideModule to work, we just need to remove the corresponding GlideModule. Why configure when obfuscation is used…
-keep public class * implements com.bumptech.glide.module.GlideModuleCopy the code
requestManager.load
The load method can also accept overloaded methods of String, Url, Integer, etc. In this case, we analyze the String parameter.
public DrawableTypeRequest<String> load(String string) {
return (DrawableTypeRequest<String>) fromString().load(string);
}
public DrawableTypeRequest<String> fromString(a) {
return loadGeneric(String.class);
}
private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
// Omit a piece of code
return optionsApplier.apply(
// Create DrawableTypeRequest, a subclass of GenericRequestBuilder
new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
glide, requestTracker, lifecycle, optionsApplier));
}Copy the code
@Override
public DrawableRequestBuilder<ModelType> load(ModelType model) {
// Call the freloadd method
super.load(model);
return this;
}Copy the code
The DrawableTypeRequest object is returned, and the DrawableTypeRequest inheritance is as followsFor the equestBuilder class, there is a creator mode. For the popular placeholder functions placeholder (), error (), transform, etc.
/ * * * {@inheritDoc} * /
@Override
public DrawableRequestBuilder<ModelType> placeholder(Drawable drawable) {
super.placeholder(drawable);
return this;
}Copy the code
We see that the parent method is eventually called again
/**
* Sets an Android resource id for a {@link android.graphics.drawable.Drawable} resourceto display while a resource
* is loading.
*
* @param resourceId The id of the resource to use as a placeholder
* @return This request builder.
*/
public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> placeholder(
int resourceId) {
this.placeholderId = resourceId;
return this;
}Copy the code
If you look at the GenericRequestBuilder source, you’ll see that every time we call placeholder (), error(), etc., we’re actually assigning values to variables in that class.
After a series of operations, the into(imageView) method is called to complete the final loading of the image
Create a request
/**
* Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into the view, and frees
* any resources Glide may have previously loaded into the view so they may be reused.
*
* @see Glide#clear(android.view.View)
*
* @param view The view to cancel previous loads for and load the new resource into.
* @return The {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link ImageView}.
*/
public Target<TranscodeType> into(ImageView view) {
Util.assertMainThread();
if (view == null) {
throw new IllegalArgumentException("You must pass in a non null View");
}
if(! isTransformationSet && view.getScaleType() ! =null) {
switch (view.getScaleType()) {
case CENTER_CROP:
applyCenterCrop();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
applyFitCenter();
break;
//$CASES-OMITTED$
default:
// Do nothing.}}return into(glide.buildImageViewTarget(view, transcodeClass));
}Copy the code
As you can see above, the final call to the into method is
/**
* Set the target the resource will be loaded into.
*
* @see Glide#clear(com.bumptech.glide.request.target.Target)
*
* @param target The target to load the resource into.
* @return The given target.
*/
public <Y extends Target<TranscodeType>> Y into(Y target) {
Util.assertMainThread();
if (target == null) {
throw new IllegalArgumentException("You must pass in a non null Target");
}
if(! isModelSet) {throw new IllegalArgumentException("You must first set a model (try #load())");
}
// Get the Request object
Request previous = target.getRequest();
//requestTracker is a request tracking object that manages requests initiated, paused, and cleared
if(previous ! =null) {
previous.clear();
requestTracker.removeRequest(previous);
previous.recycle();
}
// Create the request object
Request request = buildRequest(target);
target.setRequest(request);
// Add target to lifecycle
lifecycle.addListener(target);
// Execute the request
requestTracker.runRequest(request);
return target;
}Copy the code
All of the above calls util.assertMainThread (); Judgments can only be performed in the main thread. (Update View of course needs to be in the main thread), in Glide we can understand Target as View, but Glide is a layer of encapsulation of our View. The request object is then created via buildRequest.
// Create the request object
private Request buildRequest(Target<TranscodeType> target) {
if (priority == null) {
// The default loading priority is NORMAL
priority = Priority.NORMAL;
}
/ / create Request
return buildRequestRecursive(target, null);
}
private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {
if(thumbnailRequestBuilder ! =null) {
if (isThumbnailBuilt) {
throw new IllegalStateException("You cannot use a request as both the main request and a thumbnail, "
+ "consider using clone() on the request(s) passed to thumbnail()");
}
// Recursive case: contains a potentially recursive thumbnail request builder.
if(thumbnailRequestBuilder.animationFactory.equals(NoAnimation.getFactory())) { thumbnailRequestBuilder.animationFactory = animationFactory; }if (thumbnailRequestBuilder.priority == null) {
thumbnailRequestBuilder.priority = getThumbnailPriority();
}
if(Util.isValidDimensions(overrideWidth, overrideHeight) && ! Util.isValidDimensions(thumbnailRequestBuilder.overrideWidth, thumbnailRequestBuilder.overrideHeight)) { thumbnailRequestBuilder.override(overrideWidth, overrideHeight); } ThumbnailRequestCoordinator coordinator =new ThumbnailRequestCoordinator(parentCoordinator);
Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
// Guard against infinite recursion.
isThumbnailBuilt = true;
// Recursively generate thumbnail requests.
Request thumbRequest = thumbnailRequestBuilder.buildRequestRecursive(target, coordinator);
isThumbnailBuilt = false;
coordinator.setRequests(fullRequest, thumbRequest);
return coordinator;
} else if(thumbSizeMultiplier ! =null) {
// Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.
ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
Request thumbnailRequest = obtainRequest(target, thumbSizeMultiplier, getThumbnailPriority(), coordinator);
coordinator.setRequests(fullRequest, thumbnailRequest);
return coordinator;
} else {
// Base case: no thumbnail.
returnobtainRequest(target, sizeMultiplier, priority, parentCoordinator); }}Copy the code
Finally, the obtainRequest method is called
private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,
RequestCoordinator requestCoordinator) {
return GenericRequest.obtain(
loadProvider,
model,
signature,
context,
priority,
target,
sizeMultiplier,
placeholderDrawable,
placeholderId,
errorPlaceholder,
errorId,
fallbackDrawable,
fallbackResource,
requestListener,
requestCoordinator,
glide.getEngine(),
transformation,
transcodeClass,
isCacheable,
animationFactory,
overrideWidth,
overrideHeight,
diskCacheStrategy);
}Copy the code
It was eventually created using the GenericRequest.obtain method
public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain(...). {
@SuppressWarnings("unchecked")
GenericRequest<A, T, Z, R> request = (GenericRequest<A, T, Z, R>) REQUEST_POOL.poll();
if (request == null) {
request = new GenericRequest<A, T, Z, R>();
}
// Initializes the Request object with the set parametersrequest.init(...) ;// Return the Request object
return request;
}Copy the code
After the buildRequest request is successfully created, target.setrequest (request) is used. Set the request to target and add target to Lifecycle via addListener. Performed so much above all just request creation, request is executed through requestTracker. RunRequest (request); In the first place.
Send the request
/** * Starts tracking the given request. */
public void runRequest(Request request) {
// Add the request object to the collection
requests.add(request);
if(! isPaused) {// If the current state is not paused, call begin to send the request
request.begin();
} else {
// Add the request to the pending request collectionpendingRequests.add(request); }}Copy the code
In the last few lines of code, we saw that each time a request is submitted, the request is added to a set, used to manage the request, and then viewed through the Request implementation class GenericRequest to see what the BEGIN method does
/ * * * {@inheritDoc} * /
@Override
public void begin(a) {
startTime = LogTime.getLogTime();
if (model == null) {
// Load incorrect placeholder Settings
onException(null);
return;
}
status = Status.WAITING_FOR_SIZE;
// Verify that the width and height are valid
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
// Send the request
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
if(! isComplete() && ! isFailed() && canNotifyStatusChanged()) {// Default placeholder setting callback before loading
target.onLoadStarted(getPlaceholderDrawable());
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished run method in "+ LogTime.getElapsedMillis(startTime)); }}// Get the Drawable object that sets the placeholder image at the start of the load
private Drawable getPlaceholderDrawable(a) {
if (placeholderDrawable == null && placeholderResourceId > 0) {
placeholderDrawable = context.getResources().getDrawable(placeholderResourceId);
}
return placeholderDrawable;
}Copy the code
It said! isComplete() && ! OnLoadStarted (getNotifyStatusChanged ()) isFailed() &&canNotifyStatusChanged (); We can see the onLoadStarted callback execution statement in Target’s implementation class ImageViewTarget
// Set the ImageView Drawable
@Override
public void onLoadStarted(Drawable placeholder) {
view.setImageDrawable(placeholder);
}Copy the code
Now you have a feeling of a bright future, finally understand why after setting the placeHolder, there will be a placeHolder before loading, of course, the principle of setting the loading error image placeHolder is the same. The timing of the callback is different.
/** * A callback method that should never be invoked directly. */
@Override
public void onSizeReady(int width, int height) {
// Omit some code
status = Status.RUNNING;// Update the request status to the running state
// Omit some code
// An entry to the Engine that requests the core method to execute
loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
priority, isMemoryCacheable, diskCacheStrategy, this); loadedFromMemoryCache = resource ! =null;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished onSizeReady in "+ LogTime.getElapsedMillis(startTime)); }}Copy the code
The Engine class encapsulates important entry methods for retrieving data and provides these apis to the Request layer, such as load(), Release (), clearDiskCache(), and so on
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
// Assert whether the thread is in the main thread
Util.assertMainThread();
long startTime = LogTime.getLogTime();
final String id = fetcher.getId();
/ / create Enginekey
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
// Load images from cacheEngineResource<? > cached = loadFromCache(key, isMemoryCacheable);if(cached ! =null) {
// onResourceReady() is called back to target
cb.onResourceReady(cached);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
// Try to fetch from active Resources, which represents the Resources currently in use, unlike the in-memory cache, which is not cleared when the cache is clear.EngineResource<? > active = loadFromActiveResources(key, isMemoryCacheable);if(active ! =null) {
// Get a successful callback
cb.onResourceReady(active);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
EngineJob current = jobs.get(key);
// Determine if a task already exists in Jobs. If so, the task has already been submitted
if(current ! =null) {
current.addCallback(cb);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
// if the cache fails to get the EngineJob, create an EngineJob object
EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
transcoder, diskCacheProvider, diskCacheStrategy, priority);
//EngineRunnable is the entry to the task execution phase
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
// Start submitting the job
engineJob.start(runnable);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}Copy the code
If the return value is empty, it will be loaded again from the active resource. If it is empty again, it will check whether Jobs submitted the task. If not, EngineRunnable will be created and the task will be submitted to the engineJob. Let’s look at the start method in EngineJob
// Submit the task and add it to the thread pool
public void start(EngineRunnable engineRunnable) {
this.engineRunnable = engineRunnable;
// Submit the task to the diskCacheService thread pool
future = diskCacheService.submit(engineRunnable);
}Copy the code
Next, look at the run method of the thread class EngineRunnable, which is the entry point for task execution
// Task run entry
@Override
public void run(a) {
if (isCancelled) {
return;
}
Exception exception = null; Resource<? > resource =null;
try {
// Data acquisition, codec
resource = decode();
} catch (Exception e) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Exception decoding", e);
}
exception = e;
}
// If the current state is cancelled, all resources are reclaimed to prevent memory leaks
if (isCancelled) {
if(resource ! =null) {
resource.recycle();
}
return;
}
if (resource == null) {
// Load failed callback
onLoadFailed(exception);
} else {
// Successfully loaded callbackonLoadComplete(resource); }}privateResource<? > decode()throws Exception {
if (isDecodingFromCache()) {
//// retrieves data from DiskLruCache and decodes it
return decodeFromCache();
} else {
// Retrieve and decode data from other sources, such as network, local File, data stream, etc
returndecodeFromSource(); }}Copy the code
##DiskLruCache retrieve data ##
privateResource<? > decodeFromCache()throwsException { Resource<? > result =null;
try {
result = decodeJob.decodeResultFromCache();
} catch (Exception e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Exception decoding result from cache: "+ e); }}if (result == null) {
result = decodeJob.decodeSourceFromCache();
}
return result;
}Copy the code
Then call decodeResultFromCache in the decodeJob class
public Resource<Z> decodeResultFromCache(a) throws Exception {
if(! diskCacheStrategy.cacheResult()) {return null;
}
long startTime = LogTime.getLogTime();
// Fetch resources from DiskCache
Resource<T> transformed = loadFromCache(resultKey);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Decoded transformed from cache", startTime);
}
startTime = LogTime.getLogTime();
Resource<Z> result = transcode(transformed);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Transcoded transformed from cache", startTime);
}
return result;
}
// Fetch resources from DiskCache
private Resource<T> loadFromCache(Key key) throws IOException {
// Obtain files from DiskCache according to the key
File cacheFile = diskCacheProvider.getDiskCache().get(key);
if (cacheFile == null) {
return null;
}
Resource<T> result = null;
try {
result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
} finally {
if (result == null) { diskCacheProvider.getDiskCache().delete(key); }}return result;
}Copy the code
Next we analyze the decodeFromSource method
// Call decodeJob to get and encode data
privateResource<? > decodeFromSource()throws Exception {
return decodeJob.decodeFromSource();
}
public Resource<Z> decodeFromSource(a) throws Exception {
// Get data, decode
Resource<T> decoded = decodeSource();
// Encode and save
return transformEncodeAndTranscode(decoded);
}
// Get data, decode
private Resource<T> decodeSource(a) throws Exception {
Resource<T> decoded = null;
try {
long startTime = LogTime.getLogTime();
// Data pull
final A data = fetcher.loadData(priority);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Fetched data", startTime);
}
if (isCancelled) {
return null;
}
/ / code
decoded = decodeFromSourceData(data);
} finally {
fetcher.cleanup();
}
return decoded;
}Copy the code
LoadData () of DataFetcher is first called to pull data. There are several classes for DataFetcher implementation. Let’s take fetching data from URL as an example, namely HttpUrlFetcher class
@Override
public InputStream loadData(Priority priority) throws Exception {
return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/.null /*lastUrl*/, glideUrl.getHeaders());
}
// Return the InputStream object
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
throws IOException {
if (redirects >= MAXIMUM_REDIRECTS) {
throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
// Comparing the URLs using .equals performs additional network I/O and is generally broken.
// See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
try {
if(lastUrl ! =null && url.toURI().equals(lastUrl.toURI())) {
throw new IOException("In re-direct loop"); }}catch (URISyntaxException e) {
// Do nothing, this is best effort.}}// 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 request parameters
// Set the connection timeout to 2500ms
urlConnection.setConnectTimeout(2500);
// Set the read timeout to 2500ms
urlConnection.setReadTimeout(2500);
// Do not use HTTP caching
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
// Connect explicitly to avoid errors in decoders if connection fails.
urlConnection.connect();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (statusCode / 100= =2) {
// The request succeeded
return getStreamForSuccessfulRequest(urlConnection);
} else if (statusCode / 100= =3) {
//
String redirectUrlString = urlConnection.getHeaderField("Location");
if (TextUtils.isEmpty(redirectUrlString)) {
throw new IOException("Received empty or null redirect url");
}
URL redirectUrl = new URL(url, redirectUrlString);
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else {
if (statusCode == -1) {
throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
}
throw new IOException("Request failed " + statusCode + ":"+ urlConnection.getResponseMessage()); }}Copy the code
Seeing that this finally looks at the network load request, we can also customize the DataFetcher to use other network libraries such as OkHttp, Volley. Finally, we look at transformEncodeAndTranscode method
private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
long startTime = LogTime.getLogTime();
// Calculate the size of the image to be used by the ImageView according to the ImageView scaleType parameters and save the true width and height of the image.
Resource<T> transformed = transform(decoded);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Transformed resource from source", startTime);
}
// Write to DiskLruCache and use it directly from DiskLruCache next time
writeTransformedToCache(transformed);
startTime = LogTime.getLogTime();
// Transcode the source image to the image format required by the ImageView
Resource<Z> result = transcode(transformed);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Transcoded transformed from source", startTime);
}
return result;
}Copy the code
At this point, image loading process is introduced, and of course a lot of places did not mention, believe that if you’re reading this article, at the same time, his tracking source will be a lot easier, if you don’t jump on the source, may see this article a few times to Glide principle understanding is fog, or see understand at the time, but don’t remember soon. So remember to follow the source code again.
This article is really long, can finish this article also need a certain perseverance… If the article is inadequate or wrong, welcome to correct, in order to prevent other readers wrong guidance.
Welcome to follow my CSDN and micro blog