Android ZipInputStream:只有 DEFLATED 条目可以有 EXT 描述符

Android ZipInputStream: only DEFLATED entries can have EXT descriptor

在我的 android 设备上,我需要提取一个从内容 uri 中获取的文件(一个 xapk,据我所知这是一个普通的 zip 存档)。 我正在使用这行代码创建 ZipInputStream:

ZipInputStream zis = new ZipInputStream(getContentResolver().openInputStream(zipUri));

然后我尝试读取存档的第一个条目:

ZipEntry entry = zis.getNextEntry()

问题是我遇到了这个异常:

java.util.zip.ZipException: only DEFLATED entries can have EXT descriptor

我 100% 确定存档中没有 0bytes 文件,并且我可以使用设备中的其他实用程序(RAR、解压缩等)提取相同的存档。

如果我使用带有硬编码路径的 ZipFile(因此不涉及内容 uri),我可以毫无问题地提取相同的存档,因此该问题与带有 uri 的 ZipInputStream 有关。另一方面,我不能在这里使用 ZipFile,因为它不支持内容 uris。

尝试使用 commons-compress

<dependency>
   <groupId>org.apache.commons</groupId>
    <artifactId>commons-compress</artifactId>
    <version>1.17</version>
</dependency>

这是一个片段

public void unzip(File zipFile, File destDir) throws IOException, ArchiveException {
       String destDirectory = destDir.getAbsolutePath();

       try (ArchiveInputStream i = new ZipArchiveInputStream(new 
          FileInputStream(zipFile), "UTF-8", false, true)) {
          ArchiveEntry entry = null;
          while ((entry = i.getNextEntry()) != null) {
             if (!i.canReadEntryData(entry)) {
                System.out.println("Can't read entry: " + entry);
                continue;
             }
             String name = destDirectory + File.separator + entry.getName();
             File f = new File(name);
             if (entry.isDirectory()) {
                if (!f.isDirectory() && !f.mkdirs()) {
                   throw new IOException("failed to create directory " + f);
                }
             } else {
                File parent = f.getParentFile();
                if (!parent.isDirectory() && !parent.mkdirs()) {
                   throw new IOException("failed to create directory " + parent);
                }
                try (OutputStream o = Files.newOutputStream(f.toPath())) {
                   IOUtils.copy(i, o);
                }
             }
          }
       }
    }

很遗憾,目前唯一的答案是:

不要像 ZipInputStream 那样以流模式处理 ZIP 文件。似乎所有当前可用的 ZIP 处理组件,如来自 JRE 的 ZipInputStream 和来自 Apache commons-compressZipArchiveInputStream 都无法处理此类 ZIP 文件。

apache commons-compress 帮助页面上对问题的描述非常好:

ZIP archives know a feature called the data descriptor which is a way to store an entry's length after the entry's data. This can only work reliably if the size information can be taken from the central directory or the data itself can signal it is complete, which is true for data that is compressed using the DEFLATED compression algorithm.

ZipFile has access to the central directory and can extract entries using the data descriptor reliably. The same is true for ZipArchiveInputStream as long as the entry is DEFLATED. For STORED entries ZipArchiveInputStream can try to read ahead until it finds the next entry, but this approach is not safe and has to be enabled by a constructor argument explicitly.

https://commons.apache.org/proper/commons-compress/zip.html

解决方案

避免此问题的唯一可能是使用 ZipFile,但是 JRE 的 ZipFile 实现需要真实文件,因此您可能必须将数据保存到临时文件。

或者,如果您改用 Apache commons-compress 中的 ZipFile,并且您已经将 ZIP 文件完全保存在内存中,则可以避免使用 SeekableInMemoryByteChannel 将其保存到临时文件。


编辑:使用 Apache (Kotlin) 的内存 ZipFile 的解决方案:

ByteArrayOutputStream().use { byteArrayOutputStream ->
    inputStream.copyTo(byteArrayOutputStream)
    ZipFile(SeekableInMemoryByteChannel(byteArrayOutputStream.toByteArray())).use {
        for (entry in it.entries) {
            it.getInputStream(entry).copyTo(someOutputStream)
        }
    }
}