{"record":{"id":"460d0b24b6f1b635","repo":"Tencent/tinker","slug":"patch-s-extract-failed-s","errorCode":null,"errorMessage":"patch %s extract failed (%s).","messagePattern":"patch (.+?) extract failed \\((.+?)\\)\\.","errorType":"exception","errorClass":"TinkerRuntimeException","httpStatus":null,"severity":"error","filePath":"tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/DexDiffPatchInternal.java","lineNumber":626,"sourceCode":"\n                    patchDexFile(apk, patch, rawApkFileEntry, patchFileEntry, info, extractedFile);\n\n                    if (!SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) {\n                        ShareTinkerLog.w(TAG, \"Failed to recover dex file when verify patched dex: \" + extractedFile.getPath());\n                        manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);\n                        SharePatchFileUtil.safeDeleteFile(extractedFile);\n                        return false;\n                    }\n\n                    ShareTinkerLog.w(TAG, \"success recover dex file: %s, size: %d, use time: %d\",\n                        extractedFile.getPath(), extractedFile.length(), (System.currentTimeMillis() - start));\n                }\n            }\n            if (!mergeClassNDexFiles(context, patchFile, dir)) {\n                return false;\n            }\n        } catch (Throwable e) {\n            throw new TinkerRuntimeException(\"patch \" + ShareTinkerInternals.getTypeString(type) + \" extract failed (\" + e.getMessage() + \").\", e);\n        } finally {\n            SharePatchFileUtil.closeZip(apk);\n            SharePatchFileUtil.closeZip(patch);\n        }\n        return true;\n    }\n\n    /**\n     * repack dex to jar\n     *\n     * @param zipFile\n     * @param entryFile\n     * @param extractTo\n     * @param targetMd5\n     * @return boolean\n     * @throws IOException\n     */\n    private static boolean extractDexToJar(ZipFile zipFile, ZipEntry entryFile, File extractTo, String targetMd5) throws IOException {","sourceCodeStart":608,"sourceCodeEnd":644,"githubUrl":"https://github.com/Tencent/tinker/blob/1b7ea02c239840f563ea64fb5bd286eb98d4011e/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/DexDiffPatchInternal.java#L608-L644","documentation":"Thrown by DexDiffPatchInternal when any Throwable escapes the dex recovery loop (extracting, repacking, bsdiff/dex-patch applying, and class-N dex merging) during patch application. The exception message embeds the patch type ('dex') and the original throwable's message. It is a catch-all wrapper: the real failure is the wrapped cause.","triggerScenarios":"Applying a dex diff patch when: the old dex stream in the base APK is corrupted or not the expected format for DexPatchApplier, the patch stream is malformed, a ZipException occurs reading dex-in-jar entries, an OutOfMemoryError occurs on huge dex files, or mergeClassNDexFiles throws while combining classesN.dex files.","commonSituations":"Patch built against a different base APK than the one installed (tinkerId mismatch); patch generated by an incompatible tinker version; multidex APKs where classes2.dex/N layout changed between build and apply; proguard/r8 mapping drift changing dex contents; low-memory devices applying large multi-dex patches.","solutions":["Ensure the tinkerId of the installed base APK exactly matches the one used to build the patch (check ShareTinkerInternals/getManifestMeta or your build config).","Verify the patch file MD5 before applying to rule out a truncated or tampered download.","Match tinker gradle plugin and lib versions between patch build and app runtime.","Inspect the wrapped cause in the TinkerRuntimeException (and your PatchReporter.onPatchException callback) to identify whether it is dex format, zip, or memory related.","For large multi-dex apps, apply patches while the app is idle and memory pressure is low; consider tinker's dex split config."],"exampleFix":"// before\nTinkerInstaller.onReceiveUpgradePatch(context, patchFile);\n\n// after\ntry {\n    TinkerInstaller.onReceiveUpgradePatch(context, patchFile);\n} catch (TinkerRuntimeException e) {\n    Throwable cause = e.getCause() != null ? e.getCause() : e;\n    reportToServer(cause); // keep the wrapped cause, not just the message\n}","handlingStrategy":"try-catch","validationCode":"// Verify base APK tinkerId matches the patch's before applying\nString currentId = ShareTinkerInternals.getTinkerId(context);\nif (currentId == null || !currentId.equals(serverPayload.baseTinkerId)) {\n    refusePatch(\"tinkerId mismatch\");\n}\nif (!SharePatchFileUtil.verifyFileMd5(new File(patchPath), serverPayload.patchMd5)) {\n    refusePatch(\"patch md5 mismatch\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    TinkerInstaller.onReceiveUpgradePatch(context, patchPath);\n} catch (Throwable t) {\n    Throwable cause = (t instanceof TinkerRuntimeException && t.getCause() != null) ? t.getCause() : t;\n    patchReporter.onPatchException(cause);\n}","preventionTips":["Pin tinkerId per release and verify it server-side before distributing a patch.","Keep tinker gradle plugin and tinker-android-lib versions in lockstep.","Report the wrapped cause (getCause), not the wrapper message, when triaging dex failures."],"tags":["tinker","android","hotpatch","dex","bsdiff","multidex"],"backgroundTag":null,"analyzedSha":"1b7ea02c239840f563ea64fb5bd286eb98d4011e","analyzedAt":"2026-08-14T15:16:52.110Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}