Android 11+Kotlin 如何通过Uri读取共享存储zip文件避免临时拷贝
解决方案
针对你的场景,有两类无需全量拷贝临时文件的实现方案,可根据你的业务逻辑选择:
方案1:顺序遍历场景(零额外拷贝,无第三方依赖)
如果你的业务逻辑仅需要按顺序遍历所有ZIP条目、不需要随机跳转读取指定条目,直接用ZipInputStream即可满足需求,完全不需要中转临时文件:
代码实现
private suspend fun <T> withZipInputStreamFromUri( context: Context, uri: Uri, block: suspend (ZipInputStream) -> T ): T = withContext(Dispatchers.IO) { runCatching { context.contentResolver.openInputStream(uri)?.use { input -> ZipInputStream(input).use { zipStream -> block.invoke(zipStream) } } ?: throw FileNotFoundException("无法打开目标Uri的输入流") }.getOrThrow() }
调用示例
suspend fun doStuffWithZip(context: Context, uri: Uri) { withZipInputStreamFromUri(context, uri) { zipStream -> var entry: ZipEntry? = zipStream.nextEntry while (entry != null) { dbg("entry: ${entry.name}") // 处理当前条目内容,比如读取字节、转存等 // zipStream.read(...) zipStream.closeEntry() entry = zipStream.nextEntry } } }
限制说明
- 仅支持顺序读取条目,无法通过文件名直接定位指定条目
- 每个条目处理完成后必须调用
closeEntry才能读取下一个条目
方案2:需要随机访问指定条目场景(仅需少量依赖,无全量拷贝)
如果你的业务逻辑需要随机读取指定文件名的ZIP条目,可以引入Apache Commons Compress库,它的ZipFile实现支持直接从RandomAccessFile创建,配合SAF返回的Uri拿到的文件描述符即可实现零拷贝访问:
第一步:添加依赖
在模块级build.gradle.kts中加入依赖:
implementation("org.apache.commons:commons-compress:1.23.0")
第二步:代码实现
private suspend fun <T> withRandomAccessZipFromUri( context: Context, uri: Uri, block: suspend (org.apache.commons.compress.archivers.zip.ZipFile) -> T ): T = withContext(Dispatchers.IO) { runCatching { context.contentResolver.openAssetFileDescriptor(uri, "r")?.use { afd -> afd.parcelFileDescriptor.use { pfd -> RandomAccessFile(pfd.fileDescriptor, "r").use { raf -> org.apache.commons.compress.archivers.zip.ZipFile(raf).use { zipFile -> block.invoke(zipFile) } } } } ?: throw FileNotFoundException("无法打开目标Uri的文件描述符") }.getOrThrow() }
调用示例
suspend fun doStuffWithZip(context: Context, uri: Uri) { withRandomAccessZipFromUri(context, uri) { zipFile -> // 支持直接通过文件名获取条目,无需遍历 val targetEntry = zipFile.getEntry("target_file.txt") if (targetEntry != null) { zipFile.getInputStream(targetEntry).use { input -> // 处理指定条目内容 } } // 也支持遍历所有条目,和原生ZipFile用法完全一致 val entries = zipFile.entries while (entries.hasMoreElements()) { val entry = entries.nextElement() dbg("entry: ${entry.name}") } } }
说明
- 该方案和你原本使用原生
ZipFile的逻辑几乎完全兼容,仅需要替换导入的ZipFile包名即可 - 不会产生全量文件拷贝,仅加载需要访问的条目内容,性能远优于临时文件中转方案
内容的提问来源于stack exchange,提问作者Douglas Henke
相关产品推荐
相关产品推荐

