Tencent/tinker · error · IOException

dex2oat works unsuccessfully, exit code:

Error message

dex2oat works unsuccessfully, exit code: 

What it means

In interpret-mode fallback Tinker forks a dex2oat process directly (with --compiler-filter=interpret-only) and waits for it. This IOException means dex2oat exited with a non-zero status, so no usable odex was produced for the patched dex.

Source

Thrown at tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerDexOptimizer.java:612

            }
            commandAndParams.add("--dex-file=" + dexFilePath);
            commandAndParams.add("--oat-file=" + oatFilePath);
            commandAndParams.add("--instruction-set=" + targetISA);
            if (Build.VERSION.SDK_INT > 25) {
                commandAndParams.add("--compiler-filter=quicken");
            } else {
                commandAndParams.add("--compiler-filter=interpret-only");
            }

            final ProcessBuilder pb = new ProcessBuilder(commandAndParams);
            pb.redirectErrorStream(true);
            final Process dex2oatProcess = pb.start();
            StreamConsumer.consumeInputStream(dex2oatProcess.getInputStream());
            StreamConsumer.consumeInputStream(dex2oatProcess.getErrorStream());
            try {
                final int ret = dex2oatProcess.waitFor();
                if (ret != 0) {
                    throw new IOException("dex2oat works unsuccessfully, exit code: " + ret);
                }
            } catch (InterruptedException e) {
                throw new IOException("dex2oat is interrupted, msg: " + e.getMessage(), e);
            }
        } finally {
            try {
                if (fileLock != null) {
                    fileLock.close();
                }
            } catch (IOException e) {
                ShareTinkerLog.w(TAG, "release interpret Lock error", e);
            }
        }
    }

    private static class StreamConsumer {
        static final Executor STREAM_CONSUMER = Executors.newSingleThreadExecutor();

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Regenerate the patch with the Tinker gradle plugin version matching the Tinker SDK in use.
  2. Validate the patched dex (dexdump / rebuilt with same build-tools) and confirm the app builds and runs the same classes unpatched.
  3. Capture StreamConsumer output for dex2oat (adb logcat) to get the exact reason for the non-zero exit.
  4. If the failure is device-specific (ISA or ART flag change), update Tinker, which carries per-OS dex2oat argument fixes.
Defensive patterns

Strategy: fallback

Validate before calling

// Before loading: sanity-check extracted dex files
for (File dex : patchDexDir.listFiles()) {
    if (dex.length() == 0 || !SharePatchFileUtil.verifyDexFileMd5(dex, expected)) {
        rejectPatch();
    }
}

Try / catch

try {
    runDex2oatInterpretOnly(dexFile, oatFile);
} catch (IOException e) {
    // dex2oat failed: run the dex purely interpreted (no odex needed)
    DexExtractHelper.loadDexInterpreted(classLoader, dexFile);
}

Prevention

When it happens

Trigger: dex2oat subprocess failing: corrupted or truncated patched dex, wrong dex2oat binary/arguments for the device's ART version, ISA mismatch (32/64-bit), or dex2oat crashing due to low memory or an unsupported dex feature in the patch.

Common situations: Patch generated by an incompatible build-tools/gradle-plugin version; proguard/R8 output that the on-device dex2oat cannot process; Android 12+ where dex2oat flag sets changed; multi-dex patches where one fragment is bad.

Related errors


AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14). Data as JSON: /api/errors/00a63959e173c26b. Report an issue: GitHub.