bumptech / glide

An image loading and caching library for Android focused on smooth scrolling
https://bumptech.github.io/glide/
Other
34.59k stars 6.12k forks source link

IllegalStateException: Cannot obtain size for recycled Bitmap #4511

Open mafanwei opened 3 years ago

mafanwei commented 3 years ago

Glide Version: 4.11.0

Device/Android Version: All devices.

Stack trace / LogCat:

Caused by: java.lang.IllegalStateException: Cannot obtain size for recycled Bitmap: android.graphics.Bitmap@34aeaf5[720x1560] ARGB_8888
        at com.bumptech.glide.util.Util.getBitmapByteSize(Util.java:79)
        at com.bumptech.glide.load.resource.bitmap.BitmapResource.getSize(BitmapResource.java:52)
        at com.bumptech.glide.load.engine.LockedResource.getSize(LockedResource.java:81)
        at com.bumptech.glide.load.engine.LockedResource.getSize(LockedResource.java:81)
        at com.bumptech.glide.load.engine.EngineResource.getSize(EngineResource.java:62)
        at com.bumptech.glide.load.engine.cache.LruResourceCache.getSize(LruResourceCache.java:40)
        at com.bumptech.glide.load.engine.cache.LruResourceCache.getSize(LruResourceCache.java:11)
        at com.bumptech.glide.util.LruCache.put(LruCache.java:117)
        at com.bumptech.glide.load.engine.cache.LruResourceCache.put(LruResourceCache.java:11)
        at com.bumptech.glide.load.engine.Engine.onResourceReleased(Engine.java:396)
        at com.bumptech.glide.load.engine.EngineResource.release(EngineResource.java:116)
        at com.bumptech.glide.load.engine.Engine.release(Engine.java:362)
        at com.bumptech.glide.request.SingleRequest.clear(SingleRequest.java:326)
        at com.bumptech.glide.manager.RequestTracker.clearAndRemove(RequestTracker.java:72)
        at com.bumptech.glide.RequestManager.untrack(RequestManager.java:663)
        at com.bumptech.glide.RequestManager.untrackOrDelegate(RequestManager.java:631)
        at com.bumptech.glide.RequestManager.clear(RequestManager.java:627)
        at com.bumptech.glide.RequestManager.onDestroy(RequestManager.java:374)
        at com.bumptech.glide.manager.ActivityFragmentLifecycle.onDestroy(ActivityFragmentLifecycle.java:65)
        at com.bumptech.glide.manager.SupportRequestManagerFragment.onDestroy(SupportRequestManagerFragment.java:215)
        at androidx.fragment.app.Fragment.performDestroy(Fragment.java:2927)
        at androidx.fragment.app.FragmentStateManager.destroy(FragmentStateManager.java:482)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1294)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1354)
        at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1432)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1495)
        at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2617)
        at androidx.fragment.app.FragmentManager.dispatchDestroy(FragmentManager.java:2601)
        at androidx.fragment.app.FragmentController.dispatchDestroy(FragmentController.java:330)
        at androidx.fragment.app.FragmentActivity.onDestroy(FragmentActivity.java:365)
        at androidx.appcompat.app.AppCompatActivity.onDestroy(AppCompatActivity.java:242)

Here is small demo:

startActivityA:

class ActivityA: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.layout_test)
        tv.text = javaClass.name
        tv.setOnClickListener {
            val intent = Intent(this, ActivityB::class.java)
            startActivity(intent)
        }
    }
}

Here is ActivityB:

class ActivityB : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.layout_test)
        tv.text = javaClass.name
        tv.setOnClickListener {
            val intent = Intent(this, ActivityC::class.java)
            startActivity(intent)
            finish()
        }
        Glide.with(this).asBitmap().load("a picture url")
                .into(object : CustomTarget<Bitmap>() {
                    override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
                        val bitmap = Bitmap.createBitmap(resource, 0, 0, resource.width / 2, resource.height / 2)
                        resource.recycle() // notice!
                        img.setImageBitmap(bitmap)
                    }

                    override fun onLoadCleared(placeholder: Drawable?) {

                    }
                })
    }
}

Here is ActivityC:

class ActivityC: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.layout_test)
        tv.text = javaClass.name
    }
}

When you click on 'tv' in ActivityB, then you'll find the bug.

Since we didn't use the original image, we should recycle it, shouldn't we?

sjudd commented 3 years ago

You cannot safely call recycle() on a resource.

I wouldn't recommend copying the Bitmap either, I'm not sure what exactly the goal is. That said I don't think it's related to the error.

mafanwei commented 3 years ago

Here is my goal:

class GlideCropWithRadioTransformation(private val radio: Float) : BitmapTransformation() {

    companion object {
        private val VERSION = 1
        private val ID = "com.xxx.utils.GlideCropWithRadioTransformation.$VERSION"
    }

    override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
        val srcRadio = toTransform.width.toFloat() / toTransform.height.toFloat()
        val destWidth: Int
        val destHeight: Int
        return when {
            srcRadio == radio -> {
                toTransform
            }
            srcRadio < radio -> {
                destWidth = toTransform.width
                destHeight = (destWidth / radio).toInt()
                Bitmap.createBitmap(toTransform, 0, (toTransform.height - destHeight) / 2, destWidth, destHeight)
            }
            else -> {
                destHeight = toTransform.height
                destWidth = (destHeight * radio).toInt()
                Bitmap.createBitmap(toTransform, (toTransform.width - destWidth) / 2, 0, destWidth, destHeight)
            }
        }
    }

    override fun toString(): String {
        return "GlideCropWithRadioTransformation($radio)"
    }

    override fun equals(other: Any?): Boolean {
        return other is GlideCropWithRadioTransformation && other.radio == radio
    }

    override fun hashCode(): Int {
        return ID.hashCode() + radio.hashCode()
    }

    override fun updateDiskCacheKey(messageDigest: MessageDigest) {
        messageDigest.update((ID + radio).toByteArray(CHARSET))
    }
}

Is there anything that can be improved?

mafanwei commented 3 years ago

Are there other more efficient ways to avoid using Bitmap.createBitmap()? @sjudd