Android 14设备解压报错:ZipException与SecurityException问题求助
Android 14解压文件两类异常的修复方案
异常1:java.util.zip.ZipException: Invalid zip entry path: /res/l3MK
问题根源
你的ZipEntry名称以/开头,导致newFile方法里的路径校验失败。Java的File构造函数如果第二个参数是绝对路径(带开头斜杠),会直接忽略父目录参数,生成的文件路径不在你指定的destinationDir范围内,触发了防路径越界的校验逻辑。
修复方法
修改newFile方法,先把ZipEntry名称开头的斜杠去掉,再做路径校验:
public static File newFile(File targetPath, ZipEntry zipEntry) throws IOException { String name = zipEntry.getName(); // 去掉开头的斜杠,避免路径跳出目标目录 if (name.startsWith("/")) { name = name.substring(1); } File f = new File(targetPath, name); String canonicalPath = f.getCanonicalPath(); String targetCanonicalPath = targetPath.getCanonicalPath(); // 更严谨的路径校验,兼容目标目录末尾无分隔符的情况 if (!canonicalPath.startsWith(targetCanonicalPath) || (canonicalPath.length() > targetCanonicalPath.length() && canonicalPath.charAt(targetCanonicalPath.length()) != File.separatorChar)) { throw new ZipException("Illegal name: " + zipEntry.getName()); } return f; }
异常2:java.lang.SecurityException: Writable dex file '/data/data/..../base.apk/classes23.dex' is not allowed
问题根源
Android 14新增了Runtime Execution Protection(运行时执行保护),禁止应用在私有可写目录里写入可执行的DEX文件,防止恶意代码注入。你解压的文件里有.dex后缀的文件,写入时触发了这个安全限制。
修复方案(三选一)
方案1:直接跳过DEX文件
如果不需要这些DEX文件,遍历ZipEntry时直接跳过:while ((nextEntry = zipInputStream.getNextEntry()) != null) { // 跳过所有.dex文件 if (nextEntry.getName().toLowerCase().endsWith(".dex")) { zipInputStream.closeEntry(); continue; } // 原有解压逻辑... }方案2:修改DEX文件扩展名
要是需要保留文件内容,先把扩展名改掉,后续用的时候再改回来:File file3 = newFile(destinationDir, nextEntry); // 给DEX文件加个后缀,避免触发限制 if (nextEntry.getName().toLowerCase().endsWith(".dex")) { file3 = new File(file3.getParentFile(), file3.getName() + ".temp"); } // 原有解压逻辑...方案3:解压到允许的目录
把DEX文件放到应用的不可执行缓存目录,比如在缓存目录下建一个不可执行的子目录:// 获取上下文,创建不可执行的缓存子目录 File safeDir = new File(context.getCacheDir(), "safe_unzip"); safeDir.mkdirs(); // 设置目录权限为不可执行,确保符合Android 14要求 safeDir.setExecutable(false, false); safeDir.setReadable(true, false); safeDir.setWritable(true, false); // 把DEX文件解压到这个目录里
完整修复后的代码
public static boolean unzipFile(File sourceFile, File destinationDir, Context context) { InputStream inputStream = null; try { inputStream = new FileInputStream(sourceFile); } catch (Exception e) { e.printStackTrace(); return false; } try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { ZipEntry nextEntry; byte[] bArr = new byte[2048]; while ((nextEntry = zipInputStream.getNextEntry()) != null) { // 这里用方案1:跳过DEX文件,可自行替换为其他方案 if (nextEntry.getName().toLowerCase().endsWith(".dex")) { zipInputStream.closeEntry(); continue; } File file3 = newFile(destinationDir, nextEntry); File parentFile = nextEntry.isDirectory() ? file3 : file3.getParentFile(); if (parentFile == null) { zipInputStream.closeEntry(); continue; } if (parentFile.isDirectory() || parentFile.mkdirs()) { if (!nextEntry.isDirectory()) { // 使用try-with-resources自动关闭流,避免资源泄漏 try (FileOutputStream fileOutputStream = new FileOutputStream(file3)) { int read; while ((read = zipInputStream.read(bArr)) != -1) { fileOutputStream.write(bArr, 0, read); } } } } zipInputStream.closeEntry(); } return true; } catch (IOException e) { e.printStackTrace(); return false; } } public static File newFile(File targetPath, ZipEntry zipEntry) throws IOException { String name = zipEntry.getName(); // 去除开头斜杠,防止路径越界 if (name.startsWith("/")) { name = name.substring(1); } File f = new File(targetPath, name); String canonicalPath = f.getCanonicalPath(); String targetCanonicalPath = targetPath.getCanonicalPath(); // 严格校验文件路径是否在目标目录范围内 if (!canonicalPath.startsWith(targetCanonicalPath) || (canonicalPath.length() > targetCanonicalPath.length() && canonicalPath.charAt(targetCanonicalPath.length()) != File.separatorChar)) { throw new ZipException("Illegal name: " + zipEntry.getName()); } return f; }
内容的提问来源于stack exchange,提问作者Brainpax Technologies
相关产品推荐
相关产品推荐

