Glide Module Example: Optimizing By Loading Images In Custom Sizes

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

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.

Explore the Library

Find interesting tutorials and solutions for your problems.