Resources

Resources in Glide include things like Bitmaps, byte[] arrays, int[] arrays as well as a variety of POJOs. Glide attempts to re-use resources whenever possible to limit the amount of memory churn in your application.

Benefits

Excessive allocations of objects of any size can dramatically increase the garbage collection (GC) overhead of your application. Although Android’s Dalvik runtime has a much higher GC penalty than the newer ART runtime, excessive allocations will decrease the performance of your application regardless of which device your own.

Dalvik

Dalvik devices (pre Lollipop) face an exceptionally large penalty for excessive allocations that are worth discussing.

Dalvik has two basic modes for garbage collection, GC_CONCURRENT and GC_FOR_ALLOC, both of which you can see in logcat.

  • GC_CONCURRENT blocks the main thread twice for about 5ms for each collection. Since each operation is less than a single frame (16ms), GC_CONCURRENT tends not to cause your application to drop frames.
  • GC_FOR_ALLOC is a stop the world collection that can block the main thread for 125+ms. GC_FOR_ALLOC virtually always causes your application to drop multiple frames, resulting in visible stuttering, particularly while scrolling.

Unfortunately Dalvik seems to handle even modest allocations (a 16kb buffer for example) poorly. Repeated moderate allocations, or even a single large allocation (say for a Bitmap), will cause GC_FOR_ALLOC. Therefore, the more you allocate, the more stop the world garbage collections you incur, and the more frames your application drops.

By re-using moderate to large resources, Glide helps keep your app jank free by avoiding stop the world garbage collections as much as possible.

How Glide tracks and re-uses resources

Glide takes a permissive approach to resource re-use. Glide will opportunistically re-use resources when it believes it is safe to do so, but Glide does not require calleres to recycle resources after each request. Unless a caller explicitly signals that they’re done with a resource (see below), resources will not be recycled or re-used.

Reference counting

In order to determine when a resource is in use and when it is safe to be re-used, Glide keeps a reference count for each resource.

Incrementing the reference count

Each call to into() that loads a resource increments the reference count for that resource by one. If the same resource is loaded into two different Targets it will have a reference count of two after both loads complete.

Decrementing the reference count

The reference count is decremented when callers signal that they are done with the resource by:

  1. Calling clear() on the View or Target the resource was loaded in to.
  2. Calling into() on the View or Target with a request for a new resource.

The refrence count is also decremented when the RequestManager associated with the request is paused or cleared. The RequestManager associated with the request is determined by the Activity or Fragment you pass into Glide.with(). The RequestManager can be paused or cleared manually and will also monitor the Lifecycle of the corresponding Activity or Fragment and automatically pause or clear requests in onStop and onDestroy. If you pass the Application Context to Glide.with, the RequestManager will not automatically pause or clear requests.

Releasing resources.

When the reference count reaches zero, the resource is released and returned to Glide for re-use. After the resource is returned to Glide for re-use it is no longer safe to continue using. As a result it’s unsafe to:

  1. Retrieve a Bitmap or Drawable loaded into an ImageView using getImageDrawable() and display it (using setImageDrawable(), in an animation or TransitionDrawable or any other method).
  2. Use SimpleTarget to load a resource into a View without also implementing onLoadCleared() and removing the resource from the View in that callback.
  3. Leave onLoadCleared() unimplemented in CustomTarget
  4. Call recycle() on any Bitmap loaded with Glide.

It’s unsafe to reference a resource after clearing the corresponding View or Target because that resource may be destroyed or re-used to display a different image, resulting in undefined behavior, graphical corruption, or crashes in applications that continue to use those resources. For example, after being released back to Glide, Bitmaps may be stored in a BitmapPool and re-used to hold the bytes of a new image at some point in the future or they may have recycle() called on them (or both). In either case continuing to reference the Bitmap and expecting it to contain the original image is unsafe.

Pooling

Although most of Glide’s recycling logic is aimed at Bitmaps, all Resource implementations can implement recycle() and pool any re-usable data they might contain. ResourceDecoders are free to return any implementation of the Resource API they wish, so users can customize or provide additional pooling for novel types by implementing their own Resources and ResourceDecoders.

For Bitmaps in particular, Glide provides a BitmapPool interface that allows Resources to obtain and re-use Bitmap objects. Glide’s BitmapPool can be obtained from any Context using the Glide singleton:

Glide.get(context).getBitmapPool();

Similarly users who want more control over Bitmap pooling are free to implement their own BitmapPool, which they can then provide to Glide using a GlideModule. See the configuration page for details.

Common errors

Unfortunately pooling makes it difficult to assert that a user isn’t misusing a resource or a Bitmap. Glide tries to add assertions where possible, but because we don’t own the underlying Bitmap we can’t guarantee that callers stop using Bitmaps or other resources when they tell us they have via clear() or a new request.

Symptoms of resource re-use errors.

There are a couple of indicators that something might be going wrong with Bitmap or other resource pooling in Glide. A few of the most common symptoms we see are listed here, though this is not an exhaustive list.

Cannot draw a recycled Bitmap

Glide’s BitmapPool has a fixed size. When Bitmaps are evicted from the pool without being re-used, Glide will call recycle(). If an application inadvertently continues to hold on to the Bitmap even after indicating to Glide that it is safe to recycle it, the application may then attempt to draw the Bitmap, resulting in a crash in onDraw().

This problem could be due to the fact that one target is being used for two ImageViews, and one of the ImageViews still tries to access the recycled Bitmap after it has been put into the BitmapPool. This recycling error can be hard to reproduce, due to several factors:

  1. When the bitmap is put into the pool
  2. When the bitmap is recycled, and
  3. What the size of the BitmapPool and memory cache are that leads to the recycling of the Bitmap.

The following snippet can be put into your GlideModule to help making this problem easier to reproduce:

@Override
public void applyOptions(Context context, GlideBuilder builder) {
    int bitmapPoolSizeBytes = 1024 * 1024 * 0; // 0mb
    int memoryCacheSizeBytes = 1024 * 1024 * 0; // 0mb
    builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));
    builder.setBitmapPool(new LruBitmapPool(bitmapPoolSizeBytes));
}

The above code makes sure that there is no memory caching and the size of the BitmapPool is zero; so Bitmap, if happened to be not used, will be recycled right away. The problem will surface much quicker for debugging purposes.

Can’t call reconfigure() on a recycled bitmap

Resources are returned to Glide’s BitmapPool when they’re not in use any more. This is handled internally based on the lifecycle of a Request (who controls Resources). If something calls recycle() on those Bitmaps, but they’re still in the pool, Glide cannot re-use them and your app crashes with the above message. One key point here is that the crash will likely happen in the future at another point in your app, and not where the offending code was executed!

Views flicker between images or the same image shows up in multiple views

If a Bitmap is returned to the BitmapPool multiple times, or is returned to the pool but still held on to by a View, another image may be decoded into the Bitmap. If this happens, the contents of the Bitmap are replaced with the new image. Views may still attempt to draw the Bitmap during this process, which will result either in artifacts or in the original View showing a new image.

Causes of re-use errors.

A few common causes of re-use errors are listed below. As with symptoms, it’s difficult to be exhaustive, but these are some things you should definitely consider when trying to debug a re-use error in your application.

Attempting to load two different resources into the same Target.

There is no safe way to load multiple resources into a single Target in Glide. Users can use the thumbnail() API to load a series of resources into a Target, but it is only safe to reference each earlier resource until the next call to onResourceReady().

Typically a better answer is to actually use a second View and load the second image into the second View. ViewSwitcher can work well to allow you to cross fade between two different images loaded in separate requests. You can just add the ViewSwitcher to your layout with two ImageView children and use into(ImageView) twice, once on each child, to load the two images.

Users absolutely must load multiple resources into the same View can do so by using two separate Targets. To make sure that the loads don’t cancel each other, users either need to avoid the ViewTarget subclasses, or use a custom ViewTarget subclass and override setRequest() and getRequest() so that they do not use the View’s tag to store the Request. This is advanced usage and not typically recommended.

Loading a resource into a Target, clearing or reusing the Target, and continuing to reference the resource.

The easiest way to avoid this error is to make sure that all references to a resource are nulled out when onLoadCleared() is called. It is generally safe to load a Bitmap and then de-reference the Target and never call into() or clear() on the Target again. However, it is not safe to load a Bitmap, clear the Target, and then continue to reference the Bitmap later. Similarly it’s unsafe to load a resource into a View and then obtain the resource from the View (via getImageDrawable() or any other means) and continue to reference it elsewhere.

Recycling the original Bitmap in a Transformation<Bitmap>.

As the JavaDoc says in Transformation, the original Bitmap passed in to transform() will be automatically recycled if the Bitmap returned by the Transformation is not the same instance as the one passed in to transform(). This is an important difference from other loader libraries, for example Picasso. BitmapTransformation provides the boilerplate to handle Glide’s Resource creation, but the recycling is done internally, so both Transformation and BitmapTransformation must not recycle the passed-in Bitmap or Resource.

It’s also worth noting the any intermediate Bitmaps that any custom BitmapTransformation obtains from the BitmapPool, but does not return from transform() must be either put back to the BitmapPool or have recycle(), but never both. You should never recycle() a Bitmap obtained from Glide.