asLody/VirtualApp · warning · IOException
dex2oat works unsuccessfully, exit code
Error message
dex2oat works unsuccessfully, exit code: ${ret} What it means
ArtDexOptimizer.interpretDex2Oat shells out to the dex2oat binary to compile the plugin's dex into OAT/odex. If the dex2oat process exits with a non-zero status, the library throws this IOException, meaning the plugin's native compilation failed (though the app may still run in interpreter mode).
Solutions
- Check the dex2oat stderr output (consumed by StreamConsumer) by rerunning the dex2oat command manually for the real failure reason
- Disable/relax OAT compilation so the plugin runs via interpreter fallback (skip optimize) — the app usually still works
- Free disk space in the plugin data/OAT directory and retry
- Verify the plugin APK's dex is valid and the device ROM's dex2oat supports it
Example fix
// before
// throws and aborts when dex2oat fails
ArtDexOptimizer.interpretDex2Oat(dexPath, oatPath, ...);
// after
try {
ArtDexOptimizer.interpretDex2Oat(dexPath, oatPath, ...);
} catch (IOException e) {
Log.w(TAG, "dex2oat failed, falling back to interpreter mode", e); // continue without OAT
} Defensive patterns
Strategy: fallback
Validate before calling
File oatDir = new File(oatPath).getParentFile(); if (!oatDir.canWrite() || oatDir.getUsableSpace() < requiredBytes) { skipOptimization(); } Try / catch
try { ArtDexOptimizer.interpretDex2Oat(dex, oat, ...); } catch (IOException e) { Log.w(TAG, "OAT skipped: " + e.getMessage()); /* proceed in interpreter mode */ } Prevention
- Treat OAT optimization as best-effort; always allow interpreter-mode fallback
- Monitor free disk space in the OAT output directory
- Capture dex2oat stderr for diagnostics before discarding streams
When it happens
Trigger: dex2oat exits non-zero: unsupported dex bytecode, corrupted APK/dex, insufficient disk space in the OAT output directory, incompatible dex2oat version on the device ROM, or invalid compiler flags.
Common situations: Custom ROMs with a broken/missing dex2oat; Android version mismatch between dex flags and the ART version; out-of-space on /data; plugin APK containing desugared/preview bytecode the device dex2oat can't handle.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- dex2oat is interrupted, msg
- Unable to create application
- Unable to start receiver
- VirtualCore.startup() must called in main thread.
- Initializer = NULL
AI-assisted analysis of asLody/VirtualApp@666fefcb5d (2026-09-09).
Data as JSON: /api/errors/07cd39ff94397ec3.
Report an issue: GitHub.
Appendix: source
Thrown at VirtualApp/lib/src/main/java/com/lody/virtual/helper/ArtDexOptimizer.java:59
}
commandAndParams.add("--dex-file=" + dexFilePath);
commandAndParams.add("--oat-file=" + oatFilePath);
commandAndParams.add("--instruction-set=" + VMRuntime.getCurrentInstructionSet.call());
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);
}
}
private static class StreamConsumer {
static final Executor STREAM_CONSUMER = Executors.newSingleThreadExecutor();
static void consumeInputStream(final InputStream is) {
STREAM_CONSUMER.execute(new Runnable() {
@Override
public void run() {
if (is == null) {
return;
}
final byte[] buffer = new byte[256];
try {View on GitHub (pinned to 666fefcb5d)