Picasso: “Cannot draw recycled bitmaps” exception

Created on 14 Oct 2013  Â·  18Comments  Â·  Source: square/picasso

I've music player app which interacts with RemoteControlClient. I need to load album cover image to display it in lock screen remote contol. I'm trying to use Piccasso to achieve this. I've written the following code:

private final Target artworkTarget = new Target() {
    @Override
    public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) {
        remoteControlClient.editMetadata(false).putBitmap(RemoteControlClientCompat.MetadataEditorCompat.METADATA_KEY_ARTWORK,
                bitmap).apply();
    }

    @Override
    public void onBitmapFailed(Drawable drawable) {
        Log.e(Constants.LOG_TAG, "Artwork loading failed");
    }

    @Override
    public void onPrepareLoad(Drawable drawable) {
    }
};

...

private void playNextSong(int songPosition) {
    ...

    String artworkUrl = Constants.Urls.BASE_ARTWORK_URL + currentSong.getArtworkId();
    Picasso.with(this).load(artworkUrl).skipMemoryCache().into(artworkTarget);
    // Update the remote controls
    remoteControlClient.editMetadata(true)
                .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, currentSong.getArtist().getName())
                .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, currentSong.getAlbum().getName())
                .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, currentSong.getName())
                .apply();

   ...
   }

But sometimes I get 'Cannot draw recycled bitmaps' exception. Here is stacktrace:

10-14 10:40:53.999: ERROR/AndroidRuntime(28716): FATAL EXCEPTION: main
        java.lang.IllegalArgumentException: Cannot draw recycled bitmaps
        at android.view.GLES20Canvas.drawBitmap(GLES20Canvas.java:772)
        at android.view.GLES20RecordingCanvas.drawBitmap(GLES20RecordingCanvas.java:105)
        at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:440)
        at com.squareup.picasso.PicassoDrawable.draw(PicassoDrawable.java:96)
        at android.widget.ImageView.onDraw(ImageView.java:1025)
        at android.view.View.draw(View.java:13944)
        at android.view.View.getDisplayList(View.java:12838)
        at android.view.View.getDisplayList(View.java:12880)
        at android.view.View.draw(View.java:13657)
        at android.view.ViewGroup.drawChild(ViewGroup.java:3083)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2920)
        at android.view.View.draw(View.java:13947)
        at android.view.View.getDisplayList(View.java:12838)
        at android.view.View.getDisplayList(View.java:12880)
        at android.view.View.draw(View.java:13657)
        at android.view.ViewGroup.drawChild(ViewGroup.java:3083)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2920)
        at android.view.View.getDisplayList(View.java:12833)
        at android.view.View.getDisplayList(View.java:12880)
        at android.view.View.draw(View.java:13657)
        at android.view.ViewGroup.drawChild(ViewGroup.java:3083)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2920)
        at android.view.View.getDisplayList(View.java:12833)
        at android.view.View.getDisplayList(View.java:12880)
        at android.view.View.draw(View.java:13657)
        at android.view.ViewGroup.drawChild(ViewGroup.java:3083)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2920)
        at android.view.View.getDisplayList(View.java:12833)
        at android.view.View.getDisplayList(View.java:12880)
        at android.view.View.draw(View.java:13657)
        at android.view.ViewGroup.drawChild(ViewGroup.java:3083)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2920)
        at android.view.View.draw(View.java:13947)
        at android.widget.FrameLayout.draw(FrameLayout.java:467)
        at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:2224)
        at android.view.View.getDisplayList(View.java:12838)
        at android.view.View.getDisplayList(View.java:12880)
        at android.view.HardwareRenderer$GlRenderer.buildDisplayList(HardwareRenderer.java:1411)
        at android.view.HardwareRenderer$GlRenderer.draw(HardwareRenderer.java:1359)
        at android.view.ViewRootImpl.draw(ViewRootImpl.java:2367)
        at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2239)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1872)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1004)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5481)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:749)
        at android.view.Choreographer.doCallbacks(Choreographer.java:562)
        at android.view.Choreographer.doFrame(Choreographer.java:532)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735)
        at android.os.Handler.handleCallback(Handler.java:730)
        at android.os.Handler.dispatchMessage(Handler.java:92)
        at android.os.Looper.loop(Looper.java:137)
        at android.app.ActivityThread.main(ActivityThread.java:5103)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:525)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
        at dalvik.system.NativeStart.main(Native Method)

I didn't manage to find some steps to reproduce it, but if I remove skipMemoryCache application will crash every time I loading image.

Is something wrong with my code? Maybe I should use Picasso in some other way?

P.S. Picasso version 2.1.1

Most helpful comment

I also faced this issue. Why isn't Picasso checking for recycling?

public Bitmap get() {
    ...
    if (bitmap.isRecycled()) {
        return bitmap.copy(bitmap.getConfig(), true);
    } else {
        return bitmap;
    }
}

All 18 comments

Are you recycling bitmaps anywhere?

No, I don't recycle bitmaps in my code.

Well, something in your app is recycling bitmaps, and since bitmaps are cached a recycled bitmap is later returned. When you skip memory cache the bitmaps are always loaded from the network, so there's no chance of getting one that's been recycled.
You need to figure out where the recycling is happening.

But even when I skip memory cache sometimes I receive this error, not every time I load image, but still rather often.
Doesn't Picasso recycles bitmaps somewhere?

Picasso doesn't recycle bitmaps that has been passed to the user.

Interesting. Picasso won't recycle your bitmaps. I don't see you have any transformations applied either.

Care to provide a sample app that can easily reproduce this case? Perhaps its a content provider issue.

Can it be connected with concurrent requests? Because I load the same image in other place in my application.
I've created sample project to demonstrate this problem, please check it out here: https://github.com/Bersh/RandomMusicPlayer

It's simple adaptation of RandomMusicPlayer Android sample.
Unfortunately I still can't give you exact steps to reproduce.
It seems that to reproduce this issue you need to play song and go to next, after some times application crashs

Thanks for sample app. I'll be taking a look hopefully this weekend. :)

MetadataEditor#apply recycles old artwork: https://github.com/android/platform_frameworks_base/blob/master/media/java/android/media/RemoteControlClient.java#L556

Maybe LruCache needs to check if Bitmaps are recycled before returning.

Very interesting. I haven't worked with RemoteControlClient before on Android.

This definitely could be the culprit. If you want, you can provide a custom LruCache implementation to your Picasso instance that checks if the bitmap has been recycled, evicts it and returns null.

Should probably just skip the memory cache completely when you know beforehand that the target is going to recycle it. The bitmap could have been delivered elsewhere before MetadataEditor recycles it.

Skipping memory cache for all requests works for me. Thanks guys!
But maybe it's good idea for default cache implementation to check is bitmap recycled before returning it. What do you think about it?

The issue is that the Bitmap could have been delivered to several targets before one of them recycles it. It wouldn't solve the problem.

hm... I see. Anyway thanks for your help.

I had the same issue with another image loader library and a lock screen image. Because MetadataEditor sometimes recycles the artwork bitmap, the best solution is to copy the bitmap using Bitmap.copy() and pass the copy to the MetadataEditor.

I also faced this issue. Why isn't Picasso checking for recycling?

public Bitmap get() {
    ...
    if (bitmap.isRecycled()) {
        return bitmap.copy(bitmap.getConfig(), true);
    } else {
        return bitmap;
    }
}

Picasso doesn't recycle Bitmaps so it should not check that. When a Bitmap is recycled, it's gone. Your code will return an empty Bitmap with no pixels to display. The proper solution is to not recycle manually or give a copy of the Bitmap to the component which does the undesired recycling.

@PaulWoitaschek This solution is perfect !!!

Was this page helpful?
0 / 5 - 0 ratings