Glide talking about Glide, from English literally means Glide, sliding meaning; And Android from the perspective of development we know that it is a picture loading framework, here quoted from the official document of a sentence “Glide is a fast and efficient Android picture loading library, focusing on smooth rolling”, from the official document we know that using Glide framework to load pictures is fast and efficient, Let’s see if Glide is fast and efficient by simply using Glide and understanding the source code (the code in this article is based on Glide version 4.8).

Glide Simple use

  • 1. Add a dependency before using it

    implementation 'com. Making. Bumptech. Glide: glide: 4.8.0'// To use the Generated API, you need to introduce annotationProcessor'com. Making. Bumptech. Glide: the compiler: 4.8.0'
    Copy the code
  • 2. Simply load the network image to ImageView, you can see a simple line of code to load the network image to ImageView, also can use the Generated API

    Glide. With (Context).load(IMAGE_URL).into(mImageView) // Create MyAppGlideModule with @glidemodule annotation. Make project public final class MyAppGlideModule extends AppGlideModule {} //Generated GlideApp. With (Context).load(IMAGE_URL).into(mImageView);Copy the code
  • 3. When loading the network image, the network request is a time-consuming operation, so the image cannot be loaded immediately. During the network request, the ImageView is blank, so we can use a placeholder to display the image to optimize the user experience

    • Loading placeholders
    • Error placeholders
    • Fallback
    // add placeholder map RequestOptions RequestOptions = new RequestOptions().placeholder(r.rawable.ic_cloud_download_black_24dp) .error(R.drawable.ic_error_black_24dp) .diskCacheStrategy(DiskCacheStrategy.NONE); Glide.with(Context).load(IMAGE_URL).apply(requestOptions).into(mImageView); //Generated API (same as Glide3) glideapp. with(Context).load(IMAGE_URL) .placeholder(R.drawable.ic_cloud_download_black_24dp) .error(R.drawable.ic_error_black_24dp) .diskCacheStrategy(DiskCacheStrategy.NONE) .into(mImageView); // The Fallback API is Generated. In the scenario where the user profile picture is not set, null is Generated. Private static Final String NULL_URL=null; GlideApp.with(Context).load(NULL_URL) .fallback(R.drawable.ic_account_circle_black_24dp) .into(mImageView);Copy the code

  • 4. Specify the size of the image to load (override)

    RequestOptions = new RequestOptions(). Override (200,100); Glide.with(Context).load(IMAGE_URL).apply(requestOptions).into(mImageView); //Generated API GlideApp. With (Context).load(IMAGE_URL).override(200,100).into(mImageView);Copy the code
  • 5. Thumbnail

    • This is actually a bit like placeholder, but placeholders can only load local resources, whereas thumbnails can load network resources, and the thumbnail method runs in parallel with our active load, so if the active load is done, the thumbnail doesn’t show
    RequestOptions = new RequestOptions(). Override (200,100) .diskCacheStrategy(DiskCacheStrategy.NONE); Glide.with(Context) .load(IMAGE_URL) .thumbnail( Glide.with(this) .load(IMAGE_URL) .apply(requestOptions)) .into(mImageView); //Generated API GlideApp. With (Context).load (IMAGE_URL). Thumbnail (GlideApp. With (this) The load (IMAGE_URL). Override (200100). DiskCacheStrategy (diskCacheStrategy. NONE).) into (mImageView);Copy the code
  • 6. Image changes

    • Glide has three image change operations built into it, CenterCrop (the central area of the original image is cropped and displayed), FitCenter (the length and width of the original image are covered) and CircleCrop (circle cropped).

      RequestOptions = new RequestOptions().circlecrop () .diskCacheStrategy(DiskCacheStrategy.NONE); Glide.with(Context) .load(IMAGE_URL) .apply(requestOptions) .into(mImageView); / / RequestOptions have built-in user Glide. The three changes of static methods with (Context). The load (IMAGE_URL). Apply (RequestOptions. CircleCropTransform ()) .into(mImageView); //Generated API glideapp.with (Context).load(IMAGE_URL).circlecrop ().diskCacheStrategy(diskCacheStrategy.none) .into(mImageView);Copy the code
    • To make things even more exciting, you can use a third-party framework called Glide – Doubling, and you can make combinations of changes

      // Make a quick reference to everything'jp. Wasabeef: glide - transformations: 4.0.0'// Use glide- to change the colors and add a blur effect .placeholder(R.drawable.ic_cloud_download_black_24dp) .transforms(new ColorFilterTransformation(Color.argb(80, 255, 0, 0)),new BlurTransformation(30)) .diskCacheStrategy(DiskCacheStrategy.NONE); Glide.with(Context).load(IMAGE_URL). apply(requestOptions). into(mImageView); / / Generated API way GlideApp. With (Context). The load (IMAGE_URL) transforms (new ColorFilterTransformation (. Color argb (80, 255, 0, 0)),new BlurTransformation(30)) .placeholder(R.drawable.ic_cloud_download_black_24dp) .diskCacheStrategy(DiskCacheStrategy.NONE) .into(mImageView);Copy the code

    • See the official example for more effects

  • 7. Load the Target

    • Target is the intermediary between the request and the requester, and the return value of into is the Target object. We’ve been using into(ImageView), which is actually a helper method, It takes an ImageView parameter and wraps an appropriate ImageViewTarget for the resource type it requests

      // Target<Drawable> Target = Glide. With (Context).load(url). Into (new Target<Drawable>() {// Target<Drawable> Target = Glide. }); Glide. With (Context). Clear (target);Copy the code
    • When using Notification to display application notifications, if we want to customize the Notification interface, we need to use RemoteView. If we want to setImageView to RemoteView, according to the provided setImageViewBitmap method, If the notification interface needs to load the network picture, it needs to convert the network picture into bitmap. Generally, we can convert the network picture into bitmap according to the flow of obtaining the picture link, or use Glide framework using the topic of this paper. These are time-consuming operations and feel very troublesome to operate. The Glide framework was kind enough to give me NotificationTarget(inheriting SimpleTarget), which becomes Notification relative to our loading target

    /** * create a NotificationTarget object. Glide4's asBitmap() method must precede the load method * @param context context object * @param viewId The ID of the view to which the ImageView is loaded * @param remoteViews RemoteView object * @param notification Notification object * @param notificationId Notification Id */ String iamgeUrl ="http://p1.music.126.net/fX0HfPMAHJ2L_UeJWsL7ig==/18853325881511874.jpg?param=130y130"; NotificationTarget notificationTarget = new NotificationTarget(mContext,R.id.notification_Image_play,mRemoteViews,mNotification,notifyId); Glide.with(mContext.getApplicationContext()) .asBitmap() .load(iamgeUrl) .into( notificationTarget ); / / Generated API way GlideApp. With (mContext. GetApplicationContext ()). AsBitmap (.) the load (iamgeUrl.) into (notificationTarget );Copy the code

  • 8. Callback listening

    • Glide loads an image, although there is a placeholder method for loading or failing, but we still want to know whether the image has been loaded successfully or failed. Glide also provides a listening method to know whether the image has been loaded successfully or failed, using a callback in combination with the listener and into methods
    Glide.with(this).load(IMAGE_URL).
                  listener(new RequestListener<Drawable>() {
                      @Override
                      public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                     Toast.makeText(getApplicationContext(),"Image load failed",Toast.LENGTH_SHORT).show();
                          return false;
                      }
    
                      @Override
                      public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                     Toast.makeText(getApplicationContext(),"Image loaded successfully",Toast.LENGTH_SHORT).show();
                          return false; } }).into(mImageView); */ /Generated API glideapp.with (this).load(IMAGE_URL).listener(new RequestListener<Drawable>() {@override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) { Toast.makeText(getApplicationContext(),"Image load failed",Toast.LENGTH_SHORT).show();
                          return false;
                      }
    
                      @Override
                      public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                          Toast.makeText(getApplicationContext(),"Image loaded successfully",Toast.LENGTH_SHORT).show();
                          return false;
                      }
                  }).into(mImageView);
    Copy the code
    • If the onResourceReady method returns true, the event will not be processed. If the onResourceReady method returns true, the into method will not be executed, that is, the image will not be loaded into the ImageView. Similarly, if the onLoadFailed method returns true, the error method will not be executed.

Glide has some other use methods, here will not continue to develop, interested can continue to study.

Glide source code analysis

Glide load image to ImageView basic flowchart

Glide loads images into ImageView source code analysis

  • The last section briefly listed some ways to use Glide. Does it mean you already know it? Next, we will try to understand how Glide works by understanding the source code
Glide.with(Context).load(IMAGE_URL).into(mImageView);
Copy the code

With the method

  • Glide with() method, directly on the source
Public static RequestManager with(@nonnull Context Context) {return getRetriever(context).get(context);
  }

  @NonNull
  public static RequestManager with(@NonNull Activity activity) {
    return getRetriever(activity).get(activity);
  }

  @NonNull
  public static RequestManager with(@NonNull FragmentActivity activity) {
    return getRetriever(activity).get(activity);
  }

  @NonNull
  public static RequestManager with(@NonNull Fragment fragment) {
    return getRetriever(fragment.getActivity()).get(fragment);
  }

  @SuppressWarnings("deprecation")
  @Deprecated
  @NonNull
  public static RequestManager with(@NonNull android.app.Fragment fragment) {
    return getRetriever(fragment.getActivity()).get(fragment);
  }

  @NonNull
  public static RequestManager with(@NonNull View view) {
    return getRetriever(view.getContext()).get(view);
  }
Copy the code
  • In the source code, you can see that with has overloaded methods of different parameter types, each of which first calls the getRetriever() method
Private static RequestManagerRetriever getRetriever(@retrielable Context Context) {// Context could be nullfor other reasons (ie the user passes in null), but in practice it will
    // only occur due to errors with the Fragment lifecycle.
    Preconditions.checkNotNull(
        context,
        "You cannot start a load on a not yet attached View or a Fragment where getActivity() "
            + "returns null (which usually occurs when getActivity() is called before the Fragment "
            + "is attached or after the Fragment is destroyed).");
    return Glide.get(context).getRequestManagerRetriever();
  }
Copy the code
  • Glide in the get method through new GlideBuilder () to obtain the Glide object, and through the Glide getRequestManagerRetriever () method and eventually get RequestManagerRetriever object, Next, look at the get method on the RequestManagerRetriever object
@nonnull 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() && ! (context instanceof Application)) {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()); }}return getApplicationManager(context);
  }

  @NonNull
  public RequestManager get(@NonNull FragmentActivity activity) {
    if (Util.isOnBackgroundThread()) {
      return get(activity.getApplicationContext());
    } else {
      assertNotDestroyed(activity);
      FragmentManager fm = activity.getSupportFragmentManager();
      return supportFragmentGet(
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }

  @NonNull
  public RequestManager get(@NonNull Fragment fragment) {
    Preconditions.checkNotNull(fragment.getActivity(),
          "You cannot start a load on a fragment before it is attached or after it is destroyed");
    if (Util.isOnBackgroundThread()) {
      return get(fragment.getActivity().getApplicationContext());
    } else {
      FragmentManager fm = fragment.getChildFragmentManager();
      return supportFragmentGet(fragment.getActivity(), fm, fragment, fragment.isVisible());
    }
  }

  @SuppressWarnings("deprecation")
  @NonNull
  public RequestManager get(@NonNull Activity activity) {
    if (Util.isOnBackgroundThread()) {
      return get(activity.getApplicationContext());
    } else {
      assertNotDestroyed(activity);
      android.app.FragmentManager fm = activity.getFragmentManager();
      return fragmentGet(
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }

  @SuppressWarnings("deprecation")
  @NonNull
  public RequestManager get(@NonNull View view) {
    if (Util.isOnBackgroundThread()) {
      return get(view.getContext().getApplicationContext());
    }
    Preconditions.checkNotNull(view);
    Preconditions.checkNotNull(view.getContext(),
        "Unable to obtain a request manager for a view without a Context");
    Activity activity = findActivity(view.getContext());
    // The view might be somewhere else, like a service.
    if (activity == null) {
      return get(view.getContext().getApplicationContext());
    }

    // Support Fragments.
    // Although the user might have non-support Fragments attached to FragmentActivity, searching
    // for non-support Fragments is so expensive pre O and that should be rare enough that we
    // prefer to just fall back to the Activity directly.
    if (activity instanceof FragmentActivity) {
      Fragment fragment = findSupportFragment(view, (FragmentActivity) activity);
      returnfragment ! = null ? get(fragment) : get(activity); } // Standard Fragments. android.app.Fragment fragment = findFragment(view, activity);if (fragment == null) {
      return get(activity);
    }
    return get(fragment);
  }
Copy the code
  • In addition, the Get methods in the RequestManagerRetriever retriever also have overloads for different types of parameters, including Application, Activity, Fragmenet, and View. In this method, it divides the Context parameters into two types, an Application-type Context and a non-Application Context. If the Context is of Application type, the life cycle of Glide created follows the life cycle of ApplicationContext, which is what the getApplicationManager does below.
/** The getApplicationManager() method of the RequestManagerRetriever class */ @nonNULL Context context) { // Either an application context or we're on a background thread. if (applicationManager == null) { synchronized (this) { if (applicationManager == null) { //  Normally pause/resume is taken care of by the fragment we add to the fragment or // activity. However, in this case since the manager attached to the application will not // receive lifecycle events, we must force the manager to start resumed using // ApplicationLifecycle. // TODO(b/27524013): Factor out this Glide.get() call. Glide glide = Glide.get(context.getApplicationContext()); applicationManager = factory.build( glide, new ApplicationLifecycle(), new EmptyRequestManagerTreeNode(), context.getApplicationContext()); } } } return applicationManager; }Copy the code
  • Then, if non-Application, activities and Fragmenet are non-applications; If the Context is an Activity type, and it is not currently the main thread, continue to follow the Application life cycle. Otherwise, add a hidden Fragment to the current Activity, and Glide life cycle follows the hidden Fragment. If we look at the Fragmenet type Context, or the View type, we also add a hidden Fragment. Why is that? First of all, the Fragment life cycle is synchronized with the Activity, and the Fragment will be destroyed when the Activity is destroyed. Second, it’s easy for Glide to know when it needs to stop loading. If we start an Activity and close it, If Glide’s life cycle follows the Application, the Activity has been destroyed, but the Application has not yet quit, and Glide continues to load images, this is obviously not reasonable, and Glide is very clever to use a hidden Fragment to solve the life cycle monitoring.
/** RequestManagerRetriever Retriever fragmentGet() method */ @suppressWarnings ({"deprecation"."DeprecatedIsStillUsed"})
  @Deprecated
  @NonNull
  private RequestManager fragmentGet(@NonNull Context context,
      @NonNull android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      // TODO(b/27524013): Factor out this Glide.get() call.
      Glide glide = Glide.get(context);
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      current.setRequestManager(requestManager);
    }
    returnrequestManager; GetRequestManagerFragment RequestManagerRetriever} / * * class * / @ SuppressWarnings () method ("deprecation")
  @NonNull
  private RequestManagerFragment getRequestManagerFragment(
      @NonNull final android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
    if (current == null) {
      current = pendingRequestManagerFragments.get(fm);
      if (current == null) {
        current = new RequestManagerFragment();
        current.setParentFragmentHint(parentHint);
        if(isParentVisible) { current.getGlideLifecycle().onStart(); } pendingRequestManagerFragments.put(fm, current); fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(); }}return current;
  }
Copy the code
  • After analyzing the into method, you end up with a RequestManager object that follows the lifecycle of the corresponding Context object.

The load method

  • Glide. With (RequestManager) ¶
*/ @nonnull @checkResult public <ResourceType> RequestBuilder<ResourceType> as(@nonnull) Class<ResourceType> resourceClass) {returnnew RequestBuilder<>(glide, this, resourceClass, context); } /** RequestManager class as() */ @checkResult public RequestBuilder<Drawable>asDrawable() {
    returnas(Drawable.class); } @checkResult@override public RequestBuilder<Drawable> load(@nullable) Bitmap bitmap) {return asDrawable().load(bitmap);
  }
  @NonNull
  @CheckResult
  @Override
  public RequestBuilder<Drawable> load(@Nullable Drawable drawable) {
    return asDrawable().load(drawable);
  }

  @NonNull
  @CheckResult
  @Override
  public RequestBuilder<Drawable> load(@Nullable String string) {
    returnasDrawable().load(string); } // omit other argument types load() method.......Copy the code
  • The load method of the RequestManager object is overloaded with multiple type parameters, but it is the load method that calls the RequestBuilder object regardless of the type parameter
/** RequestBuilder load() */ @nonnull @checkResult @suppressWarnings ("unchecked")
  @Override
  public RequestBuilder<TranscodeType> load(@Nullable Object model) {
    returnloadGeneric(model); } /** @nonnull private RequestBuilder<TranscodeType> loadGeneric(@nullable Object model) { this.model = model; isModelSet =true;
    return this;
  }
Copy the code
  • The load() method of the RequestBuilder is used as an Object regardless of the type of resource parameter that the load() method of the RequestManager passes. Assign to the Model object of the RequestBuilder object in the loadGeneric method.
  • By looking at the RequestBuilder object, we also notice the apply(RequestOptions) method, which in our previous example uses caching, loading image sizes, setting loading placeholders and error placeholders to create a new RequestOptions object and set our configuration. RequestOptions. Clone () is used by Glide to load the default configuration. We will not expand it here, but we will continue to focus on the into method.
*/ @nonnull @checkResult public RequestBuilder<TranscodeType> apply(@nonnull RequestOptions requestOptions) { Preconditions.checkNotNull(requestOptions); this.requestOptions = getMutableOptions().apply(requestOptions);return this;
  }
 
  @SuppressWarnings("ReferenceEquality")
  @NonNull
  protected RequestOptions getMutableOptions() {
    return defaultRequestOptions == this.requestOptions
        ? this.requestOptions.clone() : this.requestOptions;
  }
Copy the code
  • After the with() method and the load method, we get the RequestBuilder object, which means that the real loading operation is done in the into method, which is the RequestBuilder object.

Into method

  • From the analysis of the previous section, the object acquired after the load method is the RequestBuilder, and we assign the parameters of the Load method to the Model parameter of the RequestBuilder object, and then come to the most core method Glide. This is the into method of the RequestBuilder object
Get DrawableImageViewTarget
/** public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) { Util.assertMainThread(); Preconditions.checkNotNull(view); RequestOptions requestOptions = this.requestOptions;if(! requestOptions.isTransformationSet() && requestOptions.isTransformationAllowed() && view.getScaleType() ! = null) { // Clonein this method so that if we use this RequestBuilder to load into a View and then
      // into a different target, we don't retain the transformation applied based on the previous // View's scale type.
      switch (view.getScaleType()) {
        case CENTER_CROP:
          requestOptions = requestOptions.clone().optionalCenterCrop();
          break;
        case CENTER_INSIDE:
          requestOptions = requestOptions.clone().optionalCenterInside();
          break;
        case FIT_CENTER:
        case FIT_START:
        case FIT_END:
          requestOptions = requestOptions.clone().optionalFitCenter();
          break;
        case FIT_XY:
          requestOptions = requestOptions.clone().optionalCenterInside();
          break;
        case CENTER:
        case MATRIX:
        default:
          // Do nothing.
      }
    }

    return into(
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions);
  }
Copy the code
  • The RequestBuilder into method first gets the ScaleType of the ImageView passed in, and lets Glide load the ImageView with the same ScaleType change. This method returns the RequestBuilder object into another method, see first glideContext. BuildImageViewTarget () to do the operation
/** GlideContext buildImageViewTarget method */ @nonnull public <X> ViewTarget<ImageView, X> buildImageViewTarget( @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
  }
  public class ImageViewTargetFactory {
  @NonNull
  @SuppressWarnings("unchecked")
  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)) {
      return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
    } else{// omit the code..... }}}Copy the code
  • The buildImageViewTarget () method returns a DrawableImageViewTarget object. The buildImageViewTarget () method returns a DrawableImageViewTarget object. So let’s move on to the first step: into returns what did into do
Build Request
Private <Y extends Target<TranscodeType>> Y into(@nonnull Y Target, @Nullable RequestListener<TranscodeType> targetListener, @NonNull RequestOptions options) { Util.assertMainThread(); Preconditions.checkNotNull(target);if(! isModelSet) { throw new IllegalArgumentException("You must call #load() before calling #into()"); } options = options.autoClone(); Request request = buildRequest(target, targetListener, options); // omit some code...... requestManager.clear(target); target.setRequest(request); requestManager.track(target, request);return target;
  }
Copy the code
  • The Request class is an interface, it is abstract Glide load image Request (Request class source code here will not paste), it is a very important class, The buildRequest(target, targetListener, options) method creates the Request object
 private Request buildRequest(
      Target<TranscodeType> target,
      @Nullable RequestListener<TranscodeType> targetListener,
      RequestOptions requestOptions) {
    returnbuildRequestRecursive( target, targetListener, /*parentCoordinator=*/ null, transitionOptions, requestOptions.getPriority(), requestOptions.getOverrideWidth(), requestOptions.getOverrideHeight(), requestOptions); } private Request buildRequestRecursive( Target<TranscodeType> target, @Nullable RequestListener<TranscodeType> targetListener, @Nullable RequestCoordinator parentCoordinator, TransitionOptions<? ,? super TranscodeType> transitionOptions, Priority priority, int overrideWidth, int overrideHeight, RequestOptions RequestOptions) {// Omit some code error Request build..... Request mainRequest = buildThumbnailRequestRecursive( target, targetListener, parentCoordinator, transitionOptions, priority, overrideWidth, overrideHeight, requestOptions);if (errorRequestCoordinator == null) {
      returnmainRequest; } // Omit some code error Request build..... } private Request buildThumbnailRequestRecursive( Target<TranscodeType> target, RequestListener<TranscodeType> targetListener, @Nullable RequestCoordinator parentCoordinator, TransitionOptions<? ,? super TranscodeType> transitionOptions, Priority priority, int overrideWidth, int overrideHeight, RequestOptions requestOptions) {if(thumbnailBuilder ! = null) {// omit part of code thumbnail operation..... }else {
      // Base case: no thumbnail.
      return obtainRequest(
          target,
          targetListener,
          requestOptions,
          parentCoordinator,
          transitionOptions,
          priority,
          overrideWidth,
          overrideHeight);
    }
  }

  private Request obtainRequest(
      Target<TranscodeType> target,
      RequestListener<TranscodeType> targetListener,
      RequestOptions requestOptions,
      RequestCoordinator requestCoordinator,
      TransitionOptions<?, ? super TranscodeType> transitionOptions,
      Priority priority,
      int overrideWidth,
      int overrideHeight) {
    return SingleRequest.obtain(
        context,
        glideContext,
        model,
        transcodeClass,
        requestOptions,
        overrideWidth,
        overrideHeight,
        priority,
        target,
        targetListener,
        requestListeners,
        requestCoordinator,
        glideContext.getEngine(),
        transitionOptions.getTransitionFactory());
  }
Copy the code
  • From the above source, we can see that the buildRequest method calls the buildRequestRecursive method. In the buildRequestRecursive method, most of the code handles the thumbnail, which is not set in the main process. The buildRequestRecursive method then calls the obtainRequest method. The obtainRequest method passes a lot of parameters, such as RequestOptions, OverrideWidth, overrideHeight, and the target object in the first into method, which is the DrawableImageViewTarget object, model which is the address of the image that we load, So that means that whatever parameters are passed in by the load method or the apply method ends up here and we call it SingleRequest. Obtain, so let’s move on to the SingleRequest class
public final class SingleRequest<R> implements Request, SizeReadyCallback, ResourceCallback, Factorypools. Poolable {// omit part of the code...... Public static <R> SingleRequest<R> obtain(Context Context, GlideContext, Object model, Class<R> transcodeClass, RequestOptions 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) { @SuppressWarnings("unchecked") SingleRequest<R> request =
        (SingleRequest<R>) POOL.acquire();
    if (request == null) {
      request = new SingleRequest<>();
    }
    request.init(
        context,
        glideContext,
        model,
        transcodeClass,
        requestOptions,
        overrideWidth,
        overrideHeight,
        priority,
        target,
        targetListener,
        requestListeners,
        requestCoordinator,
        engine,
        animationFactory);
    returnrequest; } // omit some code...... }Copy the code
  • Using the obtain method of the SingleRequest object, we can see that request = new SingleRequest<>(); The Request that we end up building is the SingleRequest object and assigning the various parameters passed in from the obtainRequest method in the previous step in the init method.
Request execution
  • Now that the Request object is built, we can continue with what we did with the into method
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target
Copy the code
  • The target is cleared from the RequestManager object after the first with method, and the SingleRequest object is set to target. We then executed the requestManager.track method to follow up on the method
Void track(@nonnull Target<? > target, @NonNull Request request) { targetTracker.track(target); requestTracker.runRequest(request); } /** Private final List<Request> pendingRequests = new ArrayList<>(); 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); }}Copy the code
  • The track method of the RequestManager object executes the runRequest method of the RequestTracker class. This method simply determines whether the current Glide is in the suspended state. If it is not in the suspended state, the begin method of the Request is executed. Otherwise add the Request to the ListpendingRequests queue.
Fallback callbacks, load placeholders, and error placeholder loads
  • Now let’s look at what the Request begin method does. To find the Request begin implementation, we should look at the SingleRequest object’s BEGIN method
/** SingleRequest begin */ @override public voidbegin() {
    assertNotCallingCallbacks();
    stateVerifier.throwIfRecycled();
    startTime = LogTime.getLogTime();
    if (model == null) {
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        width = overrideWidth;
        height = overrideHeight;
      }
      int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
      onLoadFailed(new GlideException("Received null model"), logLevel);
      return;
    }

    if (status == Status.RUNNING) {
      throw new IllegalArgumentException("Cannot restart a running request");
    }
    if (status == Status.COMPLETE) {
      onResourceReady(resource, DataSource.MEMORY_CACHE);
      return;
    }
    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      onSizeReady(overrideWidth, overrideHeight);
    } else {
      target.getSize(this);
    }
    if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
        && canNotifyStatusChanged()) {
      target.onLoadStarted(getPlaceholderDrawable());
    }
    if (IS_VERBOSE_LOGGABLE) {
      logV("finished run method in "+ LogTime.getElapsedMillis(startTime)); }} private void onLoadFailed(GlideException e, int maxLogLevel) {// omit some code.......if(! anyListenerHandledUpdatingTarget) {setErrorPlaceholder(); } // omit some code....... }Copy the code
  • If the model is empty and the image address we load is empty, we call onLoadFailed which in turn calls setErrorPlaceholder. So what does that do
/ * * SingleRequest classsetErrorPlaceholder method */ private voidsetErrorPlaceholder() {
    if(! canNotifyStatusChanged()) {return;
    }
    Drawable error = null;
    if (model == null) {
      error = getFallbackDrawable();
    }
    // Either the model isn't null, or there was no fallback drawable set. if (error == null) { error = getErrorDrawable(); } // The model isn't null, no fallback drawable was set or no error drawable was set.
    if (error == null) {
      error = getPlaceholderDrawable();
    }
    target.onLoadFailed(error);
  }
  
  private Drawable getErrorDrawable() {
    if (errorDrawable == null) {
      errorDrawable = requestOptions.getErrorPlaceholder();
      if(errorDrawable == null && requestOptions.getErrorId() > 0) { errorDrawable = loadDrawable(requestOptions.getErrorId()); }}return errorDrawable;
  }
Copy the code
  • If we pass in an empty image address, we first check to see if there is a back-up callback, then an error placeholder, then a loading placeholder, and finally call the target.onLoadFailed method, which is ImageViewTarget’s onLoadFailed method
public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z>implements transition. ViewAdapter {// omit some code....... public voidsetDrawable(Drawable drawable) {
    view.setImageDrawable(drawable);
  }
  
  @Override
  public void onLoadStarted(@Nullable Drawable placeholder) {
    super.onLoadStarted(placeholder);
    setResourceInternal(null);
    setDrawable(placeholder);
  }

  @Override
  public void onLoadFailed(@Nullable Drawable errorDrawable) {
    super.onLoadFailed(errorDrawable);
    setResourceInternal(null);
    setDrawable(errorDrawable); } // omit some code....... }Copy the code
  • From the above source code, I think you should have understood the fallback callback, error placeholder loading, here is a question, loading placeholder? Let’s go back to the SingleRequest begin method. I’m sure you’ll immediately see that target.onLoadStarted was called while the loading state was RUNNING. At this point we have analyzed the fallback callbacks, loading placeholders, and error placeholder loading underlying implementation logic.
Load the picture network request
  • After we have finished our analysis of the placeholder implementation, we return to the begin method of the SingleRequest object. We can notice that onSizeReady() and target.getSize() are the entry points for loading images. If we set the image loading size with Glide, Target.getsize () is called
Void getSize(@nonnull SizeReadyCallback cb) {int currentWidth = getTargetWidth(); int currentHeight = getTargetHeight();if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
        cb.onSizeReady(currentWidth, currentHeight);
        return; } // omit some code...... }Copy the code
  • Target.getsize () calls the onSizeReady() method based on the ImageView’s width and height. So let’s go straight to the SingleRequest object and see what’s happening in the onSizeReady() method.
/** Override public void onSizeReady(int width, int height) { stateVerifier.throwIfRecycled();if (IS_VERBOSE_LOGGABLE) {
      logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
    if(status ! = Status.WAITING_FOR_SIZE) {return;
    }
    status = Status.RUNNING;

    float sizeMultiplier = requestOptions.getSizeMultiplier();
    this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
    this.height = maybeApplySizeMultiplier(height, sizeMultiplier);

    if (IS_VERBOSE_LOGGABLE) {
      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.isScaleOnlyOrNoTransform(),
        requestOptions.getOptions(),
        requestOptions.isMemoryCacheable(),
        requestOptions.getUseUnlimitedSourceGeneratorsPool(),
        requestOptions.getUseAnimationPool(),
        requestOptions.getOnlyRetrieveFromCache(),
        this);

    // This is a hack that's only useful for testing right now where loads complete synchronously // even though under any executor running on any  thread but the main thread, the load would // have completed asynchronously. if (status ! = Status.RUNNING) { loadStatus = null; } if (IS_VERBOSE_LOGGABLE) { logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime)); }}Copy the code
  • The onSizeReady method mainly calls engine.load() and returns the load state. Engine.load continues to receive the various parameters we passed in earlier, including the model object, which is the image address passed in earlier load method. First of all, we need to understand what engine is, as the name implies, engine English meaning is the engine, and Glide framework he is responsible for the start of the picture load engine, mainly responsible for the start of the load, we get the engine object in the previous method with Glide object (here will not paste the source code), Now what does the engine.load() method do
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,boolean isScaleOnlyOrNoTransform,Options options,boolean isMemoryCacheable,boolean useUnlimitedSourceExecutorPool,boolean useAnimationPool,boolean onlyRetrieveFromCache,ResourceCallback cb) { Util.assertMainThread(); long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0; EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options); EngineResource<? > active = loadFromActiveResources(key, isMemoryCacheable);if(active ! = null) { cb.onResourceReady(active, DataSource.MEMORY_CACHE);if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      returnnull; } EngineResource<? > cached = loadFromCache(key, isMemoryCacheable);if(cached ! = null) { cb.onResourceReady(cached, DataSource.MEMORY_CACHE);if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      returnnull; } EngineJob<? > current = jobs.get(key, onlyRetrieveFromCache);if(current ! = null) { current.addCallback(cb);if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

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

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

    jobs.put(key, engineJob);

    engineJob.addCallback(cb);
    engineJob.start(decodeJob);

    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    returnnew LoadStatus(cb, engineJob); } / DecodeJob class hierarchy of * * * / class DecodeJob < R > implements DataFetcherGenerator. FetcherReadyCallback, Runnable, Comparable<DecodeJob<? >>, PoolableCopy the code
  • DecodeJob (Engine) : DecodeJob (Engine) : DecodeJob (Engine) : DecodeJob (Engine) : DecodeJob (Engine) : DecodeJob The DecodeJob object is a Runnable object. The DecodeJob object is a Runnable object. The DecodeJob object is a Runnable object
Public void start(DecodeJob<R> DecodeJob) {this. DecodeJob = DecodeJob; GlideExecutor executor = decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor(); executor.execute(decodeJob); } /** newSourceExecutor method of the GlideExecutor class */ public static GlideExecutor newSourceExecutor(int threadCount, int threadCount) String name, UncaughtThrowableStrategy uncaughtThrowableStrategy) {return new GlideExecutor(
        new ThreadPoolExecutor(
            threadCount /* corePoolSize */,
            threadCount /* maximumPoolSize */,
            0 /* keepAliveTime */,
            TimeUnit.MILLISECONDS,
            new PriorityBlockingQueue<Runnable>(),
            new DefaultThreadFactory(name, uncaughtThrowableStrategy, false)));
  }  
Copy the code
  • GlideExecutor is an Executor of the EngineJob object. It can be used to create a thread pool. It can be used to create a thread pool. There is also the resource thread pool (SourceExecutor), which is posted from the source code above. DecodeJob starts a Runnable object in the thread pool. The main function of EngineJob is to start a thread to load images.
/** DecodeJob run method */ public voidrun() { DataFetcher<? >localFetcher = currentFetcher;
    try {
      if (isCancelled) {
        notifyFailed();
        return; } runWrapped(); } catch (Throwable t) {// omit some code....... } finally {if (localFetcher ! = null) {localFetcher.cleanup(); } GlideTrace.endSection(); }} /** DecodeJob runWrapped method */ private voidrunWrapped() {
    switch (runReason) {
      case INITIALIZE:
        stage = getNextStage(Stage.INITIALIZE);
        currentGenerator = getNextGenerator();
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: "+ runReason); }} private Stage getNextStage(Stage current) {switch (current) {private Stage getNextStage(Stage current) {case INITIALIZE:
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        // Skip loading from source if the user opted to only retrieve the resource from cache.
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: "+ current); } /** DecodeJob class getNextGenerator */ private DataFetcherGeneratorgetNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE:
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE:
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE:
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: "+ stage); } /** DecodeJob class runGenerators method */ private voidrunGenerators() {
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;
    while(! isCancelled && currentGenerator ! = null && ! (isStarted = currentGenerator.startNext())) { stage = getNextStage(stage); currentGenerator = getNextGenerator();if (stage == Stage.SOURCE) {
        reschedule();
        return; }} // omit some code....... }Copy the code
  • DecodeJob DecodeJob DecodeJob DecodeJob DecodeJob DecodeJob DecodeJob runWrapped Stage.initialize (); stage.initialize (); stage.initialize (); Then in the getNextGenerator() method we get the SourceGenerator object, which is the first sentence in the run method DataFetcher
    localFetcher = currentFetcher where localFetcher is the SourceGenerator object we just got, continue with the runGenerators() method, In this method the while loop judgment conditions performed currentGenerator. StartNext () method, namely the SourceGenerator object startNext () method
/** SourceGenerator startNext() */ @override public BooleanstartNext() {// omit some code, skip the cache part judgment........ loadData = null; boolean started =false;
    while(! started && hasNextModelLoader()) { loadData = helper.getLoadData().get(loadDataListIndex++);if(loadData ! = null && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource()) || helper.hasLoadPath(loadData.fetcher.getDataClass()))) { started =true; loadData.fetcher.loadData(helper.getPriority(), this); }}returnstarted; } /** DecodeHelper getLoadData() */ List<LoadData<? >>getLoadData() {
    if(! isLoadDataSet) { isLoadDataSet =true; loadData.clear(); List<ModelLoader<Object, ? >> modelLoaders = glideContext.getRegistry().getModelLoaders(model); //noinspection ForLoopReplaceableByForEach to improve perffor(int i = 0, size = modelLoaders.size(); i < size; i++) { ModelLoader<Object, ? > modelLoader = modelLoaders.get(i); LoadData<? > current = modelLoader.buildLoadData(model, width, height, options);if(current ! = null) { loadData.add(current); }}}returnloadData; } /** 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);
    return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
  } 
Copy the code
  • LoadData = helper.getloaddata ().get(loadDataListIndex++) loadData= helper.getloaddata ().get(loadDataListIndex++) The DecodeJob was created when we created the DecodeJob object. The model we passed in the load step was the image URL, so the DecodeHelper getLoadData() method (we won’t expand the finer code here) Finally get ModelLoader
    modelLoader is HttpGlideUrlLoader, laodData, So modelLoader. BuildLoadData created in buildLoadData HttpGlideUrlLoader object implementation, the method of posted above the source of our model assigned to GlideUrl object, namely as a URL address for processing, Past the modelLoader. BuildLoadData obtain the loadData. Fetcher corresponding HttpUrlFetcher object, So the loadData. Fetcher. LoadData call object is HttpUrlFetcher loadData method,,?>
@override public void loadData(@nonnull Priority Priority, @nonnull DataCallback<? super InputStream> callback) { long startTime = LogTime.getLogTime(); try { InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders()); callback.onDataReady(result); } catch (IOException e) {if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
    } finally {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished http url fetcher fetch in "+ LogTime.getElapsedMillis(startTime)); }}} private InputStream loadDataWithRedirects(URL URL, int redirects, URL lastUrl, Map<String, String> headers) throws IOException {if (redirects >= MAXIMUM_REDIRECTS) {
      throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
    } else {
      // 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 HttpException("In re-direct loop");

        }
      } catch (URISyntaxException e) {
        // Do nothing, this is best effort.
      }
    }

    urlConnection = connectionFactory.build(url);
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
    urlConnection.setConnectTimeout(timeout);
    urlConnection.setReadTimeout(timeout);
    urlConnection.setUseCaches(false);
    urlConnection.setDoInput(true); // 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)) { return getStreamForSuccessfulRequest(urlConnection); } else if (isHttpRedirect(statusCode)) { String redirectUrlString = urlConnection.getHeaderField("Location"); if (TextUtils.isEmpty(redirectUrlString)) { throw new HttpException("Received empty or null redirect url"); } URL redirectUrl = new URL(url, redirectUrlString); // Closing the stream specifically is required to avoid leaking ResponseBodys in addition // to disconnecting the url connection below. See #2352. cleanup(); return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers); } else if (statusCode == INVALID_STATUS_CODE) { throw new HttpException(statusCode); } else { throw new HttpException(urlConnection.getResponseMessage(), statusCode); }}Copy the code
  • The loadData method of HttpUrlFetcher calls “loadDataWithRedirects”. HttpURLConnection () See here heart or a little happy, in front of so many source code, finally see Glide network request, happy after it is not over, still have to continue to look down, the execution of the network request success, GetStreamForSuccessfulRequest loadDataWithRedirects method in the network request is successful call returns an InputStream flow (remember the InputStream, critical), and then performs a callback callbacks, The callback object is the loadData method we called on the SourceGenerator object and passed in the SourceGenerator object itself, So callback.onDataready () calls the onDataReady method of the SourceGenerator object
@override public void onDataReady(Object data) {DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();if(data ! = null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) { dataToCache = data; // We might be being called back on someoneelse's thread. Before doing anything, we should // reschedule to get back onto Glide's thread.
      cb.reschedule();
    } else{ cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher, loadData.fetcher.getDataSource(), originalKey); }}Copy the code
  • Call cb.onDatafetCherReady (cB.onDatafetCherReady, cB.onDatafetCherReady); That is, call the DecodeJob object onDataFetcherReady
/**DecodeJob 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(); }}}Copy the code
  • Using the above source code, the onDataFetcherReady method assigns the stream obtained from the previous network request to the currentData of the current DecodeJob object and all other data to the corresponding fields, and finally calls the decodeFromRetrievedData method
Loading images (decoding, transcoding)
/**DecodeJob decodeFromRetrievedData method **/ private voiddecodeFromRetrievedData() {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Retrieved data", startFetchTime,
          "data: " + currentData
              + ", cache key: " + currentSourceKey
              + ", fetcher: " + currentFetcher);
    }
    Resource<R> resource = null;
    try {
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    if(resource ! = null) { notifyEncodeAndRelease(resource, currentDataSource); }else{ runGenerators(); Private <Data> Resource<R> decodeFromData(DataFetcher<? > fetcher, Data data, DataSource dataSource) throws GlideException { try {if (data == null) {
        return null;
      }
      long startTime = LogTime.getLogTime();
      Resource<R> result = decodeFromFetcher(data, dataSource);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Decoded result " + result, startTime);
      }
      returnresult; } finally { fetcher.cleanup(); /** private <Data> Resource<R> decodeFromFetcher(Data Data, Data Data) DataSource dataSource) throws GlideException { LoadPath<Data, ? , R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());return runLoadPath(data, dataSource, path);
  }
Copy the code
  • Using the above source code, the decodeFromRetrievedData method calls the decodeFromFetcher method, in which the LoadPath object is first retrieved through the decodeHelper.getLoadPath, The LoadPath object actually returns a specific data decoder transcoding handler based on the processing data we passed in. Let’s follow up decodeHelper.getLoadPath to see if we can decode it
/** DecodeHelper <Data> LoadPath<Data,? , Transcode> getLoadPath(Class<Data> dataClass) {returnglideContext.getRegistry().getLoadPath(dataClass, resourceClass, transcodeClass); Nullable public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath( @NonNull Class<Data> dataClass, @NonNull Class<TResource> resourceClass, @nonnull Class<Transcode> transcodeClass) {// omit some code....... List<DecodePath<Data, TResource, Transcode>> decodePaths = getDecodePaths(dataClass, resourceClass, transcodeClass);if (decodePaths.isEmpty()) {
        result = null;
      } else {
        result =
            new LoadPath<>(
                dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool);
      }
      loadPathCache.put(dataClass, resourceClass, transcodeClass, result);
    }
    returnresult; } @nonnull private <Data, TResource, Transcode> List<DecodePath<Data, TResource, Transcode>> getDecodePaths( @NonNull Class<Data> dataClass, @NonNull Class<TResource> resourceClass, @nonnull Class<Transcode> transcodeClass) { Remove interference List < ResourceDecoder < Data, TResource > > decoders. = decoderRegistry getDecoders (dataClass registeredResourceClass); ResourceTranscoder<TResource, Transcode> transcoder = transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass); @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
        DecodePath<Data, TResource, Transcode> path =
            new DecodePath<>(dataClass, registeredResourceClass, registeredTranscodeClass,
                decoders, transcoder, throwableListPool);
        decodePaths.add(path);
    }
    returndecodePaths; Public synchronized <T, R> List<ResourceDecoder<T, R>> getDecoders(@NonNull Class<T> dataClass, @NonNull Class<R> resourceClass) { List<ResourceDecoder<T, R>> result = new ArrayList<>();for(String bucket : bucketPriorityList) { List<Entry<? ,? >> entries = decoders.get(bucket);if (entries == null) {
        continue;
      }
      for(Entry<? ,? > entry : entries) {if (entry.handles(dataClass, resourceClass)) {
          result.add((ResourceDecoder<T, R>) entry.decoder);
        }
      }
    }
    // TODO: cache result list.

    return result;
  } 
Copy the code
  • Decodehelper. getLoadPath method, it calls the Registry object getLoadPath method, Registry object getLoadPath method and call its own getDecodePaths method, Now as I mentioned earlier, our network request is an InputStream, so the Data generic in the getDecodePaths method is InputStream. In accordance with getDecoders method traversal to get decoder ResourceDecoder can handle InputStream stream has StreamBitmapDecoder and StreamGifDecoder, StreamGifDecoder processing is Gif, StreamBitmapDecoder, which decodes InputStream into bitmaps, And then the ResourceTranscoder that can convert a Bitmap to a Drawable is a BitmapDrawableTranscoder, Finally, getDecodePaths passes the decoder and transcoder we just analyzed to our newly created DecodePath object, which is used to help us decode and transcode.
  • We continue with the decodeFromFetcher method of the previous step, which returns runLoadPath that finally calls the decode method of the DecodePath object obtained above
Public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height, @NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException { Resource<ResourceType> decoded = decodeResource(rewinder,  width, height, options); Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);returntranscoder.transcode(transformed, options); } /** private Resource<ResourceType> decodeResource(DataRewinder<DataType> rewinder, int width, int height, @NonNull Options options) throws GlideException { List<Throwable> exceptions = Preconditions.checkNotNull(listPool.acquire()); try {returndecodeResourceWithList(rewinder, width, height, options, exceptions); } finally { listPool.release(exceptions); } /** private Resource<ResourceType> decodeResourceWithList(DataRewinder<DataType> rewinder, int width, int height, @NonNull Options options, List<Throwable> exceptions) throws GlideException { Resource<ResourceType> result = null; // omit some code........ ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i); try { DataType data = rewinder.rewindAndGet();if(decoder.handles(data, options)) { data = rewinder.rewindAndGet(); result = decoder.decode(data, width, height, options); } // omit some code........return result;
  }  
Copy the code
  • Through the above source code, DecodePath object decode method called decodeResource method, decodeResource and call decodeResourceWithList method, after the previous analysis, Decoder obtained in decodeResourceWithList method is the StreamBitmapDecoder object mentioned above, so we continue to look at the Decode method of StreamBitmapDecoder
Override public Resource<Bitmap> decode(@nonnull InputStream)source, int width, int height,
      @NonNull Options options)
      throws IOException {

    // Use to fix the mark limit to avoid allocating buffers that fit entire images.
    final RecyclableBufferedInputStream bufferedStream;
    final boolean ownsBufferedStream;
    if (source instanceof RecyclableBufferedInputStream) {
      bufferedStream = (RecyclableBufferedInputStream) source;
      ownsBufferedStream = false;
    } else {
      bufferedStream = new RecyclableBufferedInputStream(source, byteArrayPool);
      ownsBufferedStream = true;
    }

    ExceptionCatchingInputStream exceptionStream =
        ExceptionCatchingInputStream.obtain(bufferedStream);
    MarkEnforcingInputStream invalidatingStream = new MarkEnforcingInputStream(exceptionStream);
    UntrustedCallbacks callbacks = new UntrustedCallbacks(bufferedStream, exceptionStream);
    try {
      return downsampler.decode(invalidatingStream, width, height, options, callbacks);
    } finally {
      exceptionStream.release();
      if(ownsBufferedStream) { bufferedStream.release(); }}}Copy the code
  • Downsampler.decode (); downSampler.decode (); downSampler.decode (); The downsampler object is the downsampler object. Downsamples, decodes, and rotates images according to their exif orientation.), the English annotation roughly means sampling, decoding, and rotating images in the exif format. Instead, we call its decode method, which decodes the stream we wrapped earlier
Public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight, Options options, DecodeCallbacks throws IOException {Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions, downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth, requestedHeight, fixBitmapToRequestedDimensions, callbacks);returnBitmapResource.obtain(result, bitmapPool); } finally { releaseOptions(bitmapFactoryOptions); byteArrayPool.put(bytesForOptions); } /**Downsampler decodeFromWrappedStreams(InputStream is, BitmapFactory.Options options, DownsampleStrategy downsampleStrategy, DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth, int requestedHeight, boolean fixBitmapToRequestedDimensions, Throws IOException {// omit part of the code......... Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool); callbacks.onDecodeComplete(bitmapPool, downsampled); // omit some code......... Bitmap rotated = null;if(downsampled ! = null) {/ / zoom effect is processing downsampled setDensity (displayMetrics. DensityDpi); rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation);if (!downsampled.equals(rotated)) {
        bitmapPool.put(downsampled);
      }
    }
    returnrotated; } /** Private static Bitmap decodeStream(InputStream is, bitmapFactory. Options Options, DecodeCallbacks callbacks, BitmapPool bitmapPool) throws IOException {if (options.inJustDecodeBounds) {
      is.mark(MARK_POSITION);
    } else {
      callbacks.onObtainBounds();
    }
    int sourceWidth = options.outWidth;
    int sourceHeight = options.outHeight; String outMimeType = options.outMimeType; final Bitmap result; TransformationUtils.getBitmapDrawableLock().lock(); try { result = BitmapFactory.decodeStream(is, null, options); } catch (IllegalArgumentException e) {// Omit some code.........return result;
  }  
Copy the code
  • DecodeFromWrappedStreams Downsampler decodeFromWrappedStreams Downsampler decodeFromWrappedStreams Downsampler decodeFromWrappedStreams Downsampler decodeFromWrappedStreams Downsampler In this method calls the BitmapFactory. DecodeStream, here we will finally see the Glide InputStream stream parsing became a bitmap, The resulting Resource object returned by the decode method of the Downsampler object is the BitmapResource object
  • After the previous analysis, Glide has decoded InputStream completed, at this time we have to go back to decode decode DecodePath object method, decode completed also need to transcode, here again paste ecodePath object decode method, We’ve already analyzed that the transcoder is a BitmapDrawableTranscoder object, so let’s move on to the BitmapDrawableTranscoder object, what does Transcode do
Public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height, @NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException { Resource<ResourceType> decoded = decodeResource(rewinder,  width, height, options); Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);returntranscoder.transcode(transformed, options); } /**BitmapDrawableTranscoder class transcode method **/ public Resource<BitmapDrawable> transcode(@nonnull Resource<Bitmap> toTranscode, @NonNull Options options) {returnLazyBitmapDrawableResource.obtain(resources, toTranscode); } / * * * * obtain LazyBitmapDrawableResource class method / @ Nullable public static Resource < BitmapDrawable > obtain (@ NonNull Resources  resources, @Nullable Resource<Bitmap> bitmapResource) {if (bitmapResource == null) {
      return null;
    }
    return new LazyBitmapDrawableResource(resources, bitmapResource);
  }
Copy the code
  • Through the above source code, object BitmapDrawableTranscoder transcode method finally returned to the LazyBitmapDrawableResource object, And we decode BitmapResource object is transformed into LazyBitmapDrawableResource object

At this point, Glide is nearly done decoding and transcoding the entire image, and then we go back to the decodeFromRetrievedData method of the DecodeJob object

Picture shows
/**DecodeJob decodeFromRetrievedData method **/ private voiddecodeFromRetrievedData() {
    Resource<R> resource = null;
    try {
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    if(resource ! = null) { notifyEncodeAndRelease(resource, currentDataSource); }else{ runGenerators(); Private void notifyEncodeAndRelease(Resource<R> Resource) void notifyEncodeAndRelease(Resource<R> Resource) DataSource DataSource) {// omit some code..... Resource<R> result = resource; // omit some code..... notifyComplete(result, dataSource); stage = Stage.ENCODE; // omit some code..... } /**DecodeJob class notifyComplete **/ private void notifyComplete(Resource<R> Resource, DataSource DataSource) {setNotifiedOrThrow();
    callback.onResourceReady(resource, dataSource);
  }  
Copy the code
  • Using the above source code, the decodeFromRetrievedData method of the DecodeJob object calls notifyEncodeAndRelease, We step on to get LazyBitmapDrawableResource into the notifyComplete method, onResourceReady, in notifyComplete calls the callback. The callback object is the EngineJob object (which implements the DecodeJob.Callback interface). In case you’ve forgotten what the EngineJob object is, the start method is inside the EngineJob object. So let’s look at the onResourceReady method on the EngineJob object
private static final Handler MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper(), new MainThreadCallback()); /* Override public void onResourceReady(Resource<R> Resource, DataSource dataSource) { this.resource = resource; this.dataSource = dataSource; MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget(); } @override public Boolean handleMessage(Message Message) {EngineJob<? > job = (EngineJob<? >) message.obj; switch (message.what) {case MSG_COMPLETE:
          job.handleResultOnMainThread();
          break;
        case MSG_EXCEPTION:
          job.handleExceptionOnMainThread();
          break;
        case MSG_CANCELLED:
          job.handleCancelledOnMainThread();
          break;
        default:
          throw new IllegalStateException("Unrecognized message: " + message.what);
      }
      return true; } /** The EngineJob class handleResultOnMainThread method **/ @synthetic voidhandleResultOnMainThread() {// omit some code...... engineResource = engineResourceFactory.build(resource, isCacheable); hasResource =true; // omit some code......for (int i = 0, size = cbs.size(); i < size; i++) {
      ResourceCallback cb = cbs.get(i);
      if(! isInIgnoredCallbacks(cb)) { engineResource.acquire(); cb.onResourceReady(engineResource, dataSource); }} // omit some code...... } /** Private final List<ResourceCallback> CBS = new ArrayList<>(2); void addCallback(ResourceCallback cb) { Util.assertMainThread(); stateVerifier.throwIfRecycled();if (hasResource) {
      cb.onResourceReady(engineResource, dataSource);
    } else if (hasLoadFailed) {
      cb.onLoadFailed(exception);
    } else{ cbs.add(cb); }}Copy the code
  • Engine job addCallback (SingleRequest); EngineJob addCallback (SingleRequest); The onResourceReady method of the EngineJob object passes the loaded image object to the main thread via the Hanlder. So in the handleResultOnMainThread method based on the analysis we just did we call the data callback through cb.onResourceready, which is the SingleRequest object, Let’s move on to the onResourceReady callback method for the SingleRequest object
/**SingleRequest onResourceReady callback **/ @suppressWarnings ("unchecked") @Override public void onResourceReady(Resource<? > resource, DataSource DataSource) {// omit some code....... Object received = resource.get(); // omit some code....... onResourceReady((Resource<R>) resource, (R) received, dataSource); } /** private void onResourceReady(Resource<R> Resource, R result, DataSource DataSource) {// omit some code....... status = Status.COMPLETE; this.resource = resource; // omit some code.......if(! anyListenerHandledUpdatingTarget) { Transition<? super R> animation = animationFactory.build(dataSource, isFirstResource); target.onResourceReady(result, animation); }} // omit some code....... }Copy the code

The SingleRequest onResourceReady callback calls resource.get(). And the resource is in front of us after decoding, transcoding acquisition LazyBitmapDrawableResource object, then call the object SingleRequest onResourceReady private method, The target. OnResourceReady method is called. The target object that we created when we first went into the into method is the DrawableImageViewTarget object, which inherits from the abstract class ImageViewTarget, So let’s look at the onResourceReady method of the abstract class ImageViewTarget

/ * * LazyBitmapDrawableResource the get () method of a class * * / @ NonNull @ Override public BitmapDrawableget() {
    returnnew BitmapDrawable(resources, bitmapResource.get()); } /** Override public void onResourceReady(@nonnull Z resource, @Nullable Transition<? super Z> transition) {if(transition == null || ! transition.transition(resource, this)) {setResourceInternal(resource);
    } else{ maybeUpdateAnimatable(resource); }} /** ImageViewTarget classsetResourceInternal method **/ private voidsetResourceInternal(@Nullable Z resource) {
    // Order matters here. Set the resource first to make sure that the Drawable has a valid and
    // non-null Callback before starting it.
    setResource(resource);
    maybeUpdateAnimatable(resource);
  }
  protected abstract void setResource(@Nullable Z resource);
Copy the code
  • Through the above source code, object LazyBitmapDrawableResource get () method to obtain a BitmapDrawable (as actual Drawable object), The onResourceReady method of the ImageViewTarget object is called through the previous analysis, which in turn calls the setResourceInternal method of the ImageViewTarget object, The setResourceInternal method and finally the setResource method, setResource is an abstract method in the ImageViewTarget object, which is implemented in the DrawableImageViewTarget object, And finally, let’s look at the DrawableImageViewTarget object setResource method
 @Override
  protected void setResource(@Nullable Drawable resource) {
    view.setImageDrawable(resource);
  }
Copy the code

Here, our long march has finally come to an end, Glide’s simple loading picture process has been analyzed.

The final say

  • Finally, I want to post that simple code
Glide.with(Context).load(IMAGE_URL).into(mImageView);
Copy the code
  • It’s such a simple code, but the logic behind it is so creepy that I just want to say “read the fuck source code”. In front of us, we just analyzed Glide simple loading picture process, its cache use, callback and other function principle has not been analyzed, this can only wait until the next article. If there is any mistake in the article, please mention it to me, and we can learn and make progress together. If you think my article helps you, please also give me a like and attention, and welcome to visit my personal blog.

  • If you are interested in this article, please read on.

  • Refer to the link

    • Gldie Simplified Chinese document