【发布时间】:2021-10-08 15:18:47
【问题描述】:
我有一个使用 Kotlin 30+ 目标框架编写的 Android 应用,所以我正在使用新的 Android 11 file access restrictions。应用程序需要能够打开共享存储中的任意 .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 class 只知道如何打开由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 的环境中完全不称职,但我想学会不......)
有更好的想法吗?有没有更简洁的方法来实现这一点,而不涉及制作文件的额外副本?
【问题讨论】:
-
简单的
ZipInputStream怎么样? -
基本上,将
Uri的内容读入InputStream,将其转换为ZipInputStream,提取(或其他)并避开临时文件。 -
谢谢,@Shark。试了一下,效果很好。完美!
-
如何将 Uri 转换为文件? stackoverflow.com/a/8370299/1542667
-
Yuri:在一般情况下,作为 GetContent() 合同的结果返回的 URI 可能有也可能没有“文件”方案。如果不是,则无法将其表示为文件。 (事实上,在快速测试中,在我的示例中,我得到了一个 content:// URI,并且 tofile() 抛出。)我担心任何规范的、面向未来的解决方案都需要与流一起使用。或者,我想,通过 DocumentProvider 的东西......
标签: android kotlin zipfile android-11