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
- Free device storage / verify the tinker patch directory is writable before applying.
- Confirm the patch was generated from the exact installed base APK (same tinkerId and resource table).
- Inspect the wrapped cause via PatchReporter.onPatchException to distinguish I/O from format failures.
- Re-download and re-verify the patch file MD5 before applying.
- 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
- Verify patch MD5 and free storage before every apply.
- Ensure the patch was built from the same base APK (tinkerId check) before distributing.
- Clean the tinker temp directory between failed retries so partial outputs do not accumulate.
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
- patch ${type} extract failed (${message}).
- zipEntry is null when get from oldApk
- No entries
- can't recognize zip dex format file:%s
- Rest bytes insufficient, expect to read {} bytes but only {}
AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14).
Data as JSON: /api/errors/06c2426bcd1b3afa.
Report an issue: GitHub.