Glide framework

www.cnblogs.com/billshen/p/…

Gilde is also simple to use:

Glide.with(content)
     .load(url)
     .into(imageView);
Copy the code

With specifies the binding life cycle, load specifies the loaded resource, and into specifies the loaded target

Glide frame principle

1, with() life cycle binding

1.1, with

The context passed in with determines the life cycle of Glide. If it is an activity, it is also destroyed when the activity is destroyed. If the ApplicationContext is passed in, it has the same life cycle as the APP.

1.2, the source code

Enter Glide source, we can see five with overload methods:

public static RequestManager with(Context context) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(context);
    }
   
    public static RequestManager with(Activity activity) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(activity);
    }
    
    public static RequestManager with(FragmentActivity activity) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(activity);
    }
 
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public static RequestManager with(android.app.Fragment fragment) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(fragment);
    }
 
    public static RequestManager with(Fragment fragment) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(fragment);
    }
Copy the code

Whatever the with overload method is called, it ends up here: Get (Activity Activity)

// Whichever with overload method is called will end up here
public RequestManager get(Activity activity) {
    if (Util.isOnBackgroundThread()) {
        return get(activity.getApplicationContext());
    } else {
        assertNotDestroyed(activity);
        // Main logic
        android.app.FragmentManager fm = activity.getFragmentManager();
        return fragmentGet(activity, fm, null); }}Copy the code

The main logic

// Main logic
android.app.FragmentManager fm = activity.getFragmentManager();
return fragmentGet(activity, fm, null);
Copy the code

Enter the **fragmentGet()** method

// Create a New RequestManagerFragment with no view
private RequestManager fragmentGet(Context context, android.app.FragmentManager fm, android.app.Fragment parentHint) {
    // Here we create a RequestManagerFragment object, which is clearly a Fragment.
    // Fragments can be tied to the Activity lifecycle;
    // There are two forms of Fragment, one is interface Fragment,
    // One type is unbounded Fragment. In this case, the RequestManagerFragment is an unbounded Fragment.
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
    
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) { Glide glide = Glide.get(context);// Bind Lifecycle to requestManager and Fragment
        requestManager = factory.build(glide, current.getGlideLifecycle(), 			                                            current.getRequestManagerTreeNode(), context);
        // This binds the RequestManagerFragment to the RequestManager (they are one-to-one bindings).
        // Therefore, another important function of the RequestManager is to listen for the life cycle of the Activity/Fragment interface.
        current.setRequestManager(requestManager);
    }
    return requestManager;
}
Copy the code

RequestManagerFragment. Class holds a lifecycle, in fragments into key life cycle will notify the lifecycle when relevant methods

public class RequestManagerFragment extends Fragment {...private finalActivityFragmentLifecycle lifecycle; .@Override
  public void onStart(a) {
    super.onStart();
    lifecycle.onStart();
  }

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

  @Override
  public void onDestroy(a) {
    super.onDestroy(); lifecycle.onDestroy(); }}Copy the code

ActivityFragmentLifecycle. Class holding a lifecycleListeners, key life cycle in fragments into the Lifecycle will inform all of his Listener

class ActivityFragmentLifecycle implements Lifecycle {...private final Set<LifecycleListener> lifecycleListeners;void onStart(a) {
    isStarted = true;
    for(LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) { lifecycleListener.onStart(); }}void onStop(a) {
    isStarted = false;
    for(LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) { lifecycleListener.onStop(); }}void onDestroy(a) {
    isDestroyed = true;
    for(LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) { lifecycleListener.onDestroy(); }}... }Copy the code

Load tasks are handled in the RequestManger. Class critical lifecycle

@Override
public void onStart(a) {
    resumeRequests();
    targetTracker.onStart();
}

@Override
public void onStop(a) {
    pauseRequests();
    targetTracker.onStop();
}

@Override
public void onDestroy(a) {
    targetTracker.onDestroy();
    for(Target<? > target : targetTracker.getAll()) { clear(target); } targetTracker.clear(); requestTracker.clearRequests(); }Copy the code
1.3,
  1. Glide is attached to the Activity’s life cycle.
  2. Create a new UI-free Fragment within the Activity and that particular Fragment holds a Lifecycle. Lifecycle notifies the RequestManger of relevant actions during the Fragment critical Lifecycle.
  3. The load continues at life cycle onStart, pauses at life cycle onStop, and stops the load task and cleanup at life cycle onDestory.
1.4, interview

Why can’t Glide be used with child threads?

Life cycle management is not added to the child thread. The main thread adds a blank Fragment to listen for changes in the Activity Fragment.

Each with() overloaded code is very simple, calling getRetriever(activity).get(activity) and returning a RequestManager object.

getRetriever(…) .get(…) What did you do?

The supportFragmentGet() method is always called when an Activity, FragmentActivity, or Fragment is passed in, and the final flow of both methods is the same: it adds a hidden Fragment to the current Activity.

So why add a hidden Fragment here?

A: Because Glide needs to know the loading life cycle. It’s very simple. If you are loading an image on an Activity and the user stops the image before it is loaded, the image should be unloaded. Glide doesn’t know the life cycle of the Activity. So Glide uses the trick of adding a hidden Fragment. Because the Fragment life cycle is synchronized with the Activity, it can listen to the Fragment if the Activity is destroyed, so Glide can catch the event and stop the image loading.

With the Application, why not use registerActivityLifecycleCallbacks but use hidden fragments?

RegisterActivityLifecycleCallbacks can be implemented, and my friend in his own some engineering is in use, but the individual understanding is this: RegisterActivityLifecycleCallbacks monitors all the Activity life cycle, when you use Glide loading images, however, not all of the Activity can use Glide loading pictures (in most cases), so, Using registerActivityLifecycleCallbacks phenomenon exist waste of resources. Not only that, you monitor all activities how to associate with the activity that Glide wants to monitor, although it can be achieved, but this method is really not practical, since Glide gave us such a perfect solution, we have to learn to use, try to use their own projects in the future.

2, the load ()

2.2, the source code

Since the with() method returns a RequestManager object, it is easy to assume that the load() method is in the RequestManager class, so the first thing to look at is the RequestManager class. But as we learned in the last article, Glide supports image URL strings, image local paths, and so on, so there are a number of overloaded load() methods in the RequestManager. It’s not possible to look at every overload of the load() method here, so let’s look at just one load() method that loads the image URL string.

public class RequestManager implements LifecycleListener {
 
    // So let's look at the load() method. The logic in this method is very simple, just one line of code,
    // The fromString() method is called, the load() method is called, and the image URL is passed in.
    // The fromString() method is also extremely simple, calling the loadGeneric() method and specifying string.class because the load() method takes a String argument. So it looks as if the main work is done in the loadGeneric() method.
    public DrawableTypeRequest<String> load(String string) {
        return (DrawableTypeRequest<String>) fromString().load(string);
    }
 
   
    public DrawableTypeRequest<String> fromString(a) {
        return loadGeneric(String.class);
    }
 
    // The loadGeneric() method is just a few lines long, There are calls the Glide. BuildStreamModelLoader () method and Glide buildFileDescriptorModelLoader () method to obtain ModelLoader object.
    // The ModelLoader object is used to load images, and if we pass different types of arguments to the load() method, we get different ModelLoader objects.
    // Since we just passed the string. class argument, we end up with a StreamStringLoader object that implements the ModelLoader interface.
    private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
        ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
        ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =
                Glide.buildFileDescriptorModelLoader(modelClass, context);
        if(modelClass ! =null && streamModelLoader == null && fileDescriptorModelLoader == null) {
            throw new IllegalArgumentException("Unknown type " + modelClass + ". You must provide a Model of a type for"
                    + " which there is a registered ModelLoader, if you are using a custom model, you must first call"
                    + " Glide#register with a ModelLoaderFactory for your custom model class");
        }
        The loadGeneric() method returns a DrawableTypeRequest object,
        // So a DrawableTypeRequest object is new at the end of the loadGeneric() method,
        // Then pass in the ModelLoader object you just got, along with a bunch of other stuff. The specific meaning and function of each parameter are not explained, we only look at the main flow.
        return optionsApplier.apply(
                newDrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context, glide, requestTracker, lifecycle, optionsApplier)); }}Copy the code

So what does this DrawableTypeRequest do? Let’s take a look at its source code, as follows:

public class DrawableTypeRequest<ModelType> extends DrawableRequestBuilder<ModelType> implements DownloadOptions {
    private final ModelLoader<ModelType, InputStream> streamModelLoader;
    private final ModelLoader<ModelType, ParcelFileDescriptor> fileDescriptorModelLoader;
    private final RequestManager.OptionsApplier optionsApplier;
 
    private static <A, Z, R> FixedLoadProvider<A, ImageVideoWrapper, Z, R> buildProvider(Glide glide, ModelLoader
       
         streamModelLoader, ModelLoader
        
          fileDescriptorModelLoader, Class
         
           resourceClass, Class
          
            transcodedClass, ResourceTranscoder
           
             transcoder)
           ,>
          
         
        ,>
       ,> {
        if (streamModelLoader == null && fileDescriptorModelLoader == null) {
            return null;
        }
 
        if (transcoder == null) {
            transcoder = glide.buildTranscoder(resourceClass, transcodedClass);
        }
        DataLoadProvider<ImageVideoWrapper, Z> dataLoadProvider = glide.buildDataProvider(ImageVideoWrapper.class,
                resourceClass);
        ImageVideoModelLoader<A> modelLoader = new ImageVideoModelLoader<A>(streamModelLoader,
                fileDescriptorModelLoader);
        return new FixedLoadProvider<A, ImageVideoWrapper, Z, R>(modelLoader, transcoder, dataLoadProvider);
    }
 
    DrawableTypeRequest(Class<ModelType> modelClass, ModelLoader<ModelType, InputStream> streamModelLoader,
            ModelLoader<ModelType, ParcelFileDescriptor> fileDescriptorModelLoader, Context context, Glide glide,
            RequestTracker requestTracker, Lifecycle lifecycle, RequestManager.OptionsApplier optionsApplier) {
        super(context, modelClass,
                buildProvider(glide, streamModelLoader, fileDescriptorModelLoader, GifBitmapWrapper.class,
                        GlideDrawable.class, null),
                glide, requestTracker, lifecycle);
        this.streamModelLoader = streamModelLoader;
        this.fileDescriptorModelLoader = fileDescriptorModelLoader;
        this.optionsApplier = optionsApplier;
    }
 
    /** * Most importantly, it provides the asBitmap() and asGif() methods. We learned about both methods in the previous article, which are used to force the loading of static images and dynamic images. In the source code, they create BitmapTypeRequest and GifTypeRequest, respectively, which default to DrawableTypeRequest if not specified. * /
    public BitmapTypeRequest<ModelType> asBitmap(a) {
        return optionsApplier.apply(new BitmapTypeRequest<ModelType>(this, streamModelLoader,
                fileDescriptorModelLoader, optionsApplier));
    }
 
    / * * * * /
    public GifTypeRequest<ModelType> asGif(a) {
        return optionsApplier.apply(new GifTypeRequest<ModelType>(this, streamModelLoader, optionsApplier)); }... }Copy the code

There isn’t much code in this class, so I’m just simplifying it a little bit. As you can see, the main thing is that it provides the asBitmap() and asGif() methods. We learned about both methods in the previous article, which are used to force the loading of static images and dynamic images. In the source code, they create BitmapTypeRequest and GifTypeRequest, respectively, which default to DrawableTypeRequest if not specified.

Ok, so let’s go back to the Load () method in the RequestManager. As you’ve just analyzed, the fromString() method returns a DrawableTypeRequest object, which then calls its load() method, passing in the image URL. But as we just saw, the DrawableTypeRequest doesn’t have a load() method, so it’s easy to guess that the load() method is in the parent class.

Equestbuilder class: DrawableTypeRequest and equestBuilder.

public class DrawableRequestBuilder<ModelType>
        extends GenericRequestBuilder<ModelType.ImageVideoWrapper.GifBitmapWrapper.GlideDrawable>
        implements BitmapOptions.DrawableOptions {
 
    DrawableRequestBuilder(Context context, Class<ModelType> modelClass,
            LoadProvider<ModelType, ImageVideoWrapper, GifBitmapWrapper, GlideDrawable> loadProvider, Glide glide,
            RequestTracker requestTracker, Lifecycle lifecycle) {
        super(context, modelClass, loadProvider, GlideDrawable.class, glide, requestTracker, lifecycle);
        // Default to animating.
        crossFade();
    }
 
    public DrawableRequestBuilder<ModelType> thumbnail( DrawableRequestBuilder
        thumbnailRequest) {
        super.thumbnail(thumbnailRequest);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> thumbnail( GenericRequestBuilder
        thumbnailRequest) {
        super.thumbnail(thumbnailRequest);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> thumbnail(float sizeMultiplier) {
        super.thumbnail(sizeMultiplier);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> sizeMultiplier(float sizeMultiplier) {
        super.sizeMultiplier(sizeMultiplier);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> decoder(ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> decoder) {
        super.decoder(decoder);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> cacheDecoder(ResourceDecoder<File, GifBitmapWrapper> cacheDecoder) {
        super.cacheDecoder(cacheDecoder);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> encoder(ResourceEncoder<GifBitmapWrapper> encoder) {
        super.encoder(encoder);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> priority(Priority priority) {
        super.priority(priority);
        return this;
    }
 
    public DrawableRequestBuilder<ModelType> transform(BitmapTransformation... transformations) {
        return bitmapTransform(transformations);
    }
 
    public DrawableRequestBuilder<ModelType> centerCrop(a) {
        return transform(glide.getDrawableCenterCrop());
    }
 
    public DrawableRequestBuilder<ModelType> fitCenter(a) {
        return transform(glide.getDrawableFitCenter());
    }
 
    public DrawableRequestBuilder<ModelType> bitmapTransform(Transformation<Bitmap>... bitmapTransformations) {
        GifBitmapWrapperTransformation[] transformations =
                new GifBitmapWrapperTransformation[bitmapTransformations.length];
        for (int i = 0; i < bitmapTransformations.length; i++) {
            transformations[i] = new GifBitmapWrapperTransformation(glide.getBitmapPool(), bitmapTransformations[i]);
        }
        return transform(transformations);
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> transform(Transformation<GifBitmapWrapper>... transformation) {
        super.transform(transformation);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> transcoder( ResourceTranscoder
       
         transcoder)
       ,> {
        super.transcoder(transcoder);
        return this;
    }
 
    public final DrawableRequestBuilder<ModelType> crossFade(a) {
        super.animate(new DrawableCrossFadeFactory<GlideDrawable>());
        return this;
    }
 
    public DrawableRequestBuilder<ModelType> crossFade(int duration) {
        super.animate(new DrawableCrossFadeFactory<GlideDrawable>(duration));
        return this;
    }
 
    public DrawableRequestBuilder<ModelType> crossFade(int animationId, int duration) {
        super.animate(new DrawableCrossFadeFactory<GlideDrawable>(context, animationId,
                duration));
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> dontAnimate(a) {
        super.dontAnimate();
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> animate(ViewPropertyAnimation.Animator animator) {
        super.animate(animator);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> animate(int animationId) {
        super.animate(animationId);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> placeholder(int resourceId) {
        super.placeholder(resourceId);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> placeholder(Drawable drawable) {
        super.placeholder(drawable);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> fallback(Drawable drawable) {
        super.fallback(drawable);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> fallback(int resourceId) {
        super.fallback(resourceId);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> error(int resourceId) {
        super.error(resourceId);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> error(Drawable drawable) {
        super.error(drawable);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> listener(
            RequestListener<? super ModelType, GlideDrawable> requestListener) {
        super.listener(requestListener);
        return this;
    }
    @Override
    public DrawableRequestBuilder<ModelType> diskCacheStrategy(DiskCacheStrategy strategy) {
        super.diskCacheStrategy(strategy);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> skipMemoryCache(boolean skip) {
        super.skipMemoryCache(skip);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> override(int width, int height) {
        super.override(width, height);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> sourceEncoder(Encoder<ImageVideoWrapper> sourceEncoder) {
        super.sourceEncoder(sourceEncoder);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> dontTransform(a) {
        super.dontTransform();
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> signature(Key signature) {
        super.signature(signature);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> load(ModelType model) {
        super.load(model);
        return this;
    }
 
    @Override
    public DrawableRequestBuilder<ModelType> clone(a) {
        return (DrawableRequestBuilder<ModelType>) super.clone();
    }
 
    @Override
    public Target<GlideDrawable> into(ImageView view) {
        return super.into(view);
    }
 
    @Override
    void applyFitCenter(a) {
        fitCenter();
    }
 
    @Override
    void applyCenterCrop(a) { centerCrop(); }}Copy the code

There are a number of equestbuilder methods that are part of Glide’s API. Some of them we’ve already used in the previous article, such as placeholder(), error(), diskCacheStrategy(), override(), and more. Of course, there are many other apis that haven’t been used yet, which we’ll learn about in a later article.

This completes the analysis of the second load() method. Why is that? As you can see from the equestBuilder class of DrawableRequestBuilder there is a into() method (line 220 above), which means that the load() method returns a DrawableTypeRequest object. The next step is to analyze the logic in the into() method.

2.3,

What the load method eventually does is do some initialization and get a DrawableTypeRequest object, from which we can get the image request and use it in our into method.

3, into ()

3.1,,

If the first two steps were the hors d ‘oeuvres, now it’s time to get to the main course, because the into() method is also the most logically complex part of the Glide image loading process.

But as you can see from the code, there is no logic in the into() method, just super.into(view). Into () is in the parent class of the equestBuilder for drawablerRequest.

3.1, the source code

The parent class of The RequestBuilder is GenericRequestBuilder, and the into() method of the GenericRequestBuilder class is shown as follows:

public Target<TranscodeType> into(ImageView view) {
    Util.assertMainThread();
    if (view == null) {
        throw new IllegalArgumentException("You must pass in a non null View");
    }
    if(! isTransformationSet && view.getScaleType() ! =null) {
        switch (view.getScaleType()) {
            case CENTER_CROP:
                applyCenterCrop();
                break;
            case FIT_CENTER:
            case FIT_START:
            case FIT_END:
                applyFitCenter();
                break;
            //$CASES-OMITTED$
            default:
                // Do nothing.}}return into(glide.buildImageViewTarget(view, transcodeClass));
}
Copy the code

We can leave all the judgment logic behind and explain it later when we talk about transform, but for now we just need to focus on the last line of code. . The last line of code first calls the glide buildImageViewTarget () method, this method can construct a Target object, the Target object is used to display images with eventually, if we go in to see the following code:

<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
    return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}
Copy the code

Again, the ImageViewTargetFactory buildTarget() method is called, and the code looks like this:

public class ImageViewTargetFactory {
 
    @SuppressWarnings("unchecked")
    public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
        if (GlideDrawable.class.isAssignableFrom(clazz)) {
            return (Target<Z>) new GlideDrawableImageViewTarget(view);
        } else if (Bitmap.class.equals(clazz)) {
            return (Target<Z>) new BitmapImageViewTarget(view);
        } else if (Drawable.class.isAssignableFrom(clazz)) {
            return (Target<Z>) new DrawableImageViewTarget(view);
        } else {
            throw new IllegalArgumentException("Unhandled class: " + clazz
                    + ", try .as*(Class).transcode(ResourceTranscoder)"); }}}Copy the code

As you can see, the buildTarget() method builds different Target objects based on the class argument passed in. So if you want to analyze where this class parameter is coming from, that’s a lot of work for you to do, so I’ll just sort it out for you for simplicity. This class parameter is really only two things, if you call the asBitmap() method when you Glide load the image, then this will build the BitmapImageViewTarget object, Otherwise build are GlideDrawableImageViewTarget object. As for the DrawableImageViewTarget object in the code above, this is usually not needed, so we can forget about it for the moment.

. That is, by glide buildImageViewTarget () method, we construct a GlideDrawableImageViewTarget object. Going back to the last line of the into() method, you can see that this parameter is passed to the other GenericRequestBuilder into() method that receives the Target object. Into () :

public <Y extends Target<TranscodeType>> Y into(Y target) {
    Util.assertMainThread();
    if (target == null) {
        throw new IllegalArgumentException("You must pass in a non null Target");
    }
    if(! isModelSet) {throw new IllegalArgumentException("You must first set a model (try #load())");
    }
    Request previous = target.getRequest();
    if(previous ! =null) {
        previous.clear();
        requestTracker.removeRequest(previous);
        previous.recycle();
    }
    Request request = buildRequest(target);
    target.setRequest(request);
    lifecycle.addListener(target);
    // Important: Request is used to make image loading requests. It is a key component of Glide.
    requestTracker.runRequest(request);
    return target;
}
Copy the code

Here we’ll stick to the core code, but the only two lines that matter are line 15, which calls buildRequest() to build a Request object, and line 18, which executes the Request.

Request is used to make image loading requests and is a key component of Glide. Let’s start by looking at how the buildRequest() method builds the Request object:

private Request buildRequest(Target<TranscodeType> target) {
    if (priority == null) {
        priority = Priority.NORMAL;
    }
    return buildRequestRecursive(target, null);
}
 
// buildRequest() calls buildRequestRecursive() inside the buildRequest() method
private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {
    if(thumbnailRequestBuilder ! =null) {
        if (isThumbnailBuilt) {
            throw new IllegalStateException("You cannot use a request as both the main request and a thumbnail, "
                    + "consider using clone() on the request(s) passed to thumbnail()");
        }
        // Recursive case: contains a potentially recursive thumbnail request builder.
        if(thumbnailRequestBuilder.animationFactory.equals(NoAnimation.getFactory())) { thumbnailRequestBuilder.animationFactory =  animationFactory; }if (thumbnailRequestBuilder.priority == null) {
            thumbnailRequestBuilder.priority = getThumbnailPriority();
        }
 
        if(Util.isValidDimensions(overrideWidth, overrideHeight) && ! Util.isValidDimensions(thumbnailRequestBuilder.overrideWidth, thumbnailRequestBuilder.overrideHeight)) { thumbnailRequestBuilder.override(overrideWidth, overrideHeight); } ThumbnailRequestCoordinator coordinator =new ThumbnailRequestCoordinator(parentCoordinator);
        Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
        // Guard against infinite recursion.
        isThumbnailBuilt = true;
        // Recursively generate thumbnail requests.
        Request thumbRequest = thumbnailRequestBuilder.buildRequestRecursive(target, coordinator);
        isThumbnailBuilt = false;
        coordinator.setRequests(fullRequest, thumbRequest);
        return coordinator;
    } else if(thumbSizeMultiplier ! =null) {
        // Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.
        ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
        Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
        Request thumbnailRequest = obtainRequest(target, thumbSizeMultiplier, getThumbnailPriority(), coordinator);
        coordinator.setRequests(fullRequest, thumbnailRequest);
        return coordinator;
    } else {
        // Base case: no thumbnail.''
        // The obtainRequest() method is called to obtain a Request object, and the GenericRequest obtain() method is called in obtainRequest().
        returnobtainRequest(target, sizeMultiplier, priority, parentCoordinator); }}private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,
        RequestCoordinator requestCoordinator) {
    return GenericRequest.obtain(
            loadProvider,
            model,
            signature,
            context,
            priority,
            target,
            sizeMultiplier,
            placeholderDrawable,
            placeholderId,
            errorPlaceholder,
            errorId,
            fallbackDrawable,
            fallbackResource,
            requestListener,
            requestCoordinator,
            glide.getEngine(),
            transformation,
            transcodeClass,
            isCacheable,
            animationFactory,
            overrideWidth,
            overrideHeight,
            diskCacheStrategy);
}
Copy the code

As you can see, buildRequest() actually calls buildRequestRecursive() inside the buildRequest() method. BuildRequestRecursive () is a bit long, but 90% of the code is working with thumbnails. If we just follow the main flow, we only need to look at line 47. The obtainRequest() method is called to obtain a Request object, and the GenericRequest obtain() method is called in the obtainRequest() method. Note that the obtain() method requires a lot of parameters — many of which are familiar — as in placeholderId, errorPlaceholder, diskCacheStrategy, and so on. Therefore, it is reasonable to assume that all the APIS just called in the load() method are actually assembled into the Request object here. So let’s get into the GenericRequest obtain() method and take a look:

public final class GenericRequest<A.T.Z.R> implements Request.SizeReadyCallback.ResourceCallback {
        
    public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain(
            LoadProvider<A, T, Z, R> loadProvider,
            A model,
            Key signature,
            Context context,
            Priority priority,
            Target<R> target,
            float sizeMultiplier,
            Drawable placeholderDrawable,
            int placeholderResourceId,
            Drawable errorDrawable,
            int errorResourceId,
            Drawable fallbackDrawable,
            int fallbackResourceId,
            RequestListener<? super A, R> requestListener,
            RequestCoordinator requestCoordinator,
            Engine engine,
            Transformation<Z> transformation,
            Class<R> transcodeClass,
            boolean isMemoryCacheable,
            GlideAnimationFactory<R> animationFactory,
            int overrideWidth,
            int overrideHeight,
            DiskCacheStrategy diskCacheStrategy) {
        @SuppressWarnings("unchecked")
        GenericRequest<A, T, Z, R> request = (GenericRequest<A, T, Z, R>) REQUEST_POOL.poll();
        if (request == null) {
            / / line 33:
            request = new GenericRequest<A, T, Z, R>();
        }
        request.init(loadProvider,
                model,
                signature,
                context,
                priority,
                target,
                sizeMultiplier,
                placeholderDrawable,
                placeholderResourceId,
                errorDrawable,
                errorResourceId,
                fallbackDrawable,
                fallbackResourceId,
                requestListener,
                requestCoordinator,
                engine,
                transformation,
                transcodeClass,
                isMemoryCacheable,
                animationFactory,
                overrideWidth,
                overrideHeight,
                diskCacheStrategy);
        returnrequest; }}Copy the code

As you can see, here a GenericRequest object is new at line 33 and returned at the last line, that is, the obtain() method is actually obtaining a GenericRequest object. GenericRequest init() is called on line 35, which is basically the assignment code that assigns these parameters to GenericRequest’s member variables, but we won’t follow through.

Ok, so now that we’ve solved the problem of building a Request object, let’s look at how the Request object is executed. Back to into () method, you will find in line 18 call requestTracker. RunRequest () method to execute the Request, then we go in with a look at, as shown below:

/** * Starts tracking the given request. */
public void runRequest(Request request) {
    requests.add(request);
    if(! isPaused) { request.begin(); }else{ pendingRequests.add(request); }}Copy the code

It is a simple logical judgment that Glide is currently processing the suspended state. If it is not, call Request begin() to execute the Request. If it is not, add the Request to the queue and wait until the suspended state is cleared.

The ability to pause requests is still not the concern of this article, so we’ll just ignore it and focus on the begin() method. Since the current Request object is a GenericRequest, we need to look at the begin() method in GenericRequest, as follows:

@Override
public void begin(a) {
    startTime = LogTime.getLogTime();
    if (model == null) {
        onException(null);
        return;
    }
    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        onSizeReady(overrideWidth, overrideHeight);
    } else {
        target.getSize(this);
    }
    if(! isComplete() && ! isFailed() && canNotifyStatusChanged()) { target.onLoadStarted(getPlaceholderDrawable()); }if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished run method in "+ LogTime.getElapsedMillis(startTime)); }}Copy the code

First, if model is equal to null, which is the image URL we passed in the second load() method, onException() will be called. If you look inside the onException() method, you’ll see that it ends up calling a setErrorPlaceholder(), as follows:

private void setErrorPlaceholder(Exception e) {
    if(! canNotifyStatusChanged()) {return;
    }
    Drawable error = model == null ? getFallbackDrawable() : null;
    if (error == null) {
      error = getErrorDrawable();
    }
    if (error == null) {
        error = getPlaceholderDrawable();
    }
    target.onLoadFailed(e, error);
}
Copy the code

This method retrieves an error placeholder, or a loading placeholder if not available, and then calls target.onloadFailed () and passes the placeholder. So what does that do in the onLoadFailed() method? Let’s see:

public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView.Z> implements GlideAnimation.ViewAdapter {

    @Override
    public void onLoadStarted(Drawable placeholder) {
        view.setImageDrawable(placeholder);
    }
 
    @Override
    public void onLoadFailed(Exception e, Drawable errorDrawable) { view.setImageDrawable(errorDrawable); }}Copy the code

It is simply displaying the error placeholder in ImageView because there is an exception and the normal image cannot be displayed. If you look at line 15 of the begin() method, you’ll see that it calls a target.onloadStarted () method and passes in a loading map that will be used in place of the final image display before the image request begins. This is also the underlying implementation of placeholder() and Error () apis that we learned about in the previous article.

Ok, so let’s go back to the begin() method. Now that I’ve talked about the implementation of a placeholder map, where does the actual image loading start? Are in lines 10 and 12 of the begin() method. There are two cases where you specify a fixed width and height for the image using the Override () API, or not. If specified, line 10 is executed, calling the onSizeReady() method. If not specified, line 12 is executed, calling the target.getsize () method. The target.getSize() method internally does a series of calculations based on the layout_width and layout_height values of the ImageView to figure out how wide and high the image should be. I won’t go into all the details, but once it’s done, it also calls onSizeReady(). That is, in either case, the onSizeReady() method is eventually called, where the next step is taken. So let’s follow this method:

@Override
public void onSizeReady(int width, int height) {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
    if(status ! = Status.WAITING_FOR_SIZE) {return;
    }
    status = Status.RUNNING;
    width = Math.round(sizeMultiplier * width);
    height = Math.round(sizeMultiplier * height);
    // 
    ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
    final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
    if (dataFetcher == null) {
        onException(new Exception("Failed to load model: \'" + model + "\ '"));
        return;
    }
    ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
    }
    loadedFromMemoryCache = true;
    loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
            priority, isMemoryCacheable, diskCacheStrategy, this); loadedFromMemoryCache = resource ! =null;
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished onSizeReady in "+ LogTime.getElapsedMillis(startTime)); }}Copy the code
3.3,
3.4, interview
  1. Get ImageViewTarget where the image is displayed
return into(
    // Get ImageViewTarget where the image is displayed
    glideContext.buildImageViewTarget(view, transcodeClass));
Copy the code
  1. Building a request
Request request = buildRequest(target, targetListener, options, callbackExecutor);
Copy the code

4, caching,

4.1 Introduction to Glide Cache

General picture loading library, are through memory cache LruCache, disk cache DiskLruCache to get data, so Glide is also such?

Glide’s cache design can be said to be very advanced and well considered. For caching, Glide splits it into two modules, one memory cache and one hard disk cache.

The memory Cache includes WeakReference Cache and LruCache. Hard disk cache is DiskLruCache.

The two cache modules serve different purposes:

The main purpose of the memory cache is to prevent applications from reading image data into memory repeatedly.

The main function of the disk cache is to prevent applications from repeatedly downloading and reading data from the network or other places.

It is the combination of memory cache and hard disk cache that makes Glide excellent picture cache effect, so next we will respectively analyze the use of these two kinds of cache and their implementation principle.

4.2 cache Key

Since it is a caching function, there must be a Key for caching. So how is Glide’s cache Key generated? I have to say that Glide’s cache Key generation rules are very cumbersome, with as many as 10 parameters determining the cache Key. But cumbersome cumbersome, at least the logic is relatively simple, we first look at the Glide cache Key generation logic.

The code that generates the cache Key is in the Load () method of the Engine class, which we examined in the previous article but ignored, so let’s revisit:

public class Engine implements EngineJobListener.MemoryCache.ResourceRemovedListener.EngineResource.ResourceListener {
 
    public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
            DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
            Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
        Util.assertMainThread();
        long startTime = LogTime.getLogTime();
 
        // Call fetcher.getid () to get an ID string, which is the unique identifier of the image to load, such as the url of the image on the web.
        final String id = fetcher.getId();
        // Pass the id to the EngineKeyFactory buildKey() method, along with signature, width, height, and 10 other arguments, to create a EngineKey object. The EngineKey is the same as the cache Key in Glide.EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(), loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(), transcoder, loadProvider.getSourceEncoder()); }}Copy the code

Pass this id to the EngineKeyFactory buildKey() method, along with signature, width, height, and 10 other arguments, to build a EngineKey object. The EngineKey is the same as the cache Key in Glide.

We’ve overridden the equals() and hashCode() methods to ensure that the EngineKey is treated as the same object only if all the parameters passed to it are the same.

4.3 memory cache

** Memory cache reads: ** First get LruResourceCache (Lru algorithm cache) and then put it into activeResources (a weakly referenced HashMap) to cache images in use, which protects them from being recycled by the LruCache algorithm.

** Memory cache write: ** The EngineResource called back is first put into activeResources, which is the cache written here. If the image is in use, it should be placed in the activeResources weak reference cache. If the image is no longer in use, the cached image is first removed from activeResources and then put into LruResourceCache. In this way, images in use are cached using weak references and images not in use are cached using LruCache.

** Images in use are cached using weak references, and images in use are cached using LruCache.

4.4. Disk Caching

** Disk cache reads: ** If you remember, we used hard disk cache in the first article of this series. The following code was used to disable Glide’s disk caching of images:

Glide.with(this)
     .load(url)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .into(imageView);
Copy the code

To disable disk caching, call diskCacheStrategy() and pass diskCacheStrategy.None.

The diskCacheStrategy() method, which is basically everything about the Glide cache function, can accept four arguments:

  • Diskcachestrategy. NONE: indicates that no content is cached.
  • Diskcachestrategy. SOURCE: indicates that only raw images are cached.
  • Diskcachestrategy. RESULT: Only converted images are cached (default).
  • Diskcachestrategy. ALL: caching both the original image and the converted image.

First, like memory caching, hard disk caching is implemented using the LruCache algorithm, and Google also provides an off-the-shelf utility class DiskLruCache. I have also written a special article on the DiskLruCache tool for a more comprehensive analysis, interested friends can refer to the Android DiskLruCache full resolution, the best solution for hard disk caching. Glide, of course, uses a self-written DiskLruCache tool class, but the basic implementation principles are the same.

Let’s take a look at where Glide reads the disk cache. The decode() method is used by Glide to load the image. The decode() method is used by Glide to load the image. The decode() method is used by Glide.

privateResource<? > decode()throws Exception {
    if (isDecodingFromCache()) {
        return decodeFromCache();
    } else {
        returndecodeFromSource(); }}Copy the code

As you can see, there are two cases, one is to call decodeFromCache() to read the image from the hard disk cache, and the other is to call decodeFromSource() to read the original image. By default, Glide will read first from the cache and only read the original image if there are no images in the cache to read. DecodeFromCache (); decodeFromCache(); decodeFromCache();

privateResource<? > decodeFromCache()throwsException { Resource<? > result =null;
    try {
        result = decodeJob.decodeResultFromCache();
    } catch (Exception e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Exception decoding result from cache: "+ e); }}if (result == null) {
        result = decodeJob.decodeSourceFromCache();
    }
    return result;
}
Copy the code

DecodeJob decodeResultFromCache() to get cache. If not, call decodeSourceFromCache() to get cache. The difference between these two methods is the difference between diskCacheStrategy. RESULT and diskCacheStrategy. SOURCE.

Glide problem

www.jianshu.com/p/812558735…

1. Glide loads a 100×100 picture, will it be compressed and then loaded? What happens if you put it in a 300×300 view?

When we adjust the size of the ImageView, Glide will cache an image for each different size of the ImageView, that is, whether your image has been loaded or not, as long as the size of the ImageView is different, Glide will reload it once. He will re-download the ImageView from the network before loading it, and then cache it. For example, if the ImageView on one page is 300 * 300 pixels and the ImageView on another page is 100 * 100 pixels, Glide needs to download the image twice and cache the two images.

2. Three-level cache principle

When the Android terminal needs to obtain data, such as images in the network, it first looks up the data in the memory (press the key to search), and then looks up the data in the disk file or SQLite. If there is no data in the disk, it only obtains the data through the network

3. The principle of LRUCache

LruCache is a generic class. The main principle is: When the cache is full, the least recently used objects are removed from memory, and get/put methods are provided to obtain and add the cache. LruCache is thread-safe because synchronized is used.

When the put() method is called, the element is added to the head of the list. If there is no element in the list, the size remains the same. If there is, the trimToSize method is called to determine whether the maximum cache is exceeded. If the cache size is greater than the maximum cache value, the last element in the LinkedHashMap, the least accessed, is continually removed until the cache size is less than the maximum cache value.

When LruCache’s get method is called, the LinkedHashMap calls the recordAccess method to add this element to the head of the list

4. Glide withFrescocontrast

www.jianshu.com/p/1ab5597af…

Glide:

  • Multiple image format cache, suitable for more content presentation (such as Gif, WebP, thumbnail, Video)
  • Lifecycle integration (managing image load requests based on Activity or Fragment lifecycle)
  • Efficient processing of bitmaps (reuse and active recycling of bitmaps to reduce system recycling pressure)
  • Efficient cache strategy, flexible (Picasso only caches original size images, Glide caches multiple sizes), fast loading and low memory overhead (default Bitmap format difference makes memory overhead half that of Picasso)

Fresco “:

  • The biggest advantage is bitmap loading under 5.0 (minimum 2.3). Under 5.0, Fresco places images in a special memory area (the Ashmem area)
  • Greatly reduce OOM (processing OOM in lower Native layer, images will no longer occupy the memory of App)
  • It is suitable for scenarios where a large number of images need to be loaded with high performance
5. If you had to write your own image-loading framework, what would you consider?

First, comb through the necessary image loading framework requirements:

  • Asynchronous loading: Thread pools
  • Switching threads: Handler, no argument
  • Cache: LruCache, DiskLruCache
  • Prevent OOM: Soft reference, LruCache, image compression, Bitmap pixel storage location
  • Memory leak: Pay attention to proper reference of ImageView, lifecycle management
  • List sliding loading problems: loading disorder, queue too many tasks

Of course, there are also unnecessary requirements, such as loading animations.

1. Asynchronous loading

Thread pools, how many?

There are three levels of cache: memory cache, hard disk and network.

Because the network is blocked, read memory and hard disk can be placed in one thread pool, the network needs another thread pool, or the network can use Okhttp’s built-in thread pool.

Read disks and read networks need to be handled in separate thread pools, so two thread pools are appropriate.

2. Switch threads

The image is loaded asynchronously, so you need to update the ImageView in the main thread.

Whether it’s RxJava, EventBus, or Glide, you need Handler to switch from a child thread to the Android main thread.

3. The cache

We often say the image cache: memory cache, hard disk cache, network.

** Memory cache: ** LruCache is usually used

Glide default memory cache also uses LruCache, but not the Android SDK LruCache, but also based on LinkHashMap, so the principle is the same.

Why LruCache?

LruCache uses the least recently used algorithm to set a cache size. When the cache size reaches this size, the oldest data will be removed to avoid the OOM.

4. Prevent OOM

The LruCache cache size set above can effectively prevent OOM, but when the image demand is large, it may need to set a relatively large cache, so that the probability of OOM occurrence will be increased, so we should explore other methods to prevent OOM.

5.ImageView memory leaks

Glide approach is to listen to the life cycle callback, look at the RequestManager class, in the Activity/fragment destruction, cancel the picture load task, details we can go to see the source code.

6. List loading problem