You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 16:06:03