android - Android 11 + Kotlin:读取 .zip 文件
问题描述
我有一个用 Kotlin 目标框架 30+ 编写的 Android 应用程序,所以我正在使用新的Android 11 文件访问限制。该应用程序需要能够打开共享存储中的任意 .zip 文件(由用户以交互方式选择),然后使用该 .zip 文件的内容进行处理。
我得到了 .zip 文件的 URI,我明白这是规范的方式:
val activity = this
val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) {
CoroutineScope(Dispatchers.Main).launch {
if(it != null) doStuffWithZip(activity, it)
...
}
}
getContent.launch("application/zip")
我的问题是我使用的Java.util.zip.ZipFile 类只知道如何打开由String或File指定的 .zip 文件,我没有任何简单的方法可以从一个URI。(我猜 ZipFile 对象需要实际的文件而不是某种流,因为它需要能够寻找......)
我目前使用的解决方法是将 Uri 转换为InputStream,将内容复制到私有存储中的临时文件,然后从中创建一个 ZipFile 实例:
private suspend fun <T> withZipFromUri(
context: Context,
uri: Uri, block: suspend (ZipFile) -> T
) : T {
val file = File(context.filesDir, "tempzip.zip")
try {
return withContext(Dispatchers.IO) {
kotlin.runCatching {
context.contentResolver.openInputStream(uri).use { input ->
if (input == null) throw FileNotFoundException("openInputStream failed")
file.outputStream().use { input.copyTo(it) }
}
ZipFile(file, ZipFile.OPEN_READ).use { block.invoke(it) }
}.getOrThrow()
}
} finally {
file.delete()
}
}
然后,我可以这样使用它:
suspend fun doStuffWithZip(context: Context, uri: Uri) {
withZipFromUri(context, uri) { // it: ZipFile
for (entry in it.entries()) {
dbg("entry: ${entry.name}") // or whatever
}
}
}
这可行,并且(在我的特定情况下,有问题的 .zip 文件永远不会超过几 MB)具有合理的性能。
但是,我倾向于将临时文件编程视为最终无能者的最后避难所,因此我无法摆脱我在这里错过了一个技巧的感觉。(诚然,我在Android + Kotlin 的背景下完全无能,但我想学会不要......)
有更好的想法吗?有没有一种更简洁的方法来实现这一点,而不涉及制作文件的额外副本?
解决方案
从外部来源复制(并冒着被遗忘的风险)这不是一个答案,但评论太长了
public class ZipFileUnZipExample {
public static void main(String[] args) {
Path source = Paths.get("/home/mkyong/zip/test.zip");
Path target = Paths.get("/home/mkyong/zip/");
try {
unzipFolder(source, target);
System.out.println("Done");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void unzipFolder(Path source, Path target) throws IOException {
// Put the InputStream obtained from Uri here instead of the FileInputStream perhaps?
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(source.toFile()))) {
// list files in zip
ZipEntry zipEntry = zis.getNextEntry();
while (zipEntry != null) {
boolean isDirectory = false;
// example 1.1
// some zip stored files and folders separately
// e.g data/
// data/folder/
// data/folder/file.txt
if (zipEntry.getName().endsWith(File.separator)) {
isDirectory = true;
}
Path newPath = zipSlipProtect(zipEntry, target);
if (isDirectory) {
Files.createDirectories(newPath);
} else {
// example 1.2
// some zip stored file path only, need create parent directories
// e.g data/folder/file.txt
if (newPath.getParent() != null) {
if (Files.notExists(newPath.getParent())) {
Files.createDirectories(newPath.getParent());
}
}
// copy files, nio
Files.copy(zis, newPath, StandardCopyOption.REPLACE_EXISTING);
// copy files, classic
/*try (FileOutputStream fos = new FileOutputStream(newPath.toFile())) {
byte[] buffer = new byte[1024];
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}*/
}
zipEntry = zis.getNextEntry();
}
zis.closeEntry();
}
}
// protect zip slip attack
public static Path zipSlipProtect(ZipEntry zipEntry, Path targetDir)
throws IOException {
// test zip slip vulnerability
// Path targetDirResolved = targetDir.resolve("../../" + zipEntry.getName());
Path targetDirResolved = targetDir.resolve(zipEntry.getName());
// make sure normalized file still has targetDir as its prefix
// else throws exception
Path normalizePath = targetDirResolved.normalize();
if (!normalizePath.startsWith(targetDir)) {
throw new IOException("Bad zip entry: " + zipEntry.getName());
}
return normalizePath;
}
}
这显然适用于预先存在的文件;但是,由于您已经从其中读取了 InputStream,因此Uri
您可以对其进行调整并试一试。
编辑:似乎它File
也在提取到 s - 您可以将单个 ByteArrays 存储在某个地方,然后决定稍后如何处理它们。但我希望您能大致了解 - 您可以在内存中完成所有这些操作,而无需在两者之间使用磁盘(临时文件或文件)。
但是,您的要求有点模糊和不清楚,所以我不知道您要做什么,只是建议尝试的场所/方法
推荐阅读
- drake - 离散多体植物的直接转录方法?
- javascript - 首次加载后保持动态标题不变
- swift - 使用蒙版时出现奇怪的阴影错误
- django - 在使用自定义用户模型时,在 Django 中将编辑限制为配置文件的所有者时遇到问题
- javascript - 如何在 D3.js v4 中实现多个不同方向的树?
- java - 计算列表中元素的数量并将其附加到每个项目的末尾并保持要返回的列表中的插入顺序?
- java - Java:将 4 个单独的音频字节数组组合成单个 wav 音频文件
- jquery - jquery-raty 没有显示任何星星
- sugarcrm - 为什么每当我们在系统中创建新用户时,vtiger 都会在 user_privilege 中创建新的 .php 文件
- python - 试图返回一个字典,而不是得到一个