Tencent/tinker · error · TinkerRuntimeException

zipEntry is null when get from oldApk

Error message

zipEntry is null when get from oldApk

What it means

A defensive guard inside ResDiffPatchInternal's resource reconstruction loop: while enumerating entries of the base APK via TinkerZipFile.entries(), an element came back null. TinkerZipFile's enumeration is not expected to yield null, so hitting this indicates the old APK's central directory is damaged or the zip implementation returned an abnormal entry. It aborts resource patching before any output is trusted.

Source

Thrown at tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/ResDiffPatchInternal.java:150

            TinkerZipOutputStream out = null;
            TinkerZipFile oldApk = null;
            TinkerZipFile newApk = null;
            int totalEntryCount = 0;
            try {
                if (resOutput.exists()) {
                    resOutput.delete();
                }
                out = new TinkerZipOutputStream(new BufferedOutputStream(new FileOutputStream(resOutput)));
                if (ShareTinkerInternals.isNewerOrEqualThanVersion(33, true)) {
                    resOutput.setReadOnly();
                }
                oldApk = new TinkerZipFile(apkPath);
                newApk = new TinkerZipFile(patchFile);
                final Enumeration<? extends TinkerZipEntry> entries = oldApk.entries();
                while (entries.hasMoreElements()) {
                    TinkerZipEntry zipEntry = entries.nextElement();
                    if (zipEntry == null) {
                        throw new TinkerRuntimeException("zipEntry is null when get from oldApk");
                    }
                    String name = zipEntry.getName();
                    if (name.contains("../")) {
                        continue;
                    }
                    if (ShareResPatchInfo.checkFileInPattern(resPatchInfo.patterns, name)) {
                        //won't contain in add set.
                        if (!resPatchInfo.deleteRes.contains(name)
                            && !resPatchInfo.modRes.contains(name)
                            && !resPatchInfo.largeModRes.contains(name)
                            && !name.equals(ShareConstants.RES_MANIFEST)) {
                            TinkerZipUtil.extractTinkerEntry(oldApk, zipEntry, out);
                            totalEntryCount++;
                        }
                    }
                }

                //process manifest

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Validate the installed base APK integrity (e.g., re-verify its signature or CRC) before distributing resource patches.
  2. Rebuild/re-sign the base APK with standard zipalign + apksigner and confirm it opens cleanly with ZipFile before shipping patches.
  3. Reinstall the base APK on affected devices; local corruption cannot be fixed by a patch.
  4. If unfixable on some devices, disable resource patching (TINKER_DISABLE for res) so dex/so patches still apply.
Defensive patterns

Strategy: validation

Validate before calling

try {
    ZipFile apk = new ZipFile(context.getApplicationInfo().sourceDir);
    Enumeration<? extends ZipEntry> es = apk.entries();
    while (es.hasMoreElements()) { es.nextElement(); } // walk to surface corruption early
    apk.close();
} catch (IOException e) {
    // base APK zip is unreadable: skip patching, prompt reinstall
}

Try / catch

try {
    TinkerInstaller.onReceiveUpgradePatch(context, patchPath);
} catch (TinkerRuntimeException e) {
    if (e.getMessage() != null && e.getMessage().contains("zipEntry is null")) {
        disableResPatching(); // recover with dex/so only next time
    }
}

Prevention

When it happens

Trigger: During patchResource with an old APK whose ZIP structure is corrupt (truncated central directory, overlapping entries, or a zip flavor TinkerZipFile iterates abnormally), entries.nextElement() returns null on some iteration.

Common situations: Base APK corrupted on disk or by a repack/signing step; APK served through a CDN or hot-fix channel that truncated it; v2-signing-only zips processed by an older zip reader; extremely large resource tables triggering partial-entry reads.

Related errors


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