didi/DoKit · error · IllegalStateException

Transformation " + transformation.key() + " returned input B

Error message

Transformation " + transformation.key() + " returned input Bitmap but recycled it.

What it means

A Transformation returned the same Bitmap instance it was given (newResult == result) but that bitmap is now recycled. Picasso's contract lets a transformation return the input bitmap only if it is still usable; returning a recycled input is a memory-corruption hazard, so Picasso posts IllegalStateException('... returned input Bitmap but recycled it.') on the main thread and fails the request.

Source

Thrown at Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/BitmapHunter.java:461

            .append(transformation.key())
            .append(" returned null after ")
            .append(i)
            .append(" previous transformation(s).\n\nTransformation list:\n");
        for (Transformation t : transformations) {
          builder.append(t.key()).append('\n');
        }
        DokitPicasso.HANDLER.post(new Runnable() {
          @Override public void run() {
            throw new NullPointerException(builder.toString());
          }
        });
        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;
      }

View on GitHub (pinned to 626827cddb)

Solutions

  1. Never recycle the input bitmap in transform(); Picasso owns it
  2. If you create a new bitmap, recycle the input and return the NEW bitmap (that is the legal pattern — see error 87)
  3. Audit any src.recycle() call inside Transformation implementations and delete it when src is returned

Example fix

// before
@Override public Bitmap transform(Bitmap src) {
  Bitmap out = Bitmap.createScaledBitmap(src, 100, 100, true);
  src.recycle();
  return src; // recycled input returned -> IllegalStateException
}

// after
@Override public Bitmap transform(Bitmap src) {
  Bitmap out = Bitmap.createScaledBitmap(src, 100, 100, true);
  if (out != src) src.recycle();
  return out; // return the new bitmap
}
Defensive patterns

Strategy: validation

Validate before calling

// Canonical safe pattern: return the input un-recycled, or a new bitmap with input recycled.
static Bitmap finishTransform(Bitmap src, Bitmap out) {
  if (out == src && src.isRecycled()) throw new AssertionError("input recycled"); // catch in dev
  return out;
}

Try / catch

// Posted to the main thread by Picasso; cannot be caught around .into().
// Fix the transformation source:
// WRONG: src.recycle(); return src;
// RIGHT: Bitmap out = ...; if (out != src) src.recycle(); return out;

Prevention

When it happens

Trigger: Transform code that calls src.recycle() and then returns src; a helper that recycles 'temporaries' but includes the input; double transformations where the first recycles and the second returns it.

Common situations: Over-aggressive memory optimization copying the 'recycle what you allocate' pattern onto the input bitmap; merging code from two transformations with different recycle conventions.

Related errors


AI-assisted analysis of didi/DoKit@626827cddb (2026-08-14). Data as JSON: /api/errors/d4962f6965be77c2. Report an issue: GitHub.