Tencent/tinker · error · TinkerRuntimeException
can't recognize zip dex format file:%s
Error message
can't recognize zip dex format file:%s
What it means
Thrown while repacking a patched dex: the old dex in the base APK was detected as a ZIP container (not a raw dex), but the code could not find a 'classes.dex' entry inside it after walking all entries with ZipInputStream. Tinker assumes any non-raw dex is a jar whose dex payload lives under the DEX_IN_JAR ('classes.dex') entry; when that assumption fails, the old-dex stream is unrecognizable and patching cannot proceed.
Source
Thrown at tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/DexDiffPatchInternal.java:758
if (!isRawDexFile || patchInfo.isJarMode) {
ZipOutputStream zos = null;
try {
if (patchedDexFile.exists()) {
patchedDexFile.delete();
}
zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(patchedDexFile)));
zos.putNextEntry(new ZipEntry(ShareConstants.DEX_IN_JAR));
// Old dex is not a raw dex file.
if (!isRawDexFile) {
ZipInputStream zis = null;
try {
zis = new ZipInputStream(oldDexStream);
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (ShareConstants.DEX_IN_JAR.equals(entry.getName())) break;
}
if (entry == null) {
throw new TinkerRuntimeException("can't recognize zip dex format file:" + patchedDexFile.getAbsolutePath());
}
new DexPatchApplier(zis, patchFileStream).executeAndSaveTo(zos);
} finally {
IOHelper.closeQuietly(zis);
}
} else {
new DexPatchApplier(oldDexStream, patchFileStream).executeAndSaveTo(zos);
}
zos.closeEntry();
} finally {
IOHelper.closeQuietly(zos);
}
} else {
new DexPatchApplier(oldDexStream, patchFileStream).executeAndSaveTo(patchedDexFile);
}
} finally {
IOHelper.closeQuietly(oldDexStream);
IOHelper.closeQuietly(patchFileStream);View on GitHub (pinned to 1b7ea02c23)
Solutions
- Regenerate the patch using the exact APK that is actually distributed and installed (same channel/repack step), not a pre-processing build.
- Avoid dex-encrypting packers on builds intended to receive tinker dex patches, or exclude dex patching (tinkerFlags without TINKER_DEX) for those channels.
- Verify the old APK's dex container manually: unzip the base APK, open the jar-packed dex, and confirm a 'classes.dex' entry exists.
- If dex entries are renamed by your build, keep the canonical 'classes.dex' name so tinker can find it.
Example fix
// before (build.gradle): patch built from the un-repacked release apk // apk: build/outputs/apk/release/app-release.apk // after: build the patch from the exact distributed artifact // apk: dist/app-release-jiagu-signed.apk (after channel/repack tool, aligned+signed)
Defensive patterns
Strategy: validation
Validate before calling
// Before distributing dex patches, sanity-check the base APK's jar-packed dex
ZipFile apk = new ZipFile(baseApk);
// for each dex tinker will treat as non-raw, confirm classes.dex exists inside
ZipInputStream zis = new ZipInputStream(apk.getInputStream(dexEntry));
ZipEntry e; boolean found = false;
while ((e = zis.getNextEntry()) != null) {
if ("classes.dex".equals(e.getName())) { found = true; break; }
}
if (!found) throw new IllegalStateException("base apk dex container not tinker-compatible"); Try / catch
try {
TinkerInstaller.onReceiveUpgradePatch(context, patchPath);
} catch (TinkerRuntimeException e) {
if (e.getMessage() != null && e.getMessage().contains("can't recognize zip dex format")) {
// base APK dex container incompatible: fall back to full upgrade, do not retry this patch
markChannelForFullUpgrade();
}
} Prevention
- Build patches from the exact distributed APK (after channel/repack/packing tools run).
- Do not ship dex patches for APKs processed by dex-encrypting packers; disable TINKER_DEX for those channels.
- Add a CI step that opens the release APK and asserts the classes.dex entry exists in any jar-packed dex.
When it happens
Trigger: DexDiffPatchInternal tries the non-raw-dex branch (isRawDexFile == false, typically dex files minified/renamed inside the APK such as 'assets/...' jar-packed dex) and ZipInputStream.getNextEntry() exhausts the stream without any entry named exactly 'classes.dex'.
Common situations: Base APK processed by a packer/channel tool (360 Jiagu, Bangcle, Ijiami) or an aggressive optimizer that repacks or encrypts dex containers; patch generated from the pre-obfuscated APK but applied against a repacked distribution APK; entry renamed to something other than classes.dex inside the jar.
Related errors
- Expected ${DEX_IN_JAR_NAME} in ${file}
- No entries
- patch ${type} extract failed (${message}).
- patch %s extract failed (%s).
- zipEntry is null when get from oldApk
AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14).
Data as JSON: /api/errors/3a7172ece613d3a8.
Report an issue: GitHub.