I'm creating a RecyclerView
to show a Grid of pictures. When selecting one of them, it should open a new activity with a transition.
I'm using Glide library to load the pictures and the transition looks awful because it reloads the picture in the new activity. So I had to save it in cache, and then use it for the transition.
I have the code, but sometimes if the picture doesn't load, it throws a Canvas RuntimeException.
This is the log:
07-03 15:19:58.633 28461-28461/jahirfiquitiva.project E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: jahirfiquitiva.project, PID: 28461
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@29f09d20
at android.graphics.Canvas.throwIfCannotDraw(Canvas.java:1282)
at android.view.GLES20Canvas.drawBitmap(GLES20Canvas.java:599)
at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:538)
at android.widget.ImageView.onDraw(ImageView.java:1176)
at android.view.View.draw(View.java:15239)
at android.view.View.updateDisplayListIfDirty(View.java:14175)
at android.view.View.getDisplayList(View.java:14197)
at android.view.GhostView.onDraw(GhostView.java:52)
at android.view.View.draw(View.java:15239)
at android.view.View.updateDisplayListIfDirty(View.java:14175)
at android.view.View.getDisplayList(View.java:14197)
at android.view.View.draw(View.java:14967)
at android.view.ViewGroup.drawChild(ViewGroup.java:3406)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3199)
at android.view.View.updateDisplayListIfDirty(View.java:14170)
at android.view.View.getDisplayList(View.java:14197)
at android.view.View.draw(View.java:14967)
at android.view.ViewGroup.drawChild(ViewGroup.java:3406)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3199)
at android.view.ViewOverlay$OverlayViewGroup.dispatchDraw(ViewOverlay.java:219)
at android.view.View.draw(View.java:15248)
at android.widget.FrameLayout.draw(FrameLayout.java:598)
at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:2906)
at android.view.View.updateDisplayListIfDirty(View.java:14175)
at android.view.View.getDisplayList(View.java:14197)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:273)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:279)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:318)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:2536)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2352)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1982)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1061)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5891)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:767)
at android.view.Choreographer.doCallbacks(Choreographer.java:580)
at android.view.Choreographer.doFrame(Choreographer.java:550)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:753)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5289)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:699)
This is the code to open the other activity and save the picture as cache:
private void openViewer(WallpapersAdapter.WallsHolder wallsHolder, int index, final HashMap<String, String> data) {
final Intent intent = new Intent(wallsActivity, ViewerActivity.class);
intent.putExtra("wallUrl", data.get(WallpapersActivity.WALL));
intent.putExtra("wallName", data.get(WallpapersActivity.NAME));
intent.putExtra("transitionName", ViewCompat.getTransitionName(wallsHolder.wall));
//save image from drawable
//get its path and send it to activity
Bitmap bitmap = drawableToBitmap(wallsHolder.wall.getDrawable());
//Convert to byte array and send to the other activity
Log.e("Resolution", bitmap.getWidth() + "x" + bitmap.getHeight());
try {
//Write file
String filename = "bitmap.png";
FileOutputStream stream = this.openFileOutput(filename, Context.MODE_PRIVATE);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
//Cleanup
stream.close();
bitmap.recycle();
//Pop intent
intent.putExtra("image", filename);
} catch (Exception e) {
e.printStackTrace();
}
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
this, wallsHolder.wall, ViewCompat.getTransitionName(wallsHolder.wall));
startActivity(intent, options.toBundle());
}
public static Bitmap drawableToBitmap (Drawable drawable) {
Bitmap bitmap = null;
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
if(bitmapDrawable.getBitmap() != null) {
return bitmapDrawable.getBitmap();
}
}
if(drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
drawable.draw(canvas);
return bitmap;
}
What could I do to fix this issue? Thanks in advance.
The recycle() method allows an app to reclaim memory as soon as possible. Caution: You should use recycle() only when you are sure that the bitmap is no longer being used. If you call recycle() and later attempt to draw the bitmap, you will get the error: "Canvas: trying to use a recycled bitmap" .
Canvas is the place or medium where perfroms/executes the operation of drawing, and Bitmap is responsible for storing the pixel of the picture you draw.
Use this custom ImageView class
public class MyImageView extends ImageView {
public MyImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyImageView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
try {
super.onDraw(canvas);
} catch (Exception e) {
Log.i(MyImageView.class.getSimpleName(), "Catch Canvas: trying to use a recycled bitmap");
}
}
}
I suspect that once in a while your bitmap gets into the recycled state just before the Canvas
gets a chance to draw onto it here drawable.draw(canvas);
.
A quick solution should be not to call bitmap.recycle();
, which is not strictly required for android >2.3.3. If you still want to reclaim this memory forcefully, you'll have to find a way to check when the bitmap is indeed no longer needed (i.e., Canvas
had a chance to finish its drawing operations).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With