Glide: Image first time not displayed when using onResourceReady

Created on 23 Feb 2016  路  10Comments  路  Source: bumptech/glide

This code sometimes works and sometimes it doesn't work(the image is not displayed), but only when a certain image uri is called for the first time:

Glide.with(context).load(uri).fitCenter().centerCrop()
    .into(new GlideDrawableImageViewTarget(imageView) {
        @Override
        public void onResourceReady(GlideDrawable drawable, GlideAnimation anim) {
            super.onResourceReady(drawable, anim);
            //some other action
        }
    });

This code works always(the image is displayed):

Glide.with(context).load(uri).fitCenter().centerCrop()
    .into(imageView);

It seems there is a random caching issue when using the code that can also do a task after the resource is loaded.

Tested with 'com.github.bumptech.glide:glide:3.7.0'.

question

Most helpful comment

You were right with your assumptions.
After a deeper inspection it turned out that indeed I had a custom (circle) transformation that was causing the problem just that this was into a different location. Ironically that image was always displaying fine but was propagating the error to the glide code portion that had no custom transformation.
As a reference I have replaced the custom circle transformation code with RoundedBitmapDrawable

Glide.with(activity)
    .load(photoUri)
    .asBitmap()
    .centerCrop()
    .into(new BitmapImageViewTarget(imageView) {
        @Override
        protected void setResource(Bitmap resource) {
            RoundedBitmapDrawable circularBitmapDrawable =
                    RoundedBitmapDrawableFactory.create(activity.getResources(), resource);
            circularBitmapDrawable.setCircular(true);
            imageView.setImageDrawable(circularBitmapDrawable);
        }
    });

Thanks for your prompt help.

All 10 comments

.fitCenter().centerCrop() contradict each other, use only one of them.
Try using .listener() to do something when the resource is loaded. Note that currently your "other action" is run after iv.setImageDrawable, while the .listener() is run before.

There shouldn't be any difference between the two into calls as by default GlideDrawableImageViewTarget is created, but without concrete reproducible case we can't do much. Can you share a working and a non-working URI?

Any chance you're using CircleImageView?

I removed both .fitCenter() and .centerCrop()but I have the same issue.

Glide.with(context)
    .load(uri)
    .into(new GlideDrawableImageViewTarget(imageView) {
        @Override
        public void onResourceReady(GlideDrawable drawable, GlideAnimation anim) {
            super.onResourceReady(drawable, anim);
            Log.d(TAG, "Sometimes the image is not loaded and this text is not displayed");
        }
    });

It's pretty simple to replicate: have a list with say 10 image URIs and a button that calls the code above for a random URI.
Sometimes the image is displayed and sometimes not.
When repeating the experiment clear that data of the app, close and restart is not enough as once the code is called for a URI it will always work for it.

From what I have noticed the issue happens for higher quality images. Small images are usually loaded.

Hmm, those higher quality images may be too large and a timeout happens, try to add a listener to log the error: https://github.com/bumptech/glide/wiki/Debugging-and-Error-Handling#requestlistener
If onResourceReady is not called it's either in progress of loading or called onLoadFailed or onLoadCleared if it was cancelled.

Logs result for 1 fail and 3 that worked.

D/GenericRequest: load failed
D/GenericRequest: java.lang.IllegalStateException: Can't call reconfigure() on a recycled bitmap
D/GenericRequest:     at android.graphics.Bitmap.checkRecycled(Bitmap.java:352)
D/GenericRequest:     at android.graphics.Bitmap.reconfigure(Bitmap.java:224)
D/GenericRequest:     at com.bumptech.glide.load.engine.bitmap_recycle.SizeConfigStrategy.get(SizeConfigStrategy.java:72)
D/GenericRequest:     at com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool.getDirty(LruBitmapPool.java:130)
D/GenericRequest:     at com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool.get(LruBitmapPool.java:114)
D/GenericRequest:     at com.bumptech.glide.load.resource.bitmap.CenterCrop.transform(CenterCrop.java:28)
D/GenericRequest:     at com.bumptech.glide.load.resource.bitmap.BitmapTransformation.transform(BitmapTransformation.java:54)
D/GenericRequest:     at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperTransformation.transform(GifBitmapWrapperTransformation.java:34)
D/GenericRequest:     at com.bumptech.glide.load.engine.DecodeJob.transform(DecodeJob.java:236)
D/GenericRequest:     at com.bumptech.glide.load.engine.DecodeJob.transformEncodeAndTranscode(DecodeJob.java:139)
D/GenericRequest:     at com.bumptech.glide.load.engine.DecodeJob.decodeFromSource(DecodeJob.java:129)
D/GenericRequest:     at com.bumptech.glide.load.engine.EngineRunnable.decodeFromSource(EngineRunnable.java:122)
D/GenericRequest:     at com.bumptech.glide.load.engine.EngineRunnable.decode(EngineRunnable.java:101)
D/GenericRequest:     at com.bumptech.glide.load.engine.EngineRunnable.run(EngineRunnable.java:58)
D/GenericRequest:     at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
D/GenericRequest:     at java.util.concurrent.FutureTask.run(FutureTask.java:237)
D/GenericRequest:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
D/GenericRequest:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
D/GenericRequest:     at java.lang.Thread.run(Thread.java:818)
D/GenericRequest:     at com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor$DefaultThreadFactory$1.run(FifoPriorityThreadPoolExecutor.java:118)
D/GLIDE: onException(java.lang.IllegalStateException: Can't call reconfigure() on a recycled bitmap, content://com.android.contacts/contacts/653/photo, Target for: android.support.v7.widget.AppCompatImageView{234fa2ad V.ED.... ........ 0,0-720,720 #7f0e00a7 app:id/contact_image}, true)
D/GLIDE: java.lang.IllegalStateException: Can't call reconfigure() on a recycled bitmap
D/GLIDE:     at android.graphics.Bitmap.checkRecycled(Bitmap.java:352)
D/GLIDE:     at android.graphics.Bitmap.reconfigure(Bitmap.java:224)
D/GLIDE:     at com.bumptech.glide.load.engine.bitmap_recycle.SizeConfigStrategy.get(SizeConfigStrategy.java:72)
D/GLIDE:     at com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool.getDirty(LruBitmapPool.java:130)
D/GLIDE:     at com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool.get(LruBitmapPool.java:114)
D/GLIDE:     at com.bumptech.glide.load.resource.bitmap.CenterCrop.transform(CenterCrop.java:28)
D/GLIDE:     at com.bumptech.glide.load.resource.bitmap.BitmapTransformation.transform(BitmapTransformation.java:54)
D/GLIDE:     at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperTransformation.transform(GifBitmapWrapperTransformation.java:34)
D/GLIDE:     at com.bumptech.glide.load.engine.DecodeJob.transform(DecodeJob.java:236)
D/GLIDE:     at com.bumptech.glide.load.engine.DecodeJob.transformEncodeAndTranscode(DecodeJob.java:139)
D/GLIDE:     at com.bumptech.glide.load.engine.DecodeJob.decodeFromSource(DecodeJob.java:129)
D/GLIDE:     at com.bumptech.glide.load.engine.EngineRunnable.decodeFromSource(EngineRunnable.java:122)
D/GLIDE:     at com.bumptech.glide.load.engine.EngineRunnable.decode(EngineRunnable.java:101)
D/GLIDE:     at com.bumptech.glide.load.engine.EngineRunnable.run(EngineRunnable.java:58)
D/GLIDE:     at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
D/GLIDE:     at java.util.concurrent.FutureTask.run(FutureTask.java:237)
D/GLIDE:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
D/GLIDE:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
D/GLIDE:     at java.lang.Thread.run(Thread.java:818)
D/GLIDE:     at com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor$DefaultThreadFactory$1.run(FifoPriorityThreadPoolExecutor.java:118)
V/RenderScript: Application requested CPU execution
V/RenderScript: 0x9d368a00 Launching thread(s), CPUs 4
D/GLIDE: onResourceReady(com.bumptech.glide.load.resource.bitmap.GlideBitmapDrawable@b4729e2, content://com.android.contacts/display_photo/350, Target for: android.support.v7.widget.AppCompatImageView{2dbe173 V.ED.... ........ 0,0-360,360 #7f0e00a7 app:id/contact_image}, false, true)
D/GLIDE: onResourceReady(com.bumptech.glide.load.resource.bitmap.GlideBitmapDrawable@1670bca9, content://com.android.contacts/display_photo/9, Target for: android.support.v7.widget.AppCompatImageView{3b32022e V.ED.... ........ 0,0-720,720 #7f0e00a7 app:id/contact_image}, false, true)
D/GLIDE: onResourceReady(com.bumptech.glide.load.resource.bitmap.GlideBitmapDrawable@2dcaa75c, content://com.android.contacts/display_photo/357, Target for: android.support.v7.widget.AppCompatImageView{b6f7a65 V.ED.... ........ 0,0-720,720 #7f0e00a7 app:id/contact_image}, false, true)

This is the code

Glide.with(cell.contentView.getContext())
    .load(contact.getPhotoUri())
    .centerCrop()
    .listener(new RequestListener<String, GlideDrawable>() {
        @Override
        public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
            android.util.Log.d("GLIDE", String.format(Locale.ROOT,
                    "onException(%s, %s, %s, %s)", e, model, target, isFirstResource), e);
            return false;
        }

        @Override
        public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
            android.util.Log.d("GLIDE", String.format(Locale.ROOT,
                    "onResourceReady(%s, %s, %s, %s, %s)", resource, model, target, isFromMemoryCache, isFirstResource));
            return false;
        }
    })
    .into(cell.contactImageView);

It seems it also fails even when using a listener.

Ok, so for this exception we need to look at the application as a whole. Because it means that there's a Bitmap in the pool that was already recycled. Glide doesn't do it by itself. Do you have any custom transformations, or call ImageView.getImageDrawable, or capture any onResourceReady(Bitmap|Drawable resource, ...) objects?

I don't do anything specific, but this problem occurs only sometimes and only once for a specific image URI, then I think something gets cached in glide and it works always(for that URI).

It happens only once, because after that it is loaded from the memory cache and there's no need to touch the bitmap pool (when decoding/transforming the image). If you add .skipMemoryCache(true) it may happen more often. My questions were targeting the whole app, any load that may happen before this happens since application startup is a suspect. Can you make small repro app on GitHub or otherwise share the app? (upload to Drive or mail it to me).

You were right with your assumptions.
After a deeper inspection it turned out that indeed I had a custom (circle) transformation that was causing the problem just that this was into a different location. Ironically that image was always displaying fine but was propagating the error to the glide code portion that had no custom transformation.
As a reference I have replaced the custom circle transformation code with RoundedBitmapDrawable

Glide.with(activity)
    .load(photoUri)
    .asBitmap()
    .centerCrop()
    .into(new BitmapImageViewTarget(imageView) {
        @Override
        protected void setResource(Bitmap resource) {
            RoundedBitmapDrawable circularBitmapDrawable =
                    RoundedBitmapDrawableFactory.create(activity.getResources(), resource);
            circularBitmapDrawable.setCircular(true);
            imageView.setImageDrawable(circularBitmapDrawable);
        }
    });

Thanks for your prompt help.

Good to hear that.

You can still use Transformations (this or this) as they have benefits over the drawable solution: the circle-cropped image is saved into the RESULT cache which means that the GPU doesn't have to do that transformation on every draw. Since it is saved with some transparent pixels you may even save space because the area outside the circle can be compressed well. Transformations also work for GIFs and don't require custom target implementations (this may be important if you want to do something else on that load too which wouldn't work with that target).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

r4m1n picture r4m1n  路  3Comments

piedpiperlol picture piedpiperlol  路  3Comments

FooBarBacon picture FooBarBacon  路  3Comments

ghost picture ghost  路  3Comments

sant527 picture sant527  路  3Comments