You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 22:57:47