didi/DoKit · error · IllegalStateException
Target callback must not recycle bitmap!
Error message
Target callback must not recycle bitmap!
What it means
Thrown by TargetAction.complete(Bitmap, LoadedFrom) after it invokes target.onBitmapLoaded(result, from) and then finds result.isRecycled() is true. Picasso retains ownership of delivered bitmaps — they may still live in the memory cache and be drawn later — so a Target that recycles the bitmap inside onBitmapLoaded corrupts shared state. The library detects this immediately and throws IllegalStateException.
Source
Thrown at Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/TargetAction.java:38
final class TargetAction extends Action<Target> {
TargetAction(DokitPicasso picasso, Target target, Request data, int memoryPolicy, int networkPolicy,
Drawable errorDrawable, String key, Object tag, int errorResId) {
super(picasso, target, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key, tag,
false);
}
@Override void complete(Bitmap result, DokitPicasso.LoadedFrom from) {
if (result == null) {
throw new AssertionError(
String.format("Attempted to complete action with no result!\n%s", this));
}
Target target = getTarget();
if (target != null) {
target.onBitmapLoaded(result, from);
if (result.isRecycled()) {
throw new IllegalStateException("Target callback must not recycle bitmap!");
}
}
}
@Override void error() {
Target target = getTarget();
if (target != null) {
if (errorResId != 0) {
target.onBitmapFailed(picasso.context.getResources().getDrawable(errorResId));
} else {
target.onBitmapFailed(errorDrawable);
}
}
}
}
View on GitHub (pinned to 626827cddb)
Solutions
- Remove every bitmap.recycle() call inside onBitmapLoaded; Picasso manages the bitmap lifecycle, including cache eviction.
- If you mutate the bitmap, do it via Picasso's transform(RequestHandler.Transform) API — Picasso recycles the source of a transformation itself.
- If you truly need exclusive ownership, load with .memoryPolicy(MemoryPolicy.NO_CACHE) and understand the consequences instead of recycling a cached bitmap.
Example fix
// before
@Override public void onBitmapLoaded(Bitmap b, Picasso.LoadedFrom from) {
imageView.setImageBitmap(b);
b.recycle(); // crashes: bitmap may be cached/shared
}
// after
@Override public void onBitmapLoaded(Bitmap b, Picasso.LoadedFrom from) {
imageView.setImageBitmap(b); // Picasso owns lifecycle; do not recycle
} Defensive patterns
Strategy: validation
Validate before calling
// Contract: never recycle inside onBitmapLoaded
@Override public void onBitmapLoaded(Bitmap b, Picasso.LoadedFrom from) {
imageView.setImageBitmap(b);
// no b.recycle() anywhere in this callback
} Try / catch
Catching this IllegalStateException is wrong — recycling already corrupted shared cache state. Prevent it by auditing every recycle() call in Target implementations.
Prevention
- Picasso owns bitmap lifecycle; never call recycle() on delivered bitmaps.
- Use Transform for bitmap mutation; Picasso recycles the source itself.
- Grep for '.recycle()' in any class implementing Target.
When it happens
Trigger: Implementing Target.onBitmapLoaded to call bitmap.recycle() (directly or via a helper like a custom drawable's recycle, or transforming the bitmap and recycling the original); the check runs right after the callback returns.
Common situations: Legacy code from the pre-Picasso era where manual recycling was routine; calling bitmap.recycle() after setting it on a custom view 'to free memory'; recycling in onBitmapLoaded before pushing the bitmap through another pipeline.
Related errors
- Negative size: " + bitmap
- Transformation " + transformation.key() + " crashed with exc
- Transformation " + transformation.key() + " returned null af
- Transformation " + transformation.key() + " returned input B
- Transformation <transformation.key()> mutated input Bitmap b
AI-assisted analysis of didi/DoKit@626827cddb (2026-08-14).
Data as JSON: /api/errors/69c50b2e3904eacb.
Report an issue: GitHub.