I'm developing an application which receives events from other application (login attempts), and then it shows the result of the login process in a dialog fragment. The login is done with Fingerprint recognition so in the result I need to show the fingerprint image (which is a bitmap).
The dialog works well and I get no errors, but the problem is: if the user attempts to log in very fast sometimes I'm getting this error while displaying the image:
W/dalvikvm( 1830): threadid=1: thread exiting with uncaught exception (group=0x2bf4efc0)
E/AndroidRuntime( 1830): FATAL EXCEPTION: main
E/AndroidRuntime( 1830): java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@2cd4a720
E/AndroidRuntime( 1830): at android.graphics.Canvas.throwIfRecycled(Canvas.java:1026)
E/AndroidRuntime( 1830): at android.graphics.Canvas.drawBitmap(Canvas.java:1127)
E/AndroidRuntime( 1830): at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:393)
E/AndroidRuntime( 1830): at android.view.View.draw(View.java:13443)
E/AndroidRuntime( 1830): at android.view.View.draw(View.java:13342)
E/AndroidRuntime( 1830): at android.view.ViewGroup.drawChild(ViewGroup.java:2929)
E/AndroidRuntime( 1830): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2799)
E/AndroidRuntime( 1830): at android.view.View.draw(View.java:13461)
E/AndroidRuntime( 1830): at android.view.View.draw(View.java:13342)
E/AndroidRuntime( 1830): at android.view.ViewGroup.drawChild(ViewGroup.java:2929)
E/AndroidRuntime( 1830): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2799)
E/AndroidRuntime( 1830): at android.view.View.draw(View.java:13461)
E/AndroidRuntime( 1830): at android.view.View.draw(View.java:13342)
E/AndroidRuntime( 1830): at android.view.ViewGroup.drawChild(ViewGroup.java:2929)
E/AndroidRuntime( 1830): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2799)
E/AndroidRuntime( 1830): at android.view.View.draw(View.java:13340)
E/AndroidRuntime( 1830): at android.view.ViewGroup.drawChild(ViewGroup.java:2929)
E/AndroidRuntime( 1830): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2799)
E/AndroidRuntime( 1830): at android.view.View.draw(View.java:13340)
E/AndroidRuntime( 1830): at android.view.ViewGroup.drawChild(ViewGroup.java:2929)
E/AndroidRuntime( 1830): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2799)
E/AndroidRuntime( 1830): at android.view.View.draw(View.java:13461)
E/AndroidRuntime( 1830): at android.widget.FrameLayout.draw(FrameLayout.java:467)
E/AndroidRuntime( 1830): at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:2188)
E/AndroidRuntime( 1830): at android.view.ViewRootImpl.drawSoftware(ViewRootImpl.java:2326)
E/AndroidRuntime( 1830): at android.view.ViewRootImpl.draw(ViewRootImpl.java:2221)
E/AndroidRuntime( 1830): at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2089)
E/AndroidRuntime( 1830): at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1864)
E/AndroidRuntime( 1830): at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1032)
E/AndroidRuntime( 1830): at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4282)
E/AndroidRuntime( 1830): at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725)
E/AndroidRuntime( 1830): at android.view.Choreographer.doCallbacks(Choreographer.java:555)
E/AndroidRuntime( 1830): at android.view.Choreographer.doFrame(Choreographer.java:525)
E/AndroidRuntime( 1830): at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711)
E/AndroidRuntime( 1830): at android.os.Handler.handleCallback(Handler.java:615)
E/AndroidRuntime( 1830): at android.os.Handler.dispatchMessage(Handler.java:92)
E/AndroidRuntime( 1830): at android.os.Looper.loop(Looper.java:137)
E/AndroidRuntime( 1830): at android.app.ActivityThread.main(ActivityThread.java:4745)
E/AndroidRuntime( 1830): at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime( 1830): at java.lang.reflect.Method.invoke(Method.java:511)
E/AndroidRuntime( 1830): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
E/AndroidRuntime( 1830): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
E/AndroidRuntime( 1830): at dalvik.system.NativeStart.main(Native Method)
My code to show / recycle the image is this:
public class FingerprintSettableImage implements SettableResultImage {
private final FingerPrintImageModel image;
private static Object lock = new Object();
private static final String TAG = FingerprintSettableImage.class
.getCanonicalName();
private static Bitmap previousFPImage = null;
public FingerprintSettableImage(final FingerPrintImageModel image) {
this.image = image;
}
@SuppressLint("NewApi")
@Override
public void bindToView(final ImageView view) {
synchronized (lock) {
if (image == null) {
view.setImageResource(R.drawable.drw_login_finger);
} else {
final Bitmap aux = ImageHelper.renderCroppedGreyscaleBitmap(
image.image, image.width, image.height);
final int fpImageDisplayRule = FPReaderSettings
.getImageDisplayRules(view.getContext());
if (fpImageDisplayRule == FPReaderSettings.DISPLAY_FP_IMAGE_FOR_PUNCH_AND_ENROLLMENT) {
Drawable d = new BitmapDrawable(view.getContext()
.getResources(), aux);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
view.setBackgroundDrawable(d);
} else {
view.setBackground(d);
}
} else {
view.setBackgroundResource(R.drawable.drw_login_default_finger_image);
}
view.setImageResource(R.drawable.drw_login_finger_transparent);
recycle();
previousFPImage = aux;
}
}
}
@Override
public void recycle() {
if (previousFPImage != null && !previousFPImage.isRecycled()) {
Log.d(TAG, "Recycling image " + previousFPImage.hashCode());
previousFPImage.recycle();
}
}
}
To display the image we use:
public class ImageHelper {
/**
* Using the array generate a Bitmap object
*
* @param yuvData
* @param width
* @param height
* @return
*/
public static Bitmap renderCroppedGreyscaleBitmap(byte[] yuvData,
final int width, final int height) {
int[] pixels = new int[width * height];
int inputOffset = 0;
for (int y = 0; y < height; y++) {
final int outputOffset = y * width;
for (int x = 0; x < width; x++) {
final int grey = yuvData[inputOffset + x] & 0xff;
pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101);
}
inputOffset += width;
}
final Bitmap bitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.RGB_565);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
yuvData = null;
pixels = null;
return bitmap;
}
}
I use the lock to try to ensure that only one image is loading at a time.
My relevant code of the dialog fragment is this:
@Override
public View onCreateView(final LayoutInflater inflater,
final ViewGroup container, final Bundle savedInstanceState) {
...
setResultImage();
...
}
private void setResultImage() {
FingerPrintImageModel fpImageModel = curLC.getFingerImage();
image = new FingerprintSettableImage(fpImageModel);
Log.d(TAG, "binding image" + aux.getTimestamp());
image.bindToView(imageFP1);
}
Maybe I'm doing some silly mistake, but I can't find it, can somebody see what's wrong?