Search code examples
androidbitmapfresco

fresco Postprocessor:java.lang.RuntimeException: Canvas: trying to use a recycled bitmap


12-21 11:01:14.045: E/AndroidRuntime(6819): java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@4180103 12-21 11:01:14.045: E/AndroidRuntime(6819): at android.graphics.Canvas.throwIfCannotDraw(Canvas.java:1084) 12-21 11:01:14.045: E/AndroidRuntime(6819): at android.view.GLES20Canvas.drawBitmap(GLES20Canvas.java:844) 12-21 11:01:14.045: E/AndroidRuntime(6819): at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:490) 12-21 11:01:14.045: E/AndroidRuntime(6819): at android.widget.ImageView.onDraw(ImageView.java:1037) 12-21 11:01:14.045: E/AndroidRuntime(6819): at android.view.View.draw(View.java:14465) 12-21 11:01:14.045: E/AndroidRuntime(6819): at android.view.View.getDisplayList(View.java:13362) 12-21 11:01:14.045: E/AndroidRuntime(6819): at android.view.View.getDisplayList(View.java:13404) 12-21 11:01:14.045: E/AndroidRuntime(6819): at android.view.View.draw(View.java:14182) 12-21 11:01:14.045: E/AndroidRuntime(6819): at android.view.ViewGroup.drawChild(ViewGroup.java:3103) 12-21 11:01:14.045: E/AndroidRuntime(6819): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2940) 12-21 11:01:14.045: E/AndroidRuntime(6819): at android.widget.AbsListView.dispatchDraw(AbsListView.java:2458)

I build a new class and make it extends BasePostprocessor, I do nothing in it. But when it runs, the sample throw the above exception; I just use imagepipeline to download the image, don't use the simpledraweeview.

com.facebook.imagepipeline.request.ImageRequestBuilder requestBuilder=   com.facebook.imagepipeline.request.ImageRequestBuilder
.newBuilderWithSource(uri);

if (imageRequest.getTargetWidth() > 0 && imageRequest.getTargetHeight()  > 0) {
    requestBuilder.setResizeOptions(new com.facebook.imagepipeline.common.ResizeOptions(imageRequest
            .getTargetWidth(), imageRequest.getTargetHeight()));
}

requestBuilder.setAutoRotateEnabled(true);
requestBuilder.setPostprocessor(new FPostProcessor(getImageConfig()));
public class FPostProcessor extends BasePostprocessor{
private FImageConfig mImageConfig;

public FPostProcessor(FImageConfig imageConfig){
    mImageConfig = imageConfig;
}
/*
@Override
public CloseableReference<Bitmap> process(Bitmap sourceBitmap,    PlatformBitmapFactory bitmapFactory) {
return super.process(sourceBitmap, bitmapFactory);
}*/
}

Solution

  • I don't think this has anything to do with the postprocessor. If you are not using Drawee, but the imagepipeline directly you need to be very careful with how you deal with images. You should read the documentation about how to use the imagepipeline directly.

    I suspect that you are getting a ClsoeableReference<CloseableImage> from the pipeline and then you just take the Bitmap out of it and forget about the ClsoeableReference. This is a wrong thing to do. As soon as ClsoeableReference gets out of scope it will become susceptible for garbage collection and when GC happens the underlying bitmap may get recycled. Same is true even if you use BaseBitmapDataSubscriber that provides Bitmap directly. This is also covered in great detail in the documentation.

    The most likely reason why you experience this with postprocessor and not without it is because in the former case the bitmap cache may keep your bitmap alive. With your postprocessor this won't happen because it doesn't have caching enabled.

    In other words say that the pipeline returns a ClsoeableReference<CloseableImage> R1 that wraps CloseableImage I with Bitmap B. There will be another CloseableReference R2 that wraps that same CloseableImage I kept in the bitmap cache, until at some later point the cache decides to evict that image. Since both the cache and you have a reference to that image, it's reference count is 2. Even if you don't obey the Fresco API and you let your CloseableReference R1 get garbage collected, reference count will only drop to 1 because cache still has it in memory. However, you should not rely on that. That was without postprocessor.

    If you specify a postprocessor something slightly different happens. Pipeline will now cache the original CloseableImage I1 with Bitmap B1 but will return a new postprocessed CloseableImage I2 with Bitmap B2. Since your postprocessor doesn't have caching enabled, you will be the only holder of a reference to I2 and this time if you let it get garbage collected, you will left with a recycled bitmap because there is no cache to hide the issue.

    Just to be clear, proper solution is not to enable postprocessor caching! Proper solution is to handle the CloseableReference returned by the pipeline correctly, as explained in the documentation.

    Without providing the actual code of how you use the pipeline, I cannot tell for sure, but this is most likely what happens.