Hello everyone, today we are going to continue to learn Glide.

In the last article, I took you to explore the Glide cache mechanism, we not only master the use of Glide cache, but also through the source code analysis of the working principle of the cache to understand. Although the previous article and the content of this article is not very big, but interested friends can still go to read the Android picture loading framework most complete analysis (three), explore Glide cache mechanism.

Today is the fourth article of this Glide series, we have to choose a new function module to start learning, so to study the Glide callback and monitoring function. Today’s learning mode is still a combination of basic usage and source code analysis, of course, the source code in this article is built on the basis of the second source code analysis, has not seen this article friends, recommended to read the Android picture loading framework most complete analysis (two), Understand Glide’s execution process from the source point of view.

The source code implementation of the callback

As a veteran Glide, I believe you have become very familiar with the basic usage of Glide. As we all know, using Glide to load and display an image on an interface requires only one line of code:

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

And Glide, behind that one line of code, implemented thousands of lines of logic for us. In fact, in the second article, we have analyzed the entire execution process behind this line of code, but I will take you to review the callback part of the source code separately, which will help us learn today’s article.

Let’s start with the into() method, where we pass an ImageView instance into the into() method, Glide loads the image, and the image is displayed on the ImageView. How does this work? Into () :

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; default: } } return into(glide.buildImageViewTarget(view, transcodeClass)); }Copy the code

As you can see, the last line of code that invokes the glide. BuildImageViewTarget () method to construct a Target object, then it is passed to another to receive the Target parameters into () method. Target object is used to display images with eventually, if we follow up to glide. The buildImageViewTarget () method, you will see the source code of the following:

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

The buildTarget() method builds different Target objects based on the class argument passed in. If you call the asBitmap() method when using Glide to load the image, Here will build BitmapImageViewTarget object, otherwise building 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.

Glide calls GenericRequest’s onResourceReady() method after the image is loaded. GenericRequest’s onResourceReady() method is called by Glide.

public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback, ResourceCallback { private Target<R> target; . private void onResourceReady(Resource<? > resource, R result) { boolean isFirstResource = isFirstReadyResource(); status = Status.COMPLETE; this.resource = resource; if (requestListener == null || ! requestListener.onResourceReady(result, model, target, loadedFromMemoryCache, isFirstResource)) { GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource); target.onResourceReady(result, animation); } notifyLoadSuccess(); }... }Copy the code

Here at line 14 calls the target. OnResourceReady () method, and just now we already know that the target is GlideDrawableImageViewTarget object, so let’s see its source code:

public class GlideDrawableImageViewTarget extends ImageViewTarget<GlideDrawable> { ... @Override public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) { if (! resource.isAnimated()) { float viewRatio = view.getWidth() / (float) view.getHeight(); float drawableRatio = resource.getIntrinsicWidth() / (float) resource.getIntrinsicHeight(); if (Math.abs(viewRatio - 1f) <= SQUARE_RATIO_MARGIN && Math.abs(drawableRatio - 1f) <= SQUARE_RATIO_MARGIN) { resource =  new SquaringDrawable(resource, view.getWidth()); } } super.onResourceReady(resource, animation); this.resource = resource; resource.setLoopCount(maxLoopCount); resource.start(); } @Override protected void setResource(GlideDrawable resource) { view.setImageDrawable(resource); }... }Copy the code

As you can see, the image presentation is handled in the onResourceReady() method, along with the GIF playback logic, so an image is displayed, which is the basic implementation of Glide callback.

Okay, so that’s it, and then we’ll look at what we can expand on in terms of callbacks and listening.

Into () method

Glide has been around for so long that we know that ImageView can be passed into into(). Can the into() method pass in any other arguments? Can I have images loaded by Glide not display in ImageView? The answer is yes, which requires the custom Target functionality.

As we already know from the above analysis, the into() method also has an overload that accepts the Target argument. Glide will automatically build a Target object internally even if the argument we pass in is ImageView. If we can master the custom Target technology, we can control Glide’s callback more freely.

Let’s start by looking at the inheritance structure diagram of Target in Glide, as follows:

As you can see, Target’s inheritance structure is quite complex, with many subclasses implementing the Target interface. But don’t be intimidated by the number of subclasses, most of which are Fully functional Target subclasses that Glide has already implemented. If we’re going to customize it, we usually just need to customize it based on two types of Target: SimpleTarget, One is ViewTarget.

Next, I will use these two types of Target as examples to learn about the functions of custom Target.

Let’s start with SimpleTarget. As the name suggests, it’s a very SimpleTarget that we can use to get image objects that Glide has loaded instead of just displaying images on an ImageView.

Let’s take a look at a simple example of SimpleTarget in action:

SimpleTarget<GlideDrawable> simpleTarget = new SimpleTarget<GlideDrawable>() { @Override public void onResourceReady(GlideDrawable resource, GlideAnimation glideAnimation) { imageView.setImageDrawable(resource); }}; public void loadImage(View view) { String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg"; Glide.with(this) .load(url) .into(simpleTarget); }Copy the code

How’s that? True to SimpleTarget, it took just a few lines of code. Here we create an instance of SimpleTarget, specify that its generic type is GlideDrawable, and override the onResourceReady() method. In the onResourceReady() method, we get the Glide loaded image object, the GlideDrawable object passed in as the method parameter. So once you have this object you can do whatever logic you want with it, but I’m just simply displaying it in the ImageView.

Now that the SimpleTarget implementation is created, all you need to do is pass it into the into() method when the image is loaded. Now run the program and it will look like the image below.

While this effect is currently no different than passing in the ImageView directly into the into() method, we’ve got the image object instance and can do much more with it.

Of course, generics in SimpleTarget don’t have to be GlideDrawable. If you’re sure you’re loading a static image and not a GIF, you can get a Bitmap of the image, as shown below:

SimpleTarget<Bitmap> simpleTarget = new SimpleTarget<Bitmap>() { @Override public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { imageView.setImageBitmap(resource); }}; public void loadImage(View view) { String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg"; Glide.with(this) .load(url) .asBitmap() .into(simpleTarget); }Copy the code

As you can see, here we specify SimpleTarget’s generic as a Bitmap, and then call the asBitmap() method when loading the image to enforce that this is a static graph. This will get the map’s Bitmap object in the onResourceReady() method.

Now that SimpleTarget is simple, let’s look at ViewTarget.

In fact, from just can see the inheritance of chart, Glide internally to help us create GlideDrawableImageViewTarget is automatically ViewTarget subclass. GlideDrawableImageViewTarget is only limited to effect on the ImageView, the function of the ViewTarget more widely, it works on any View.

For example, I created a custom layout MyLayout as follows:

public class MyLayout extends LinearLayout { private ViewTarget<MyLayout, GlideDrawable> viewTarget; public MyLayout(Context context, AttributeSet attrs) { super(context, attrs); viewTarget = new ViewTarget<MyLayout, GlideDrawable>(this) { @Override public void onResourceReady(GlideDrawable resource, GlideAnimation glideAnimation) { MyLayout myLayout = getView(); myLayout.setImageAsBackground(resource); }}; } public ViewTarget<MyLayout, GlideDrawable> getTarget() { return viewTarget; } public void setImageAsBackground(GlideDrawable resource) { setBackground(resource); }}Copy the code

In the MyLayout constructor, we create an instance of ViewTarget and pass in the current instance of MyLayout, this. The ViewTarget needs to specify two generics, one for the View type and one for the image type (GlideDrawable or Bitmap). Then in the onResourceReady() method, we can get an instance of MyLayout through the getView() method and call any of its interfaces. For example, here we call the setImageAsBackground() method to use the loaded image as the MyLayout background.

Let’s take a look at how to use the Target. Since MyLayout already provides the getTarget() interface, we just need to write it where the image is loaded:

public class MainActivity extends AppCompatActivity { MyLayout myLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myLayout = (MyLayout) findViewById(R.id.background); } public void loadImage(View view) { String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg"; Glide.with(this) .load(url) .into(myLayout.getTarget()); }}Copy the code

It’s that simple. Just pass myLayout.gettarget () into the into() method. Now run the program again. The result should look like the following image.

Ok, that’s all we have to say about custom Target. These are the basic uses of custom Target, but once you’ve mastered these uses, you can deal with all kinds of complicated logic.

Preload () method

Glide load picture although very smart, it will automatically determine whether the picture has been cached, if so, directly from the cache to read, if not from the network to download. But what if I want to pre-load an image so that when I actually need to load it, I can read it directly from the cache instead of waiting for a long network load time?

This is a real problem for many Glide beginners, because before learning this article, the into() method must pass in an ImageView, and the image will be displayed. How can this be preloaded?

But by the end of this article, you should be able to come up with a solution. Since into() can pass in an ImageView as well as a Target object, if we make an empty implementation in the Target object’s onResourceReady() method, the image will not be displayed. Glide’s cache mechanism will still work normally, so it does not realize the preloading function?

Yes, this is perfectly fine for pre-loading, but it’s a bit clumsy. In fact, Glide specifically provides us with a preloaded interface, the preload() method, that we can use directly.

The preload() method has two method overloads, one with no arguments indicating the original size of the image that will be loaded, and the other with arguments specifying the width and height of the loaded image.

The preload() method is also very simple to use, simply replacing the into() method as follows:

Glide.with(this)
     .load(url)
     .diskCacheStrategy(DiskCacheStrategy.SOURCE)
     .preload();
Copy the code

Note that if we use preload(), it is best to specify diskCacheStrategy’s cache policy as diskCacheStrategy.source. Because the preload() method defaults to the preloaded original image size, the into() method defaults to dynamically loading images based on the size of the ImageView control. Therefore, if diskCacheStrategy’s cache policy is not specified as diskCacheStrategy.source, it is easy to use into() to load images after preloading and still request images from the network.

After preloading, it will be very fast to load the image again because Glide will read the image directly from the cache and display it like this:

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

Note that we still need to specify the disk cache policy as diskCacheStrategy.source using the diskCacheStrategy() method to ensure that Glide will read the image cache that has just been preloaded.

The preload() method is about as simple as that, but it’s obviously too low a level to use, so let’s satisfy our curiosity by looking at how it’s implemented in the source code.

Like the into() method, the preload() method is in the GenericRequestBuilder class, as shown below:

public class GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> implements Cloneable { ... public Target<TranscodeType> preload(int width, int height) { final PreloadTarget<TranscodeType> target = PreloadTarget.obtain(width, height); return into(target); } public Target<TranscodeType> preload() { return preload(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL); }... }Copy the code

As mentioned, preload() has two method overloads. You can call preload() with arguments to specify the width and height of the image, or you can call preload() without arguments. Internally, it automatically specifies the width and height of the image as target.size_original, which is the original size of the image.

Then we can see that the preloadTarget.obtain () method is called here at line 5 to get an instance of PreloadTarget and pass it into the into() method. As you can see from the inheritance diagram, PreloadTarget is a subclass of SimpleTarget, so it can be passed directly into into().

PreloadTarget PreloadTarget PreloadTarget PreloadTarget PreloadTarget PreloadTarget

public final class PreloadTarget<Z> extends SimpleTarget<Z> { public static <Z> PreloadTarget<Z> obtain(int width, int height) { return new PreloadTarget<Z>(width, height); } private PreloadTarget(int width, int height) { super(width, height); } @Override public void onResourceReady(Z resource, GlideAnimation<? super Z> glideAnimation) { Glide.clear(this); }}Copy the code

The source code for PreloadTarget is very simple; the obtain() method simply new an instance of PreloadTarget, and the onResourceReady() method does nothing but call Glide.clear().

Glide.clear() is not used to clear the cache, but it is used to release the resource.

The idea behind PreloadTarget is the same as we just mentioned: do nothing. Because after the image is loaded, it is only cached without displaying it, which is equivalent to preloading.

The preload() method is fairly simple, both in terms of usage and source implementation, so that’s all we have to say about it.

DownloadOnly () method

Glide has always been used to bring pictures to the interface. Glide is known to cache images during loading, but where do the cache files actually exist and how do I access them directly? We don’t even know yet.

Glide designed the image loading interface to make it easier to use without having to worry too much about the underlying implementation details. But what if I want to access the image cache right now? This is where the downloadOnly() method comes in.

Similar to preload(), downloadOnly() can replace into(), but the usage of downloadOnly() is significantly more complicated than preload(). As the name suggests, the downloadOnly() method means that only images are downloaded, not loaded. When the image is downloaded, we can get the image storage path for subsequent operations.

So first let’s look at the basic usage. The downloadOnly() method is defined in the DrawableTypeRequest class. It has two method overloads, one that accepts the width and height of the image, and the other that accepts a generic object, as shown below:

  • downloadOnly(int width, int height)
  • downloadOnly(Y target)

Each of these methods has its own application scenarios, where downloadOnly(int Width, int height) is used to download images from child threads, and downloadOnly(Y target) is used to download images from the main thread.

So let’s see how downloadOnly(int width, int height) is used. When the downloadOnly(int Width, int height) method is called, a FutureTarget object is immediately returned, and Glide begins downloading the image file in the background. We then call FutureTarget’s get() method to retrieve the downloaded image file. If the image has not been downloaded, the get() method will block and return a value until the image has been downloaded.

Let’s use an example to demonstrate this. The code looks like this:

public void downloadImage(View view) { new Thread(new Runnable() { @Override public void run() { try { String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg"; final Context context = getApplicationContext(); FutureTarget<File> target = Glide.with(context) .load(url) .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL); final File imageFile = target.get(); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(context, imageFile.getPath(), Toast.LENGTH_LONG).show(); }}); } catch (Exception e) { e.printStackTrace(); } } }).start(); }Copy the code

This code is a little bit longer, so LET me walk you through it. The downloadOnly(int width, int height) method must be used in the child Thread, so the first step here is to create a new Thread. In the child thread, we get an Application Context first, and we can’t use the Activity as the Context anymore, because there’s a chance that the Activity has been destroyed before the child thread has finished executing.

Next is the basic use of Glide, which replaces the into() method with the downloadOnly() method. The downloadOnly() method returns a FutureTarget object, by which time Glide has actually started downloading the image in the background. We can call FutureTarget’s Get () method at any time to get the downloaded image file. However, if the image has not been downloaded, the thread will temporarily block and return the image’s File object when the download is complete.

Finally, we use runOnUiThread() to cut back to the main thread, and then use Toast to display the downloaded image file path.

Now run the code again to see what it looks like below.

So we can clearly see what the full cache path of the image is.

We can then use the following code to load the image and it will be displayed immediately without having to request it on the network:

public void loadImage(View view) {
    String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
    Glide.with(this)
            .load(url)
            .diskCacheStrategy(DiskCacheStrategy.SOURCE)
            .into(imageView);
}
Copy the code

Note that the disk cache policy must be specified as diskCacheStrategy. SOURCE or diskCacheStrategy. ALL, otherwise it will not be able to use the image cache file we just downloaded.

Now run the code again to see what it looks like below.

As you can see, the image load and display is very fast because Glide uses the cache file that was just downloaded.

So how does this downloadOnly(int Width, int height) method work? Let’s take a quick and easy look at its source code.

The DrawableTypeRequest class defines this method as follows: DrawableTypeRequest

public class DrawableTypeRequest<ModelType> extends DrawableRequestBuilder<ModelType> implements DownloadOptions { ... public FutureTarget<File> downloadOnly(int width, int height) { return getDownloadOnlyRequest().downloadOnly(width, height); } private GenericTranscodeRequest<ModelType, InputStream, File> getDownloadOnlyRequest() { return optionsApplier.apply(new GenericTranscodeRequest<ModelType, InputStream, File>( File.class, this, streamModelLoader, InputStream.class, File.class, optionsApplier)); }}Copy the code

The getDownloadOnlyRequest() method is called to get a GenericTranscodeRequest object, and its downloadOnly() method is called as follows:

public class GenericTranscodeRequest<ModelType, DataType, ResourceType> implements DownloadOptions { ... public FutureTarget<File> downloadOnly(int width, int height) { return getDownloadOnlyRequest().into(width, height); } private GenericRequestBuilder<ModelType, DataType, File, File> getDownloadOnlyRequest() { ResourceTranscoder<File, File> transcoder = UnitTranscoder.get(); DataLoadProvider<DataType, File> dataLoadProvider = glide.buildDataProvider(dataClass, File.class); FixedLoadProvider<ModelType, DataType, File, File> fixedLoadProvider = new FixedLoadProvider<ModelType, DataType, File, File>(modelLoader, transcoder, dataLoadProvider); return optionsApplier.apply( new GenericRequestBuilder<ModelType, DataType, File, File>(fixedLoadProvider, File.class, this)) .priority(Priority.LOW) .diskCacheStrategy(DiskCacheStrategy.SOURCE) .skipMemoryCache(true); }}Copy the code

Here again, a getDownloadOnlyRequest() method is called to build an image download request, which returns a GenericRequestBuilder object, Call its into(width, height) method and let’s follow through:

public FutureTarget<TranscodeType> into(int width, int height) { final RequestFutureTarget<ModelType, TranscodeType> target = new RequestFutureTarget<ModelType, TranscodeType>(glide.getMainHandler(), width, height); glide.getMainHandler().post(new Runnable() { @Override public void run() { if (! target.isCancelled()) { into(target); }}}); return target; }Copy the code

As you can see, new first generates a RequestFutureTarget object, which is a subclass of Target. The thread is then cut back to the main thread by a Handler and the RequestFutureTarget is passed into the into() method.

So, it’s called into() that receives the Target parameter, and Glide begins to perform normal image loading logic. So the only question that remains is, what logic is being handled in the RequestFutureTarget? Let’s open its source code to have a look:

public class RequestFutureTarget<T, R> implements FutureTarget<R>, Runnable { ... @Override public R get() throws InterruptedException, ExecutionException { try { return doGet(null); } catch (TimeoutException e) { throw new AssertionError(e); } } @Override public R get(long time, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException { return doGet(timeUnit.toMillis(time)); } @Override public void getSize(SizeReadyCallback cb) { cb.onSizeReady(width, height); } @Override public synchronized void onLoadFailed(Exception e, Drawable errorDrawable) { exceptionReceived = true; this.exception = e; waiter.notifyAll(this); } @Override public synchronized void onResourceReady(R resource, GlideAnimation<? super R> glideAnimation) { resultReceived = true; this.resource = resource; waiter.notifyAll(this); } private synchronized R doGet(Long timeoutMillis) throws ExecutionException, InterruptedException, TimeoutException { if (assertBackgroundThread) { Util.assertBackgroundThread(); } if (isCancelled) { throw new CancellationException(); } else if (exceptionReceived) { throw new ExecutionException(exception); } else if (resultReceived) { return resource; } if (timeoutMillis == null) { waiter.waitForTimeout(this, 0); } else if (timeoutMillis > 0) { waiter.waitForTimeout(this, timeoutMillis); } if (Thread.interrupted()) { throw new InterruptedException(); } else if (exceptionReceived) { throw new ExecutionException(exception); } else if (isCancelled) { throw new CancellationException(); } else if (! resultReceived) { throw new TimeoutException(); } return resource; } static class Waiter { public void waitForTimeout(Object toWaitOn, long timeoutMillis) throws InterruptedException { toWaitOn.wait(timeoutMillis); } public void notifyAll(Object toNotify) { toNotify.notifyAll(); }}... }Copy the code

Here I have simplified the source code for RequestFutureTarget so that we can only look at the main logic.

We’ve just looked at the basic usage of the downloadOnly() method. After calling the downloadOnly() method, FutureTarget’s Get () method is used to get the downloaded image file. The FutureTarget object returned by the downloadOnly() method is actually the RequestFutureTarget, so we can just look at its Get () method.

RequestFutureTarget calls a doGet() method in its get() method, and the doGet() method is where the actual logic is handled. First, the doGet() method determines whether the current thread is in a child thread and raises an exception if it is not. It then determines whether the download has been cancelled or failed, and raises an exception if it has. The resultReceived variable is then used to determine whether the download is complete, and if the variable is true, the result is returned.

So what if the download isn’t complete yet? As we continue, we enter a wait() to block the current thread and prevent further code execution. This is why the downloadOnly(int Width, int height) method must be used in child threads, because it blocks the current thread and, if used in the main thread, blocks the main thread and prevents the user from doing anything else.

So now that the thread is blocked, when can it be restored? The answer is in the onResourceReady() method. As you can see, there are only three lines of code in the onResourceReady() method. The first line sets the resultReceived value to true, indicating that the image file has been downloaded, so that the next call to the get() method does not block the thread and can return the result directly. The second line assigns the downloaded image file to a global resource variable so that the doGet() method can access it. The third line notifyAll notifies all wait threads to unblock. The image file has been downloaded, so the doGet() method returns the result.

Ok, so that’s the basic usage and implementation of the downloadOnly(int width, int height) method, so let’s look at the downloadOnly(Y target) method.

Recall that the downloadOnly(int width, int height) method has to be used in child threads, mainly because it automatically creates a RequestFutureTarget internally, It is the RequestFutureTarget that requires execution in child threads. The downloadOnly(Y Target) method requires us to pass in a self-created target, so it is not restricted by RequestFutureTarget.

But the downloadOnly(Y Target) method is a little more complicated to use, because we’re creating a target again, and this time we have to implement the top-level Target interface directly. Much more complex than SimpleTarget and ViewTarget.

The Target interface’s generics must be specified as File objects. This is required by the downloadOnly(Y Target) method, as shown below:

public class DownloadImageTarget implements Target<File> { private static final String TAG = "DownloadImageTarget"; @Override public void onStart() { } @Override public void onStop() { } @Override public void onDestroy() { } @Override public void onLoadStarted(Drawable placeholder) { } @Override public void onLoadFailed(Exception e, Drawable errorDrawable) { } @Override public void onResourceReady(File resource, GlideAnimation<? super File> glideAnimation) { Log.d(TAG, resource.getPath()); } @Override public void onLoadCleared(Drawable placeholder) { } @Override public void getSize(SizeReadyCallback cb) { cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL); } @Override public void setRequest(Request request) { } @Override public Request getRequest() { return null; }}Copy the code

Because you are implementing the Target interface directly, there are many methods that need to be overridden. Most of these methods are callbacks to Glide load the image life cycle, and we can ignore them. There are only two methods that must be implemented, the getSize() method and the onResourceReady() method.

In the second Glide source code analysis, I led the analysis that Glide will calculate the size of the image before it starts loading, and then call back to the onSizeReady() method before it starts loading the image. Here, we’re left to calculate the size of the image. It’s just that this is the simplest Target implementation, and I just call back target.size_original in the getSize() method, which represents the original size of the image.

Then the onResourceReady() method is familiar, which calls back to this once the image is downloaded. I just printed the path of the downloaded image file in this method.

The simplest DownloadImageTarget is defined, and it’s so easy to use that we don’t have to worry about threads. Instead, we simply pass its instance into a downloadOnly(Y target) method, as shown below:

public void downloadImage(View view) {
    String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
    Glide.with(this)
            .load(url)
            .downloadOnly(new DownloadImageTarget());
}
Copy the code

Now re-run the code and click the Download Image button, and watch the output of the console log, as shown below.

Here we use the downloadOnly(Y target) method to also get the cache path of the downloaded image file.

Okay, so that’s all we have to do with the downloadOnly() method.

Listener () method

That’s enough for today, so let’s end with a simple thing: the last part of Glide callback and listener().

In fact, the listener() method is very common. It can be used to listen for the state of Glide loading images. For example, let’s say we just used the preload() method to preload images, but how do I know if the preload is complete? And if Glide load picture fails, how do I debug the cause of the error? The answers are in the listener() method.

Instead of replacing into(), the listener() method is used in conjunction with the into() method, or with the preload() method. The most basic usage is as follows:

public void loadImage(View view) {
    String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
    Glide.with(this)
            .load(url)
            .listener(new RequestListener<String, GlideDrawable>() {
                @Override
                public boolean onException(Exception e, String model, Target<GlideDrawable> target,
                    boolean isFirstResource) {
                    return false;
                }

                @Override
                public boolean onResourceReady(GlideDrawable resource, String model,
                    Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
                    return false;
                }
            })
            .into(imageView);
}
Copy the code

Here we concatenate a listener() method before the into() method, and then implement an instance of RequestListener. RequestListener implements two methods, onResourceReady() and onException(). As you can see from the name of the method, onResourceReady() is called back when the image load is complete and onException() is called back when the image load fails. The onException() method passes in the failed Exception argument so that we can locate the specific cause of the failure.

Yes, the listener() method is that simple. The onResourceReady() and onException() methods both return a Boolean value, false to indicate that the event was not handled, passed down, and true to indicate that the event was handled. So it doesn’t pass any further down. For a simpler example, if we return true in the onResourceReady() method of the RequestListener, then the onResourceReady() method of Target will not be called again.

So much for using the listener() method, but again, let’s look at its source code.

First, the Listener () method is defined in the GenericRequestBuilder class, and the instance we pass in to the Listener () method is assigned to a requestListener variable, as shown below:

public class GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> implements Cloneable { private RequestListener<? super ModelType, TranscodeType> requestListener; . public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> listener( RequestListener<? super ModelType, TranscodeType> requestListener) { this.requestListener = requestListener; return this; }... }Copy the code

This variable will then be passed in when GenericRequest is built, and when the image is loaded, we will see the following logic:

public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback, ResourceCallback { private RequestListener<? super A, R> requestListener; . private void onResourceReady(Resource<? > resource, R result) { boolean isFirstResource = isFirstReadyResource(); status = Status.COMPLETE; this.resource = resource; if (requestListener == null || ! requestListener.onResourceReady(result, model, target, loadedFromMemoryCache, isFirstResource)) { GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource); target.onResourceReady(result, animation); } notifyLoadSuccess(); }... }Copy the code

As you can see, line 11 calls the onResourceReady() method of the requestListener first, and only if the onResourceReady() method returns false, Call Target’s onResourceReady() method, which is how the Listener () method works.

The other onException() method is implemented exactly the same way, again in the GenericRequest class, as shown below:

public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback, ResourceCallback { ... @Override public void onException(Exception e) { status = Status.FAILED; if (requestListener == null || ! requestListener.onException(e, model, target, isFirstReadyResource())) { setErrorPlaceholder(e); }}... }Copy the code

As you can see, the onException() method of the requestListener is called back at line 9, and the setErrorPlaceholder() method will only continue to be called if the onException() method returns false. That is, if we return true in the onException() method, the exception placeholder set in the Glide request using the error(int resourceId) method is invalidated.

This completes the implementation of the Listener () method.

Well, Glide callback and monitoring aspects of the content today is here, this article is very substantial, I hope we can master. In the next article, I will continue to take you to in-depth analysis of Glide’s other function modules, talk about the knowledge of picture conversion, interested friends please continue to read Android picture loading framework the most complete analysis (five), Glide powerful picture conversion function.

Pay attention to my technical public account “Guo Lin”, there are high-quality technical articles pushed every day. Pay attention to my entertainment public number, work, study tired when relax yourself.