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

  1. Regenerate the patch using the exact APK that is actually distributed and installed (same channel/repack step), not a pre-processing build.
  2. Avoid dex-encrypting packers on builds intended to receive tinker dex patches, or exclude dex patching (tinkerFlags without TINKER_DEX) for those channels.
  3. Verify the old APK's dex container manually: unzip the base APK, open the jar-packed dex, and confirm a 'classes.dex' entry exists.
  4. 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

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


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