Tencent/tinker · error · TinkerRuntimeException

patch %s extract failed (%s).

Error message

patch %s extract failed (%s).

What it means

ResDiffPatchInternal's catch-all: any Throwable thrown while reconstructing the new resources file (merging old APK entries with added/modified/large-modified resources and re-checking resources.arsc MD5) is wrapped in a TinkerRuntimeException tagged with the resource patch type. Checksum mismatches alone do not throw this (they report via onPatchTypeExtractFail and return false); this exception means an unexpected Throwable escaped the reconstruction logic.

Source

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

                IOHelper.closeQuietly(out);
                IOHelper.closeQuietly(oldApk);
                IOHelper.closeQuietly(newApk);

                //delete temp files
                SharePatchFileUtil.deleteDir(tempResFileDirectory);
            }
            boolean result = SharePatchFileUtil.checkResourceArscMd5(resOutput, resPatchInfo.resArscMd5);

            if (!result) {
                ShareTinkerLog.i(TAG, "check final new resource file fail path:%s, entry count:%d, size:%d", resOutput.getAbsolutePath(), totalEntryCount, resOutput.length());
                SharePatchFileUtil.safeDeleteFile(resOutput);
                manager.getPatchReporter().onPatchTypeExtractFail(patchFile, resOutput, ShareConstants.RES_NAME, type);
                return false;
            }

            ShareTinkerLog.i(TAG, "final new resource file:%s, entry count:%d, size:%d", resOutput.getAbsolutePath(), totalEntryCount, resOutput.length());
        } catch (Throwable e) {
            throw new TinkerRuntimeException("patch " + ShareTinkerInternals.getTypeString(type) +  " extract failed (" + e.getMessage() + ").", e);
        }
        return true;
    }

    private static boolean checkAndExtractResourceLargeFile(Context context, String apkPath, File directory, File tempFileDirtory,
                                                            File patchFile, ShareResPatchInfo resPatchInfo, int type, boolean useCustomPatcher) {
        long start = System.currentTimeMillis();
        Tinker manager = Tinker.with(context);
        ZipFile apkFile = null;
        ZipFile patchZipFile = null;
        try {
            //recover resources.arsc first
            apkFile = new ZipFile(apkPath);
            ZipEntry arscEntry = apkFile.getEntry(ShareConstants.RES_ARSC);
            File arscFile = new File(directory, ShareConstants.RES_ARSC);
            if (arscEntry == null) {
                ShareTinkerLog.w(TAG, "resources apk entry is null. path:" + ShareConstants.RES_ARSC);
                manager.getPatchReporter().onPatchTypeExtractFail(patchFile, arscFile, ShareConstants.RES_ARSC, type);

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Free device storage / verify the tinker patch directory is writable before applying.
  2. Confirm the patch was generated from the exact installed base APK (same tinkerId and resource table).
  3. Inspect the wrapped cause via PatchReporter.onPatchException to distinguish I/O from format failures.
  4. Re-download and re-verify the patch file MD5 before applying.
  5. For very large resource changes, ship a full APK upgrade instead of a res patch.
Defensive patterns

Strategy: try-catch

Validate before calling

if (context.getFilesDir().getFreeSpace() < requiredSpaceForResPatch) {
    deferPatchApply("low storage");
}
if (!SharePatchFileUtil.verifyFileMd5(new File(patchPath), serverPayload.patchMd5)) {
    deferPatchApply("bad patch file");
}

Try / catch

try {
    TinkerInstaller.onReceiveUpgradePatch(context, patchPath);
} catch (TinkerRuntimeException e) {
    Throwable cause = e.getCause() != null ? e.getCause() : e;
    if (cause instanceof IOException) { scheduleRetryAfterCleanup(); }
    else { reportPatchFormatFailure(cause); }
}

Prevention

When it happens

Trigger: Applying a resource patch when TinkerZipOutputStream write fails (disk full, resOutput set read-only on API 33+ path), an entry extraction from oldApk/newApk TinkerZipFile throws, or checkResourceArscMd5's file I/O throws. Also reached if the arsc patch application inside this try block raises.

Common situations: Disk exhaustion on low-end devices; patch built against a different base APK so entry lookups misbehave; API 33+ read-only resOutput interplay with the custom zip writer; resources.arsc changes too large for the in-place merge strategy.

Related errors


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