Glide source code compilation and analysis (Tencent interview reference answers, Android Interview Questions Summary)

Posted by jonstu on Tue, 07 Sep 2021 08:03:08 +0200

public final class RequestOptions extends BaseRequestOptions<RequestOptions> {

private static RequestOptions skipMemoryCacheTrueOptions;
private static RequestOptions skipMemoryCacheFalseOptions;
private static RequestOptions fitCenterOptions;
private static RequestOptions centerCropOptions;
private static RequestOptions circleCropOptions;
private static RequestOptions noTransformOptions;
private static RequestOptions noAnimationOptions;

// ... omit
}

RequestBuilder   Transition (transition options) {} This method is mainly used to load the transition animation of the object from the placeholder or thumbnail to the real object.

RequestBuilder   In the load(...) {} method, many types of data objects can be loaded here, including String, Uri, File, resourceId and byte []. Of course, the corresponding coding methods behind these are also different.

Target   The into(...) {} method is where the Request is triggered. In the above example, this method is called to initiate the Request.

I have to mention the registry domain, which mounts many components. The register includes module loader, Encoder, resource decoder, resource Encoder, data rewinder and Transcoder. These are both basic and core functions of Glide in resource encoding and decoding.

Code structure

Here are some important components and their structural relationships:
ModelLoader

DataFetcher

Target

Resource

ResourceTransformation

Pool

Cache

Decoder

Encoder

Listing these component code structures is mainly to let readers and users see some functions they need at a glance.

Execution process

1. Create requestmanagerfragments or supportrequestmanagerfragments according to different versions of fragments and add them to the corresponding Fragment manager. These two fragments do not have any interface and are mainly used to synchronize the life cycle. The specific implementation is as follows:

public static RequestManager with(Context context) {
 RequestManagerRetriever retriever = RequestManagerRetriever.get();
 return retriever.get(context);
 }

// RequestManagerRetriever.get(...) 
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
 public RequestManager get(Activity activity) {
 if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
 return get(activity.getApplicationContext());
 } else {
 assertNotDestroyed(activity);
 android.app.FragmentManager fm = activity.getFragmentManager();
 return fragmentGet(activity, fm, null);
 }
 }
 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 RequestManager fragmentGet(Context context, android.app.FragmentManager fm,
 android.app.Fragment parentHint) {
 RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
 RequestManager requestManager = current.getRequestManager();
 if (requestManager == null) {
 requestManager =
 new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
 current.setRequestManager(requestManager);
 }
 return requestManager;
 }

 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
 RequestManagerFragment getRequestManagerFragment(
 final android.app.FragmentManager fm, android.app.Fragment parentHint) {
 RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
 if (current == null) {
 current = pendingRequestManagerFragments.get(fm);
 if (current == null) {
 current = new RequestManagerFragment();
 current.setParentFragmentHint(parentHint);
 pendingRequestManagerFragments.put(fm, current);
 fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
 handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
 }
 }
 return current;
 }

2. Create a RequestBuilder and add a transition animation of DrawableTransitionOptions type

public RequestBuilder<Drawable> asDrawable() {
 return as(Drawable.class).transition(new DrawableTransitionOptions());
 }

3. Load object (model field)

public RequestBuilder<TranscodeType> load(@Nullable Object model) {
 return loadGeneric(model);
 }

private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
 this.model = model;
 isModelSet = true;
 return this;
 }

4. Load object (containing the origin of the request).

public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
 Util.assertMainThread();
 Preconditions.checkNotNull(target);
 if (!isModelSet) {
 throw new IllegalArgumentException("You must call #load() before calling #into()");
 }

 Request previous = target.getRequest();

 if (previous != null) {
 requestManager.clear(target);
 }

 requestOptions.lock();
 Request request = buildRequest(target);
 target.setRequest(request);
 requestManager.track(target, request);

 return target;
 }

Generally speaking, most users are used to load pictures, so they will call the following method:

public Target<TranscodeType> into(ImageView view) {
 Util.assertMainThread();
 Preconditions.checkNotNull(view);

 if (!requestOptions.isTransformationSet()
 && requestOptions.isTransformationAllowed()
 && view.getScaleType() != null) {
 if (requestOptions.isLocked()) {
 requestOptions = requestOptions.clone();
 }
 switch (view.getScaleType()) {
 case CENTER_CROP:
 requestOptions.optionalCenterCrop(context);
 break;
 case FIT_CENTER:
 case FIT_START:
 case FIT_END:
 requestOptions.optionalFitCenter(context);
 break;
 //$CASES-OMITTED$
 default:
 // Do nothing.
 }
 }

 return into(context.buildImageViewTarget(view, transcodeClass));
 }

Here, the filling method of ImageView is filtered and set to requestOptions accordingly. Finally, create failed targets through ImageView and transcodeClass (for example, BitmapImageViewTarget corresponding to Bitmap and DrawableImageViewTarget corresponding to Drawable)

4.1 create buildRequest(target).
During the creation of the Request, requests of different sizes will be created according to whether there are thumbnails. The thumbnail method can be added by using the RequestBuilder.thumbnail(...) method.
The requests in Glide all use the SingleRequest class. Of course, the thumbnail uses the ThumbnailRequestCoordinator class:

private Request obtainRequest(Target<TranscodeType> target,
 BaseRequestOptions<?> requestOptions, RequestCoordinator requestCoordinator,
 TransitionOptions<?, ? super TranscodeType> transitionOptions, Priority priority,
 int overrideWidth, int overrideHeight) {
 requestOptions.lock();

 return SingleRequest.obtain(
 context,
 model,
 transcodeClass,
 requestOptions,
 overrideWidth,
 overrideHeight,
 priority,
 target,
 requestListener,
 requestCoordinator,
 context.getEngine(),
 transitionOptions.getTransitionFactory());
 }

What is worth praising is the writing method of SingleRequest.obtain. Personally, I think it is more concise and clear than the new keyword.

target.setRequest(request) is also worth noting. If the target is ViewTarget, the request will be set to the View tag. In fact, this has an advantage. Each View has its own request. If there are duplicate requests, it will first get the last bound request and clean it up from the RequestManager. This should be the function of de duplication.

4.2 requestManager.track(target, request)
This method is very complex and is mainly used for triggering requests, encoding and decoding, loading and caching. Let's look at it step by step:

4.2.1 cache target and start Request

void track(Target<?> target, Request request) {
 targetTracker.track(target);
 requestTracker.runRequest(request);
 }
 /**
 * Starts tracking the given request.
 */
 public void runRequest(Request request) {
 requests.add(request); //Add memory cache
 if (!isPaused) {
 request.begin(); // start
 } else {
 pendingRequests.add(request); // Pending request
 }
 }

Continue to look at the begin method in SingleRequest:

@Override
 public void begin() {
 stateVerifier.throwIfRecycled();
 startTime = LogTime.getLogTime();
 // If the model is empty, it cannot be executed. The model here is the model in the RequestBuilder mentioned earlier
 if (model == null) {
 if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
 width = overrideWidth;
 height = overrideHeight;
 }
 // Only log at more verbose log levels if the user has set a fallback drawable, because
 // fallback Drawables indicate the user expects null models occasionally.
 int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
 onLoadFailed(new GlideException("Received null model"), logLevel);
 return;
 }

 status = Status.WAITING_FOR_SIZE;
 // If the current View size has been loaded and obtained, it will enter the real loading process.
 if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
 onSizeReady(overrideWidth, overrideHeight);
 } else {
 // On the contrary, if the current View has not been drawn, there is no size.
 // ViewTreeObserver.addOnPreDrawListener will be called here.
 // Wait until the dimensions of the View are ok before continuing
 target.getSize(this);
 }

 // If the status is waiting and executing, the placeholder Drawable is currently loaded
 if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
 && canNotifyStatusChanged()) {
 target.onLoadStarted(getPlaceholderDrawable());
 }
 if (Log.isLoggable(TAG, Log.VERBOSE)) {
 logV("finished run method in " + LogTime.getElapsedMillis(startTime));
 }
 }

Next is the target.getSize(this) method. Here we mainly talk about the case where the size is not loaded (ViewTarget.java):

void getSize(SizeReadyCallback cb) {
 int currentWidth = getViewWidthOrParam();
 int currentHeight = getViewHeightOrParam();
 if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {
 cb.onSizeReady(currentWidth, currentHeight);
 } else {
 // We want to notify callbacks in the order they were added and we only expect one or two
 // callbacks to
 // be added a time, so a List is a reasonable choice.
 if (!cbs.contains(cb)) {
 cbs.add(cb);
 }
 if (layoutListener == null) {
 final ViewTreeObserver observer = view.getViewTreeObserver();
 layoutListener = new SizeDeterminerLayoutListener(this);
 // Add size monitoring before painting. I think most Android development students should know this.
 // Next, let's look at what the target did when the system triggered the Listener.
 observer.addOnPreDrawListener(layoutListener);
 }
 }
 }

private static class SizeDeterminerLayoutListener implements ViewTreeObserver
 .OnPreDrawListener {
 // Note that this is a weak reference
 private final WeakReference<SizeDeterminer> sizeDeterminerRef;

 public SizeDeterminerLayoutListener(SizeDeterminer sizeDeterminer) {
 sizeDeterminerRef = new WeakReference<>(sizeDeterminer);
 }

 @Override
 public boolean onPreDraw() {
 if (Log.isLoggable(TAG, Log.VERBOSE)) {
 Log.v(TAG, "OnGlobalLayoutListener called listener=" + this);
 }
 SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();
 if (sizeDeterminer != null) {
 // Inform SizeDeterminer to recheck the size and trigger subsequent operations.
 // SizeDeterminer is a bit like a tool class and serves as a detection interface for size callback
 sizeDeterminer.checkCurrentDimens();
 }
 return true;
 }
 }

ok, go back to the SingleRequest.onSizeReady method, which is mainly the load operation initiated by the Engine

public void onSizeReady(int width, int height) {
 stateVerifier.throwIfRecycled();
 if (Log.isLoggable(TAG, Log.VERBOSE)) {
 logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
 }
 if (status != Status.WAITING_FOR_SIZE) {
 return;
 }
 status = Status.RUNNING;

 float sizeMultiplier = requestOptions.getSizeMultiplier();
 this.width = Math.round(sizeMultiplier * width);
 this.height = Math.round(sizeMultiplier * height);

 if (Log.isLoggable(TAG, Log.VERBOSE)) {
 logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
 }
 loadStatus = engine.load(
 glideContext,
 model,
 requestOptions.getSignature(),
 this.width,
 this.height,
 requestOptions.getResourceClass(),
 transcodeClass,
 priority,
 requestOptions.getDiskCacheStrategy(),
 requestOptions.getTransformations(),
 requestOptions.isTransformationRequired(),
 requestOptions.getOptions(),
 requestOptions.isMemoryCacheable(),
 this);
 if (Log.isLoggable(TAG, Log.VERBOSE)) {
 logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
 }
 }

In particular, all operations come from the only Engine, and its creation comes from the initialization of Glide. If you need to modify the cache configuration, you can continue to see the creation of diskCacheFactory:

if (engine == null) {
 engine = new Engine(memoryCache, diskCacheFactory, diskCacheExecutor, sourceExecutor);
 }

Continue to look at the detailed process of Engine.load:

public <R> LoadStatus load(
 GlideContext glideContext,
 Object model,
 Key signature,
 int width,
 int height,
 Class<?> resourceClass,
 Class<R> transcodeClass,
 Priority priority,
 DiskCacheStrategy diskCacheStrategy,
 Map<Class<?>, Transformation<?>> transformations,
 boolean isTransformationRequired,
 Options options,
 boolean isMemoryCacheable,
 ResourceCallback cb) {
 Util.assertMainThread();
 long startTime = LogTime.getLogTime();

 // Create a key, which is the unique identifier for each load of resources.
 EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
 resourceClass, transcodeClass, options);

 // Find cache resources through the key (PS cache here is mainly in memory, remember, you can view MemoryCache)
 EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
 if (cached != null) {
 // If so, use the current cached resources directly.
 cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
 if (Log.isLoggable(TAG, Log.VERBOSE)) {
 logWithTimeAndKey("Loaded resource from cache", startTime, key);
 }
 return null;
 }

 // This is a cache reference of L2 memory. It simply uses a map < key, WeakReference < engineresource <? > > > Loaded.
 // Who will put this cache in? You can refer to the loadFromCache method of the first level memory cache above.
 EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
 if (active != null) {
 cb.onResourceReady(active, DataSource.MEMORY_CACHE);
 if (Log.isLoggable(TAG, Log.VERBOSE)) {
 logWithTimeAndKey("Loaded resource from active resources", startTime, key);
 }
 return null;
 }

 // Get the cached job according to the key.
 EngineJob current = jobs.get(key);
 if (current != null) {
 current.addCallback(cb); // Add Callback to current job
 if (Log.isLoggable(TAG, Log.VERBOSE)) {
 logWithTimeAndKey("Added to existing load", startTime, key);
 }
 return new LoadStatus(cb, current);
 }

 // Create job
 EngineJob<R> engineJob = engineJobFactory.build(key, isMemoryCacheable);
 DecodeJob<R> decodeJob = decodeJobFactory.build(
 glideContext,
 model,
 key,
 signature,
 width,
 height,
 resourceClass,
 transcodeClass,
 priority,
 diskCacheStrategy,
 transformations,
 isTransformationRequired,
 options,
 engineJob);
 jobs.put(key, engineJob);
 engineJob.addCallback(cb);
 // Put it into the thread pool and execute
 engineJob.start(decodeJob);

 if (Log.isLoggable(TAG, Log.VERBOSE)) {
 logWithTimeAndKey("Started new load", startTime, key);
 }
 return new LoadStatus(cb, engineJob);
 }

There are some noteworthy points above:

  1. Memory cache: in Glide, the default is LruResourceCache. Of course, you can also customize;
  2. Why load from active resources. My personal understanding is that the L1 cache uses LRU algorithm for caching, which can not guarantee all hits. Adding L2 cache can improve the hit rate;
  3. Responsibilities of EngineJob and DecodeJob: EngineJob acts as the manager and dispatcher, mainly responsible for loading and various callback notifications; DecodeJob is a real worker. This class implements the Runnable interface.

Let's take a look at how the DecodeJob is executed:

private void runWrapped() {
 switch (runReason) {
 case INITIALIZE:
 // Initialize to get the status of the next stage
 stage = getNextStage(Stage.INITIALIZE);
 currentGenerator = getNextGenerator();
 // function
 runGenerators();
 break;
 case SWITCH_TO_SOURCE_SERVICE:
 runGenerators();
 break;
 case DECODE_DATA:
 decodeFromRetrievedData();
 break;
 default:
 throw new IllegalStateException("Unrecognized run reason: " + runReason);
 }
 }

// The phase strategy here is to first find from resource, then data, and then source
private Stage getNextStage(Stage current) {
 switch (current) {
 case INITIALIZE: 
 // Go back to the next state according to the defined cache policy
 // The cache policy comes from the requestOptions field of RequestBuilder
 // If you have a custom policy, you can call the RequestBuilder.apply method
 // See DiskCacheStrategy.java for detailed available cache policies
 return diskCacheStrategy.decodeCachedResource()
 ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
# last

**Share with you a mobile architecture outline, which contains all the technical systems that mobile architects need to master. You can compare your shortcomings or deficiencies and learn and improve in a direction;**

**[CodeChina Open source projects:< Android Summary of study notes+Mobile architecture video+Real interview questions for large factories+Project practice source code](https://codechina.csdn.net/m0_60958482/android_p7)**

witch (current) {
 case INITIALIZE: 
 // Go back to the next state according to the defined cache policy
 // The cache policy comes from the requestOptions field of RequestBuilder
 // If you have a custom policy, you can call the RequestBuilder.apply method
 // See DiskCacheStrategy.java for detailed available cache policies
 return diskCacheStrategy.decodeCachedResource()
 ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
# last

**Share with you a mobile architecture outline, which contains all the technical systems that mobile architects need to master. You can compare your shortcomings or deficiencies and learn and improve in a direction;**

**[CodeChina Open source projects:< Android Summary of study notes+Mobile architecture video+Real interview questions for large factories+Project practice source code](https://codechina.csdn.net/m0_60958482/android_p7)**

![](https://img-blog.csdnimg.cn/img_convert/3edb485195f9359dd0787cdb6c91008f.png)

Topics: Android Design Pattern Interview