didi/DoKit · error · RuntimeException

Transformation " + transformation.key() + " crashed with exc

Error message

Transformation " + transformation.key() + " crashed with exception.

What it means

applyCustomTransformations catches a RuntimeException thrown by a Transformation.transform(bitmap) and rethrows it (with the transformation's key in the message) on the main thread via DokitPicasso.HANDLER.post, returning null so the request fails. The original exception is attached as the cause. The message identifies exactly which transformation in the chain crashed — the real diagnosis is in the cause.

Source

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

      RequestHandler requestHandler = requestHandlers.get(i);
      if (requestHandler.canHandleRequest(request)) {
        return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
      }
    }

    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
  }

  static Bitmap applyCustomTransformations(List<Transformation> transformations, Bitmap result) {
    for (int i = 0, count = transformations.size(); i < count; i++) {
      final Transformation transformation = transformations.get(i);
      Bitmap newResult;
      try {
        newResult = transformation.transform(result);
      } catch (final RuntimeException e) {
        DokitPicasso.HANDLER.post(new Runnable() {
          @Override public void run() {
            throw new RuntimeException(
                "Transformation " + transformation.key() + " crashed with exception.", e);
          }
        });
        return null;
      }

      if (newResult == null) {
        final StringBuilder builder = new StringBuilder() //
            .append("Transformation ")
            .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() {

View on GitHub (pinned to 626827cddb)

Solutions

  1. Read the cause stack trace in logcat — it names the line inside your Transformation that threw
  2. Guard size math: skip scaling when src.getWidth()/getHeight() <= 0 or target dims are 0
  3. Pair the transform with .resize(w, h) / .fit() so dimensions are known before transforming
  4. Recycle intermediate bitmaps you create and never recycle the input bitmap (that is error 86)
  5. For memory-bound transforms, force a smaller decode first: .config(Bitmap.Config.RGB_565) or lower resize

Example fix

// before
class CropSquare implements Transformation {
  @Override public Bitmap transform(Bitmap src) {
    int size = Math.min(src.getWidth(), src.getHeight());
    return Bitmap.createBitmap(src, (src.getWidth()-size)/2, (src.getHeight()-size)/2, size, size);
  }
  @Override public String key() { return "square()"; }
}

// after
@Override public Bitmap transform(Bitmap src) {
  if (src.isRecycled() || src.getWidth() == 0 || src.getHeight() == 0) return src;
  int size = Math.min(src.getWidth(), src.getHeight());
  Bitmap out = Bitmap.createBitmap(src, (src.getWidth()-size)/2, (src.getHeight()-size)/2, size, size);
  if (out != src) src.recycle();
  return out;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Harden the transformation itself rather than validating at the call site:
static Bitmap safeTransform(Transformation t, Bitmap src) {
  if (src == null || src.isRecycled() || src.getWidth() == 0 || src.getHeight() == 0) return src;
  try { return t.transform(src); } catch (RuntimeException e) { return src; }
}

Try / catch

// Picasso rethrows on the MAIN thread via HANDLER.post, so a caller try-catch around
// .into() will NOT catch it. Set a default Thread.UncaughtExceptionHandler during debug,
or// make transformations exception-free (see validationCode). The cause chain in logcat
// names the failing transformation — fix it there.

Prevention

When it happens

Trigger: A custom Transformation (set via .transform(t)) throwing on OOM, on recycled-input bitmaps, on ARGB_8888 vs RGB_565 config assumptions, or on zero-sized bitmaps when targetWidth/targetHeight were never resolved (e.g. into(notification) without resize).

Common situations: Rounded-corner or blur transformations on huge bitmaps throwing OutOfMemoryError-adjusive IllegalArgumentException; transform that calls Bitmap.createScaledBitmap with width/height of 0 because the request had no target size; transformation run on a bitmap already recycled by a previous transformation.

Related errors


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