在我的 Android 设备上,我需要提取一个从内容 uri 获取的文件(一个 xapk,据我所知,这是一个普通的 zip 存档)。 我正在使用这行代码创建 ZipInputStream:
ZipInputStream zis = new ZipInputStream(getContentResolver().openInputStream(zipUri));
然后我尝试使用以下命令读取存档的第一个条目:
ZipEntry entry = zis.getNextEntry()
问题是我遇到了这个异常:
java.util.zip.ZipException:只有 DEFLATED 条目可以有 EXT 描述符
我 100% 确定存档中没有 0 字节文件,并且我可以使用设备中的其他实用程序(RAR、解压缩等)提取相同的存档。
如果我使用带有硬编码路径的 ZipFile(因此不涉及内容 uri),我可以毫无问题地提取相同的存档,因此问题与带有 uri 的 ZipInputStream 有关。另一方面,我不能在这里使用 ZipFile,因为它不支持内容 uri。
不幸的是,目前唯一的答案是:
不要像
ZipInputStream
那样以流模式处理ZIP文件。似乎所有当前可用的 ZIP 处理组件(例如 JRE 中的 ZipInputStream
和 Apache commons-compress中的
ZipArchiveInputStream
)都无法处理此类 ZIP 文件。
apache commons-compress 帮助页面上有一个很好的问题描述:
ZIP 档案知道一个称为数据描述符的功能,这是一种方法 在条目数据之后存储条目的长度。这只能工作 如果可以从中央获取尺寸信息,则可靠 目录或数据本身可以表明它已完成,这是事实 对于使用 DEFLATED 压缩算法压缩的数据。
ZipFile 可以访问中央目录并可以提取条目 可靠地使用数据描述符。对于 ZipArchiveInputStream,只要条目是 DEFLATED。对于存储的 ZipArchiveInputStream 可以尝试提前读取条目,直到找到 下一个条目,但这种方法并不安全,必须由 显式构造函数参数。
https://commons.apache.org/proper/commons-compress/zip.html
避免此问题的唯一可能是使用
ZipFile
,但是 JRE 的 ZipFile
实现需要真实文件,因此您可能必须将数据保存到临时文件中。
或者,如果您使用 Apache commons-compress 中的
ZipFile
并且您已经将 ZIP 文件完全保存在内存中,则可以使用 ZipFile.Builder().setByteArray(byteArray).get()
来避免将其保存到临时文件中。
编辑:使用Apache(Kotlin)内存中ZipFile的解决方案:
ByteArrayOutputStream().use { byteArrayOutputStream ->
inputStream.copyTo(byteArrayOutputStream)
ZipFile.Buider().setByteArray(byteArrayOutputStream.toByteArray()).get().use {
for (entry in it.entries) {
it.getInputStream(entry).copyTo(someOutputStream)
}
}
}
尝试使用 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);
}
}
}
}
}