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
- Regenerate the patch with the Tinker gradle plugin version matching the Tinker SDK in use.
- Validate the patched dex (dexdump / rebuilt with same build-tools) and confirm the app builds and runs the same classes unpatched.
- Capture StreamConsumer output for dex2oat (adb logcat) to get the exact reason for the non-zero exit.
- 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
- Always verify patch package checksums before extraction and load.
- Keep gradle plugin and Tinker SDK versions in lockstep.
- Enable Tinker's patch verification (tinkerLoadVerifyFlag) in production.
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
- No odex file was generated after calling performDexOptSecond
- dex2oat is interrupted, msg:
- No entries
- libName or context is null!
- libName or appLike is null!
AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14).
Data as JSON: /api/errors/00a63959e173c26b.
Report an issue: GitHub.