didi/DoKit · error · IllegalStateException
Transformation <transformation.key()> mutated input Bitmap b
Error message
Transformation <transformation.key()> mutated input Bitmap but failed to recycle the original.
What it means
The complementary guard to error 86: when a Transformation returns a DIFFERENT bitmap (newResult != result), Picasso requires the original input to have been recycled. If result.isRecycled() is still false, it posts IllegalStateException('... mutated input Bitmap but failed to recycle the original.') and fails the request. This leaks otherwise — Picasso's memory model expects transformations to hand ownership of exactly one bitmap back.
Source
Thrown at Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/BitmapHunter.java:473
return null;
}
if (newResult == result && result.isRecycled()) {
DokitPicasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new IllegalStateException("Transformation "
+ transformation.key()
+ " returned input Bitmap but recycled it.");
}
});
return null;
}
// If the transformation returned a new bitmap ensure they recycled the original.
if (newResult != result && !result.isRecycled()) {
DokitPicasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new IllegalStateException("Transformation "
+ transformation.key()
+ " mutated input Bitmap but failed to recycle the original.");
}
});
return null;
}
result = newResult;
}
return result;
}
static Bitmap transformResult(Request data, Bitmap result, int exifRotation) {
int inWidth = result.getWidth();
int inHeight = result.getHeight();
boolean onlyScaleDown = data.onlyScaleDown;
int drawX = 0;View on GitHub (pinned to 626827cddb)
Solutions
- Add src.recycle() whenever you return a bitmap other than src
- Use the canonical pattern: Bitmap out = ...; if (out != src) src.recycle(); return out;
- If you must keep the input alive for caching, return src (unmodified) instead and copy inside your cache logic
Example fix
// before
@Override public Bitmap transform(Bitmap src) {
return Bitmap.createScaledBitmap(src, 200, 200, true); // src never recycled
}
// after
@Override public Bitmap transform(Bitmap src) {
Bitmap out = Bitmap.createScaledBitmap(src, 200, 200, true);
if (out != src) src.recycle();
return out;
} Defensive patterns
Strategy: validation
Validate before calling
// Use one helper for the ownership handoff so the pattern is never forgotten:
static Bitmap handoff(Bitmap src, Bitmap out) {
if (out != src && !src.isRecycled()) src.recycle();
return out;
}
// in transform(): return handoff(src, Bitmap.createScaledBitmap(src, w, h, true)); Try / catch
// Main-thread throw from Picasso; prevention lives inside the Transformation:
@Override public Bitmap transform(Bitmap src) {
Bitmap out = Bitmap.createBitmap(src, 0, 0, w, h);
if (out != src) src.recycle(); // required
return out;
} Prevention
- Whenever transform returns a different instance, recycle the input in the same statement block
- Centralize the 'create + recycle + return' pattern in a shared utility
- Do not keep references to the input bitmap after returning a new one
When it happens
Trigger: A transform that does Bitmap out = Bitmap.createBitmap(...); return out; without calling src.recycle(); returning a cached/derived bitmap while leaving the input alive.
Common situations: Transformations written for Glide or plain BitmapFactory code where the caller manages recycling; forgetting the recycle after refactoring a transform to return a new instance.
Related errors
- Transformation " + transformation.key() + " returned input B
- Transformation " + transformation.key() + " crashed with exc
- Transformation " + transformation.key() + " returned null af
- Bitmap config must not be null.
- Target callback must not recycle bitmap!
AI-assisted analysis of didi/DoKit@626827cddb (2026-08-14).
Data as JSON: /api/errors/504a817b8c06f6f4.
Report an issue: GitHub.