In the last tutorials, we've seen a bunch of various customizations for Glide using Glide modules. We'll show you the last example today, but possibly the most interesting one: how to request images in specific dimensions from your server.
Glide Series Overview
- Integrating Networking Stacks
- Customize Glide with Modules
- Glide Module Example: Accepting Self-Signed HTTPS Certificates
- Glide Module Example: Customize Caching
- Glide Module Example: Optimizing By Loading Images In Custom Sizes
- Dynamically Use Model Loaders
Why Request Images in Certain Dimensions
In a recent project we worked with a media server, which also served the images, that provided the images in a very high resolution (images were like 6000x4500 pixels). While we could use direct links to the source file, it was extremely inefficient regarding the device's bandwidth, memory, and battery. Even with the high-resolution displays of today's devices, there is no benefit of having such an extremely high resolution. That's why Glide always measures the dimensions of the ImageView
and then reduces the memory-allocated image to that size. However, the download and computation overhead to reduce it to the smaller size is still there. Thus, the media server got a new feature: it could serve the images in custom resolutions. Imagine it in the following way:
// previous way: we directly accessed the images
https://futurestud.io/images/logo.png
// new way, server could handle additional parameter and provide the image in a specific size
// in this case, the server would serve the image in 400x300 pixel size
https://futurestud.io/images/logo.png?w=400&h=300
The media server kept previously computed sizes on disk and, if not requested in the past, scale the image on the fly. Now, the initial implementation on the Android side calculated the size of the ImageView
, then made the Glide request with the concatenated URL (like ../logo.png?w=400&h=300
), like we've shown you above. This way worked, but is a little too complicated, especially if you consider that Glide offers help here.
Another Custom GlideModule
Yes, of course, we'll have to declare a new Glide module. In this case, we'll have to register a new model to Glide with the registry.append()
(Glide 3.x: glide.register()
) method:
Glide 4.x
@GlideModule
public class CustomImageSizeGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
// nothing to do here
}
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
registry.append(CustomImageSizeModel.class, InputStream.class, new CustomImageSizeUrlLoaderFactory());
}
}
Glide 3.x
public class CustomImageSizeGlideModule implements GlideModule {
@Override public void applyOptions(Context context, GlideBuilder builder) {
// nothing to do here
}
@Override public void registerComponents(Context context, Glide glide) {
glide.register(CustomImageSizeModel.class, InputStream.class, new CustomImageSizeModelFactory());
}
}
The .append()
call configures Glide to understand all requests which are made against the CustomImageSizeModel
interface (instead of the regular GlideUrl
interface). So this line has the effect that you can create and pass instances of a CustomImageSizeModel
implementation to Glide. In order to handle this new custom model, we'll need to write a CustomImageSizeModelFactory
class, which creates an instance of our model request handler.
In summary, after you've added the Glide module from above, you should have two unknown classes. The first is the CustomImageSizeModel
:
public interface CustomImageSizeModel {
String requestCustomSizeUrl(int width, int height);
}
CustomImageSizeModel
is just an interface, which adds the width and height as a parameter. That is necessary so we can request pixel-exact images from our media server. The second unknown class is the CustomImageSizeUrlLoaderFactory
:
Glide 4.x
private class CustomImageSizeUrlLoaderFactory implements ModelLoaderFactory<CustomImageSizeModel, InputStream> {
private final ModelCache<CustomImageSizeModel, GlideUrl> modelCache = new ModelCache<>(500);
@Override
public ModelLoader<CustomImageSizeModel, InputStream> build(MultiModelLoaderFactory multiFactory) {
ModelLoader<GlideUrl, InputStream> modelLoader = multiFactory.build(GlideUrl.class, InputStream.class);
return new CustomImageSizeUrlLoader(modelLoader, modelCache);
}
@Override
public void teardown() {
}
}
Glide 3.x
private class CustomImageSizeUrlLoaderFactory implements ModelLoaderFactory<CustomImageSizeModel, InputStream> {
@Override
public ModelLoader<CustomImageSizeModel, InputStream> build(Context context, GenericLoaderFactory factories) {
return new CustomImageSizeUrlLoader( context );
}
@Override
public void teardown() {
}
}
This class just follows Glide's ModelLoaderFactory
interface. It creates a new instance of our CustomImageSizeUrlLoader
, which takes care of loading the image once the Glide request was created:
Glide 4.x
public static class CustomImageSizeUrlLoader extends BaseGlideUrlLoader<CustomImageSizeModel> {
public CustomImageSizeUrlLoader(ModelLoader<GlideUrl, InputStream> concreteLoader, @Nullable ModelCache<CustomImageSizeModel, GlideUrl> modelCache) {
super(concreteLoader, modelCache);
}
@Override
protected String getUrl(CustomImageSizeModel model, int width, int height, Options options) {
return model.requestCustomSizeUrl(width, height);
}
@Override
public boolean handles(CustomImageSizeModel customImageSizeModel) {
return true;
}
}
Glide 3.x
public class CustomImageSizeUrlLoader extends BaseGlideUrlLoader<CustomImageSizeModel> {
public CustomImageSizeUrlLoader(Context context) {
super( context );
}
@Override
protected String getUrl(CustomImageSizeModel model, int width, int height) {
return model.requestCustomSizeUrl( width, height );
}
}
And with that our new Glide module is done and ready for custom size requests. We've implemented everything on the Glide module side, but we've not actually created an implementation of the CustomImageSizeModel
interface. In order to pass requests to Glide with the CustomImageSizeModel
, we'll need a class, which builds the custom image size URLs:
public class CustomImageSizeModelFutureStudio implements CustomImageSizeModel {
String baseImageUrl;
public CustomImageSizeModelFutureStudio(String baseImageUrl) {
this.baseImageUrl = baseImageUrl;
}
@Override
public String requestCustomSizeUrl(int width, int height) {
// previous way: we directly accessed the images
// https://futurestud.io/images/logo.png
// new way, server could handle additional parameter and provide the image in a specific size
// in this case, the server would serve the image in 400x300 pixel size
// https://futurestud.io/images/logo.png?w=400&h=300
return baseImageUrl + "?w=" + width + "&h=" + height;
}
}
In the CustomImageSizeModelFutureStudio
class above, we've implemented the logic to build the image URL with the additional height and width parameters. Finally, we can create an instance of this class and make a Glide request:
String baseImageUrl = "https://futurestud.io/images/example.png";
CustomImageSizeModel customImageRequest = new CustomImageSizeModelFutureStudio(baseImageUrl);
// Glide 3.x
Glide
.with(context)
.load(customImageRequest)
.into(imageView2);
// Glide 4.x
GlideApp
.with(context)
.load(customImageRequest)
.into(imageView2);
As you can see above, we won't need to pass the exact dimensions. Glide will measure the ImageView
and pass it with our request. Now the server will respond with an image in the perfectly optimized size!
Of course, you can just add additional CustomImageSizeModel
model implementations, if you've multiple servers, which use different logic to build the URL. Just create a new CustomImageSizeModel
implementation and pass it to your Glide request. You can use as many model implementations as you need!
Outlook
In this tutorial, you've seen how to cut out a significant part of image request overhead. Every time your users will see their battery status and data usage, they'll love you for it. Unfortunately, you'll need the support for it on the server side. Nevertheless, Glide makes the Android side very easy. The initial setup is a little complex, but once you understood the concept, it's very useful.
The issue with the approach we've shown you in this tutorial: it'll be used on every single request. What if you've a mixed usage between image URLs, which can be resized, and image URLs, which cannot be adjusted? Next week, we'll show you how to apply the same idea dynamically on a single request.