{"record":{"id":"7a0841ce963cfef1","repo":"koral--/android-gif-drawable","slug":"bitmap-is-recycled","errorCode":null,"errorMessage":"Bitmap is recycled","messagePattern":"Bitmap is recycled","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"android-gif-drawable/src/main/java/pl/droidsonroids/gif/GifDecoder.java","lineNumber":162,"sourceCode":"\t}\n\n\t/**\n\t * @return true if GIF is animated (has at least 2 frames and positive duration), false otherwise\n\t */\n\tpublic boolean isAnimated() {\n\t\treturn mGifInfoHandle.getNumberOfFrames() > 1 && getDuration() > 0;\n\t}\n\n\t/**\n\t * See {@link GifDrawable#recycle()}\n\t */\n\tpublic void recycle() {\n\t\tmGifInfoHandle.recycle();\n\t}\n\n\tprivate void checkBuffer(final Bitmap buffer) {\n\t\tif (buffer.isRecycled()) {\n\t\t\tthrow new IllegalArgumentException(\"Bitmap is recycled\");\n\t\t}\n\t\tif (buffer.getWidth() < mGifInfoHandle.getWidth() || buffer.getHeight() < mGifInfoHandle.getHeight()) {\n\t\t\tthrow new IllegalArgumentException(\"Bitmap ia too small, size must be greater than or equal to GIF size\");\n\t\t}\n\t\tif (buffer.getConfig() != Bitmap.Config.ARGB_8888) {\n\t\t\tthrow new IllegalArgumentException(\"Only Config.ARGB_8888 is supported. Current bitmap config: \" + buffer.getConfig());\n\t\t}\n\t}\n}\n","sourceCodeStart":144,"sourceCodeEnd":172,"githubUrl":"https://github.com/koral--/android-gif-drawable/blob/26ff795f78b7fe8bbaf375a947b2bda95fc58e3d/android-gif-drawable/src/main/java/pl/droidsonroids/gif/GifDecoder.java#L144-L172","documentation":"checkBuffer validates the Bitmap passed to seekToTime/seekToFrame before native code renders into it. If the bitmap has been recycled, drawing into it would crash the native layer, so the library throws IllegalArgumentException early with a clear message.","triggerScenarios":"Calling seekToTime(millis, bitmap) or seekToFrame(index, bitmap) on GifDecoder with a Bitmap on which recycle() was already called (e.g. after releasing previous resources, or bitmap evicted and recycled by your own cache).","commonSituations":"Reusing a bitmap field after an activity/fragment teardown recycled it; bitmap pools recycling on background threads while a seek is in flight; confusing GifDrawable's internal buffer with a user-supplied bitmap that was recycled elsewhere.","solutions":["Do not recycle the bitmap while the decoder may still use it; keep a strong reference until done seeking","Check bitmap.isRecycled() before calling seekTo* and allocate a fresh ARGB_8888 bitmap if recycled","Wrap seek calls in try-catch for IllegalArgumentException and recreate the buffer"],"exampleFix":"// before\ndecoder.seekToFrame(frameIndex, bitmap); // bitmap may be recycled\n// after\nif (bitmap == null || bitmap.isRecycled()) {\n    bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);\n}\ndecoder.seekToFrame(frameIndex, bitmap);","handlingStrategy":"validation","validationCode":"if (buffer == null || buffer.isRecycled()) { buffer = Bitmap.createBitmap(decoder.getWidth(), decoder.getHeight(), Bitmap.Config.ARGB_8888); }","typeGuard":"Bitmap ensureLiveBitmap(Bitmap b, int w, int h) { return (b == null || b.isRecycled() || b.getWidth() < w || b.getHeight() < h) ? Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) : b; }","tryCatchPattern":"try { decoder.seekToFrame(i, buffer); } catch (IllegalArgumentException e) { buffer = Bitmap.createBitmap(decoder.getWidth(), decoder.getHeight(), Bitmap.Config.ARGB_8888); decoder.seekToFrame(i, buffer); }","preventionTips":["Never recycle a bitmap still referenced by an active GifDecoder","Centralize buffer creation in one helper that checks isRecycled()","Tie buffer lifetime to the decoder's lifecycle"],"tags":["android","gif","bitmap","lifecycle"],"backgroundTag":"invalid-argument-value","analyzedSha":"26ff795f78b7fe8bbaf375a947b2bda95fc58e3d","analyzedAt":"2026-09-10T14:37:11.595Z","contentChangedAt":"2026-09-10T14:37:11.595Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}