Glide source code analysis

Posted by brenda on Tue, 04 Feb 2020 11:04:54 +0100

Article directory

.into(*)

This method is very complex, you have to look at the source code several times. ——Lu Xun

Take. into(ImageView) for example, let's see what this method does:

  public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    BaseRequestOptions<?> requestOptions = this;
  	//Omit validation and some setup code
    ...
    return into(
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions,
        Executors.mainThreadExecutor());
  }

1,Target

When you see the above source code, you will finally call another overloaded method:
private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener<TranscodeType> targetListener, BaseRequestOptions<?> options, Executor callbackExecutor)
We only look at the first parameter, which is a generic type. This parameter is assigned by glidcontext.buildimageviewtarget (view, transcodeclass):

  public <X> ViewTarget<ImageView, X> buildImageViewTarget(
      @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
      //We traced the origin of transcodeClass, when this type was called.load() before.
      //The default value is Drawable.class
    return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
  }
  
  public <Z> ViewTarget<ImageView, Z> buildTarget(@NonNull ImageView view,
      @NonNull Class<Z> clazz) {
    if (Bitmap.class.equals(clazz)) {
      return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
    } else if (Drawable.class.isAssignableFrom(clazz)) {
      //Because we already know it's Drawable.class, we'll take this branch
      return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
    } else {
      throw new IllegalArgumentException(
          "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
    }
  }

It can be seen that glidcontext.buildimageviewtarget (view, transcodeclass) creates a Target, and deeply explores the DrawableImageViewTarget. We find that this class has a life cycle, including view instances, requests, and request result types.

2,Request

To continue tracing overloaded into() methods:

  private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      BaseRequestOptions<?> options,
      Executor callbackExecutor) {
    ......
	//Create a Request instance
    Request request = buildRequest(target, targetListener, options, callbackExecutor);

    ......

    requestManager.clear(target);
    //As mentioned before, Target contains a request, which is initialized here.
    target.setRequest(request);
    requestManager.track(target, request);

    return target;
  }

We directly track buildRequest(target, targetListener, options, callbackExecutor). Because of the deep involvement, I show it in the form of pseudo code:

If (wrong request set){
	Var requestmain = if (thumbnail set){
		Initialize a request with thumbnails
	}else{
		Initialize a request
	}
	var requestError = initialize a request (including the error request)
	return requestMain + requestError / / can be seen as a combination of functionality
}else{
	Var requestmain = if (thumbnail set){
		Initialize a request with thumbnails
	}else{
		Initialize a request
	}
	return requestMain 
}

There are three kinds of requests: 1. Main request (for normal network picture request); 2. Thumbnail request; 3. Error request.
Through the above method, a combined request will be generated (forgive my straightforward explanation). Why is it called combined? Because these functions exist separately, one of our requests can contain these three functions, and the execution of these three functions is in order, through:

  public void setRequests(Request primary, Request error) {
    this.primary = primary;
    this.error = error;
  }

This method specifies which request is in the front and which request is in the back.
The most important point: the method returns the request object, which is an interface. The implementation of these request objects is of SingleRequest type.

Let's quickly make a summary here: the request object has been generated through the above line of code. The request object may also contain error request and thumbnail request, but it is a complete request object anyway. The next step is to see how it performs the request.

3,track()

In the overloaded method of into(), we will continue to look at the following three lines of code after completing the Request:

	requestManager.clear(target);	//1
    target.setRequest(request);	//2
    requestManager.track(target, request);	//3
  public synchronized void clear(@Nullable final Target<?> target) {
    if (target == null) {
      return;
    }

    untrackOrDelegate(target);
  }

  synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
    targetTracker.track(target);
    requestTracker.runRequest(request);
  }
  1. According to our current method, target's request must be null, so this line of code is useless.
  2. It is to add request to target.
  3. The name of the method is: trace. When you enter the method, there are only two lines of code. They are targetTracker object to trace target and requestTracker object to trace request. Obviously, there must be a big article here!
3.1 life cycle

targetTracker.track(target); this line of code is simple:

  public void track(@NonNull Target<?> target) {
    targets.add(target);
  }

Just add the target to a Set set, and then look at the code in the TargetTracker:

  @Override
  public void onStart() {
    for (Target<?> target : Util.getSnapshot(targets)) {
      target.onStart();
    }
  }

  @Override
  public void onStop() {
    for (Target<?> target : Util.getSnapshot(targets)) {
      target.onStop();
    }
  }

  @Override
  public void onDestroy() {
    for (Target<?> target : Util.getSnapshot(targets)) {
      target.onDestroy();
    }
  }

At first glance, it is used to manage the life cycle of target objects. Let's see where TargetTracker has been used? There is only one place, in RequestManager:
private final TargetTracker targetTracker = new TargetTracker();

Directly as a property of RequestManager, and RequestManager also contains lifecycle methods (because it implements the lifecycle listener interface):

  //Only one is listed here
  @Override
  public synchronized void onStart() {
    resumeRequests();
    targetTracker.onStart();
  }

And RequestManager is the property of SupportRequestManagerFragment. In order to prohibit dolling, we first repeatedly:

  1. When initializing Glide, if the non applicationContext is passed in by. with(*), a Fragment will be created. If the Fragment is directly associated with the Activity, the Fragment will sense the life cycle of the Activity.
  2. According to our idea, when the lifecycle of Fragment changes, you can directly call the lifecycle method corresponding to RequestManager in Fragment (for example: requestManager.onStart()).
  3. In the life cycle method corresponding to RequestManager, the life cycle method of its TargetTracker can also be called.

therefore

Of course, the second and third of the above three points are my personal conjectures

In fact, if you do this, it is also very feasible, but people's Glide is to think more thoughtful!
The conjecture in the second and third points above is basically correct, but someone else's Glide added another layer: activity fragment lifecycle (you think I'm the second layer, but actually I'm the fifth layer)

In SupportRequestManagerFragment, it does not directly invoke the RequestManager lifecycle method in the lifecycle method (so the above second points are a little bit of a problem). Instead, it calls the ActivityFragmentLifecycle lifecycle method:

  @Override
  public void onStart() {
    super.onStart();
    lifecycle.onStart();
  }

Let's see where the activity fragment lifecycle is initialized:

  public SupportRequestManagerFragment() {
    this(new ActivityFragmentLifecycle());
  }

  @VisibleForTesting
  @SuppressLint("ValidFragment")
  public SupportRequestManagerFragment(@NonNull ActivityFragmentLifecycle lifecycle) {
    this.lifecycle = lifecycle;
  }

It turns out that it's a direct new, which is easy to do. Let's take a look at the life cycle method of this object

  void onStart() {
    isStarted = true;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onStart();
    }
  }

  @Override
  public void addListener(@NonNull LifecycleListener listener) {
    lifecycleListeners.add(listener);
	...
  }

Do you understand why you need to add another layer of activity fragment lifecycle? It is convenient to manage monitoring of multiple life cycles at the same time.
We also found the key code in RequestManager:
If in the main thread, execute directly:
lifecycle.addListener(this);
If it is in a non main thread, it will cause the handler post to execute a task in the main thread:
lifecycle.addListener(RequestManager.this);

This is the end of lifecycle management.

3.2 requestTracker.runRequest(Request)

Let's follow the main task~

  public void runRequest(@NonNull Request request) {
    requests.add(request);
    if (!isPaused) {
      request.begin();
    } else {
      request.clear();
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Paused, delaying request");
      }
      pendingRequests.add(request);
    }
  }

When the life cycle is onStart(), isPaused = false will be called, so go to request.begin(), and remember that the request is SingleRequest:

  @Override
  public synchronized void begin() {
    //Omit validation and other codes
    ...
    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {	//1
      onSizeReady(overrideWidth, overrideHeight);	//3
    } else {
      target.getSize(this);	//2
    }
    //Omit relevant code of Placeholder
    ...
  }
  1. This line of code is to verify whether the width and height of the picture are set. The default width and height = UNSET=-1. If we do not set the width and height, we will go 2
  2. The target here is the target interface. Do you remember which is the specific implementation? This is the DrawableImageViewTarget. We find this method layer by layer:
  public void getSize(@NonNull SizeReadyCallback cb) {
    sizeDeterminer.getSize(cb);
  }

  void getSize(@NonNull SizeReadyCallback cb) {
      int currentWidth = getTargetWidth();
      int currentHeight = getTargetHeight();
      if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
        cb.onSizeReady(currentWidth, currentHeight);
        return;
      }
	  ...
    }

Here you will get the width and height of ImageView, and verify whether it is greater than 0. If it is greater than 0, call back the onSizeReady() method of SizeReadyCallback interface.

  1. If we set the width and height manually, the onSizeReady() method will still be called.

The key clue comes to onSIzeReady():

  @Override
  public synchronized void onSizeReady(int width, int height) {
    //Omit validation and other codes
    ...
    status = Status.RUNNING;

    ...
    
    loadStatus =
        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);
    ...
  }

The engine that hasn't appeared for a long time appears. As an engine, it must play a driving role. We continue to track its implementation:

  public synchronized <R> LoadStatus load(
      ...//Omit various parameters
      ) {
    ...
	//Check whether there is data from memory or cache. Suppose we get data for the first time, these are null
    ...

    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);	//1

    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);	//2

    jobs.put(key, engineJob);

    engineJob.addCallback(cb, callbackExecutor);	//3
    engineJob.start(decodeJob);	//4
	...
  }
  1. Create an engineJob
  2. Create a decodeJob
  3. Add two callbacks to the engineJob (focus on the first cb)
  4. Start Job

4,DecodeJob.run()

From the above analysis, we can directly see engineJob.start(decodeJob):

  public synchronized void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }
  //Select a thread pool, and execute the DecodeJob, indicating that the DecodeJob implements the Runnable interface,
  //Look directly at the run method of DecodeJob:
  public void run() {
    //Omit validation and other codes
    ...
    try {
      if (isCancelled) {
        notifyFailed();
        return;
      }
      runWrapped();
    } catch (CallbackException e) {
      throw e;
    } catch (Throwable t) {
      ...
    } finally {
      ...
    }
  }

Go out of irrelevant code and come to runWrapped():

  private void runWrapped() {
    switch (runReason) { //1. First runReason == INITIALIZE
      case INITIALIZE:
        stage = getNextStage(Stage.INITIALIZE);
        currentGenerator = getNextGenerator();
        runGenerators();
        break;
      ...
    }
  }

At the beginning, runReason == INITIALIZE, let's continue to see getNextStage(INITIALIZE):

  private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
      ...
    }
  }

diskCacheStrategy.decodeCachedResource() returns true by default, so the returned stage is Stage.RESOURCE_CACHE. Next, go to getNextGenerator():

  private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE:
        return new ResourceCacheGenerator(decodeHelper, this);
      ...
  }

This step is to initialize currentGenerator, and finally call runGenerators():

  private void runGenerators() {
    ...
    boolean isStarted = false;	//1
    while (!isCancelled && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {	//2
      stage = getNextStage(stage);	//3.1
      currentGenerator = getNextGenerator();	//3.2

      if (stage == Stage.SOURCE) {	
        reschedule();
        return;
      }
    }
    ...
  }
  1. A flag that defines whether or not to start.
  2. Normally, if the cancel() method is not called, isCancelled == false,
    The currentGenerator has just been initialized. It cannot be null,
    Finally, call currentGenerator.startNext():
  public boolean startNext() {
  	//If it is the first time to load a picture, the sourceIds must not have. Return false directly
    List<Key> sourceIds = helper.getCacheKeys();
    if (sourceIds.isEmpty()) {
      return false;
    }
    ...
  }

How do you understand this line of code? What it means is simple:
If there is a current generator in the normal process of requesting pictures, if the current generator can't find the pictures, go through the process of 3.1 and 3.2, assign values to the next stage and the current generator, and then find the pictures.

According to this idea, suppose we load pictures for the first time, and there are no pictures in the memory cache. Finally, stage == Stage.SOURCE, currentGenerator == SourceGenerator(), and then go to SourceGenerator.startNext() (in the middle, the reschedule() method is used to assign runReason, which does not affect the subsequent process).

5,ModelLoader

Next, take a look at the implementation of SourceGenerator.startNext():

  @Override
  public boolean startNext() {
    //Omit cache related code
    ...
    
    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {	//1
      loadData = helper.getLoadData().get(loadDataListIndex++);	//2
      if (loadData != null
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
          || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
        started = true;
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }

  1. Let's take a look at hasnextmoderloader()
  private boolean hasNextModelLoader() {
  	//The initial loadDataListIndex is 0
    return loadDataListIndex < helper.getLoadData().size();
  }
  1. Also called helper.getLoadData()

So I'm very responsible to tell you that the focus of this section is on the helper.getLoadData() method, and it is also the decryption of the previous chapter. How can I pass a type to find the corresponding implementation?
The source code will not be put here. The call relationship is a little complex and the work is relatively simple. So oral + some key source codes are displayed:
Do you remember the model? At that time, when we called. load(*), we passed in the URL of a String type image, which was saved as a model. As a ModelLoader, we can guess from the name that this thing must be related to various overloaded methods of. load(*).
The helper.getLoadData() method is to find the corresponding ModelLoader through the model type, and these modelloaders are defined in their construction methods when they first generate Glide instances:

//These are String related
registry.append(String.class, InputStream.class, new DataUrlLoader.StreamFactory<String>())
        .append(String.class, InputStream.class, new StringLoader.StreamFactory())
        .append(String.class, ParcelFileDescriptor.class, new StringLoader.FileDescriptorFactory())
        .append(
            String.class, AssetFileDescriptor.class, new StringLoader.AssetFileDescriptorFactory())
//These are Uri related
		.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
        .append(Uri.class, InputStream.class, new AssetUriLoader.StreamFactory(context.getAssets()))
//There are many other examples not listed here, such as File, Bitmap, Drawable, etc

The append() method has three parameters: the first is the type of * when calling. load(*); the second is the type returned after data loading; and the third is the specific ModelLoader class.

Because the String type is passed in, there are four ModelLoader classes in total. Apart from some judgment and filtering in the middle, there are still three. In fact, all three are StringLoader, but there are three internal factory classes in this class, which are generated into differentiated StringLoader objects.

After getting the ModelLoader set, transform the model into LoadData through its method buildLoadData(), while the buildLoadData() of StringLoader is not directly returned to LoadData, the middle winding finally calls HttpGlideUrlLoader.buildLoadData():

  @Override
  public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height,
      @NonNull Options options) {
    GlideUrl url = model;
    if (modelCache != null) {
      url = modelCache.get(model, 0, 0);
      if (url == null) {
        modelCache.put(model, 0, 0, model);
        url = model;
      }
    }
    int timeout = options.get(TIMEOUT);
    return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
  }

After getting the loadData, you can request the data:
loadData.fetcher.loadData(helper.getPriority(), this);

6,HttpUrlFetcher

loadData will use the catcher to call the loadData() method, and the catcher is an interface, which is implemented in HttpGlideUrlLoader. It is httpurlcatcher:

  @Override
  public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    ...
    try {
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      callback.onDataReady(result);
    } catch (IOException e) {
      ...
      callback.onLoadFailed(e);
    } finally {
      ...
    }
  }

It's almost the same here. The loadDataWithRedirects() method is the method of network data request, and the code in it will not be released. It's the ordinary HttpURLConnection request network, and then it returns an input stream, which is a picture.
Then the data is passed to onDataReady() method through callback, which is implemented in the previous SourceGenerator:

  @Override
  public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      dataToCache = data;
      cb.reschedule();
    } else {
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }

You will assign dataToCache here and then call cb.reschedule();

7, cache

cb.reschedule(); passing layer by layer will return to calling startNext() (previously hidden code):

  public boolean startNext() {
    //dataToCache was null before, so I didn't go
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }
    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
	...
  }

  private void cacheData(Object dataToCache) {
    ...
    try {
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache); //1
      DataCacheWriter<Object> writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      helper.getDiskCache().put(originalKey, writer);	//2
     ...
    } finally {
      loadData.fetcher.cleanup();
    }
   sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);	//3
  }

Here is the function of calling cacheData() to cache pictures.

  1. It is similar to the previous search for ModelLoader and decoder. Glide will add several decoders during initialization. getSourceEncoder(*) will get the Encoder according to the type of value passed. Before, dataToCache was of InputStream type, so the decoder obtained was StreamEncoder
  2. helper.getDiskCache() will get the DiskLruCacheWrapper, and then perform LRU caching.
  3. Initialize * * sourceCacheGenerator * *.

The execution continues in the startNext() method:

    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }

  @Override
  public boolean startNext() {
    while (modelLoaders == null || !hasNextModelLoader()) {
      sourceIdIndex++;
      if (sourceIdIndex >= cacheKeys.size()) {
        return false;
      }

      Key sourceId = cacheKeys.get(sourceIdIndex);
     
      @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
      cacheFile = helper.getDiskCache().get(originalKey);
      if (cacheFile != null) {
        this.sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      loadData =
          modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
              helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }

8. Load picture

The above operation is to cache, and the display image is actually a key line of code:
loadData.fetcher.loadData(helper.getPriority(), this);
Where loadData.fetcher is ByteBufferFileLoader, and its method source code is:

    @Override
    public void loadData(@NonNull Priority priority,
        @NonNull DataCallback<? super ByteBuffer> callback) {
      ByteBuffer result;
      try {
        result = ByteBufferUtil.fromFile(file);
      } catch (IOException e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "Failed to obtain ByteBuffer for file", e);
        }
        callback.onLoadFailed(e);
        return;
      }

      callback.onDataReady(result);
    }

  @Override
  public void onDataReady(Object data) {
    cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey);
  }

And then to DecodeJob:

  @Override
  public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
      DataSource dataSource, Key attemptedKey) {
    this.currentSourceKey = sourceKey;
    this.currentData = data;
    this.currentFetcher = fetcher;
    this.currentDataSource = dataSource;
    this.currentAttemptingKey = attemptedKey;
    if (Thread.currentThread() != currentThread) {
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
        decodeFromRetrievedData();
      } finally {
        GlideTrace.endSection();
      }
    }
  }

In this method, assign runreason = runreason.decode? Data again; call callback.reschedule(this); return to the original runWrapped() method again and take another branch:

  private void runWrapped() {
    switch (runReason) {
      ...
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;
      ...
    }
  }

Decode in the decodeFromRetrievedData method, and then call back to inform EnginJob to finish loading the picture. The key code in EnginJob is:

for (final ResourceCallbackAndExecutor entry : copy) {
      entry.executor.execute(new CallResourceReady(entry.cb));
    }

It is also a thread pool execution method. Look directly at CallResourceReady's run() method:

    @Override
    public void run() {
      synchronized (EngineJob.this) {
        if (cbs.contains(cb)) {
          engineResource.acquire();
          callCallbackOnResourceReady(cb);
          removeCallback(cb);
        }
        decrementPendingCallbacks();
      }
    }
  }

  @Synthetic
  synchronized void callCallbackOnResourceReady(ResourceCallback cb) {
    try {
      cb.onResourceReady(engineResource, dataSource);
    } catch (Throwable t) {
      throw new CallbackException(t);
    }
  }

Finally, the onResourceReady() method is called back. We can find the role of this cb layer by layer. It turns out to be SingleRequest, and then look at its method implementation. The key is to call the onResourceReady() method:

  private synchronized void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
    // We must call isFirstReadyResource before setting status.
    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 (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);
        target.onResourceReady(result, animation);
      }
    } finally {
      isCallingCallbacks = false;
    }

    notifyLoadSuccess();
  }

The most important one is target.onResourceReady(result, animation); do you remember what the target is in this line of code? This is DrawableImageViewTarget. Let's go to its implementation:

@Override
  protected void setResource(@Nullable Drawable resource) {
    view.setImageDrawable(resource);
  }

Just one line of code, finally loaded the image to ImageView!

summary

  1. First, create the target object, which contains the reference of imageView, the type of image, and a Request(SingleRequest).
  2. Create a Request and bind it to the Target. This Request is all requests, including normal picture Request, abnormal picture Request and thumbnail Request.
  3. Put the Target into the collection related to the management life cycle and start executing the Request
  4. Create an EnginJob and a DecodeJob.
  5. In the DecodeJob, the resource image or network request resource will be removed from the cache, and then it will be cached and decoded.
  6. Finally, callback layer by layer, and return to Request to make Target load resource image to ImageView.

Final personal summary

  1. Why did you write this article?
    In April 2019, I entered a very small company. The senior of the company stayed with me for only four days, and he left. During this period, he taught me how to improve myself and recommended many methods, such as reading Mr. Guo Lin's blog, and Play Android Website ah, especially he recommended to see the source code.
    But as time goes on, for learning, I just go to Google's official website to learn about Jetpack. I go to github ctrl c+v if I have any problems in my work. It's very easy to use these things, but these are very simple things.
    Finally one day, in December 2019, I left my last company. At the beginning, I was very confident that I could find a good job. However, in the interview process, when I asked some source code related questions, I always used to practice stupidly or perfunctorily.
    All these reasons make me want to sink in and really analyze the source code of a tripartite framework.
  2. About Glide
    This series of articles is not very detailed, because Glide itself has a lot of content, this series of articles is just to help understand Glide.
    I hope you can learn something from it.
  3. My harvest
    After a series of articles, my personal summary ability has been improved a lot, and my ability to watch the source code has also been improved a little, so I am very confident that in the next work, I can quickly integrate into the development.
    This is my first source code series of articles, which is a milestone in commemoration. I hope that I can make further efforts to produce more high-quality articles for your reference.
Published 8 original articles, won praise 4, visited 2363
Private letter follow

Topics: Fragment network Android Google