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
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 !!!
Most helpful comment
I also faced this issue. Why isn't Picasso checking for recycling?