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

Android 7.0:通过Storage Volume/SAF获取Zip包内文件总数

如何通过Storage Access Framework获取Zip文件内的文件总数

好问题!在Android 7.0及以上版本使用Storage Access Framework(SAF)操作SD卡上的Zip文件时,确实会遇到没法直接用ZipFile(path)的尴尬——毕竟SAF只提供Uri而非真实文件路径。针对你不想先遍历一遍统计数量、再重新遍历解压的性能痛点,这里有两个实用的解决方案:

方案一:缓存Zip条目元数据,避免重复读取完整流

虽然需要打开两次输入流,但第一次只读取Zip的目录结构(这部分数据量很小,性能开销可以忽略),缓存关键信息后就能拿到总数,再用新的流进行解压:

// 第一步:缓存ZipEntry的关键信息并统计总数
List<ZipEntryInfo> entryList = new ArrayList<>();
try (InputStream is = getContentResolver().openInputStream(myZipFile.getUri());
     BufferedInputStream bis = new BufferedInputStream(is);
     ZipInputStream zis = new ZipInputStream(bis)) {
    ZipEntry zipEntry;
    while ((zipEntry = zis.getNextEntry()) != null) {
        // 不要直接存储ZipEntry(它和当前流绑定,后续无法复用),缓存关键信息即可
        entryList.add(new ZipEntryInfo(zipEntry.getName(), zipEntry.isDirectory(), zipEntry.getSize()));
    }
} catch (IOException e) {
    e.printStackTrace();
}

int totalFiles = entryList.size();
// 这里用totalFiles初始化进度对话框的最大值

// 第二步:重新打开流进行解压,同时跟踪进度
try (InputStream is = getContentResolver().openInputStream(myZipFile.getUri());
     BufferedInputStream bis = new BufferedInputStream(is);
     ZipInputStream zis = new ZipInputStream(bis)) {
    int currentProgress = 0;
    ZipEntry zipEntry;
    while ((zipEntry = zis.getNextEntry()) != null) {
        // 执行你的解压逻辑:写入SD卡目标目录(记得用SAF的Uri创建输出流)
        // ...
        
        // 更新进度
        currentProgress++;
        updateProgressDialog(currentProgress, totalFiles);
        
        zis.closeEntry();
    }
} catch (IOException e) {
    e.printStackTrace();
}

// 辅助类:用来缓存ZipEntry的关键信息
private static class ZipEntryInfo {
    String name;
    boolean isDirectory;
    long size;

    ZipEntryInfo(String name, boolean isDirectory, long size) {
        this.name = name;
        this.isDirectory = isDirectory;
        this.size = size;
    }
}

方案二:利用文件描述符使用ZipFile(更高效)

如果SAF返回的Uri支持获取文件描述符,我们可以用ZipFile(FileDescriptor)构造方法直接创建ZipFile实例,这样既能快速拿到文件总数,又能一次完成解压:

ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(myZipFile.getUri(), "r");
if (pfd != null) {
    try (ZipFile zipFile = new ZipFile(pfd.getFileDescriptor())) {
        int totalFiles = zipFile.size();
        // 初始化进度对话框

        // 遍历所有条目进行解压
        Enumeration<? extends ZipEntry> entries = zipFile.entries();
        int currentProgress = 0;
        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            // 获取当前条目的输入流
            try (InputStream entryIs = zipFile.getInputStream(entry)) {
                // 写入到SD卡的目标位置:通过SAF获取目标Uri的输出流
                // OutputStream os = getContentResolver().openOutputStream(targetUri);
                // 执行写入逻辑...
            }
            
            // 更新进度
            currentProgress++;
            updateProgressDialog(currentProgress, totalFiles);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            pfd.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

重要注意事项

  • 所有Zip读取、解压操作都必须放在子线程中执行,绝对不能在主线程处理,否则会触发ANR。
  • 写入SD卡目标文件夹时,必须使用SAF提供的Uri创建输出流(getContentResolver().openOutputStream(targetUri)),不能直接使用文件路径。

内容的提问来源于stack exchange,提问作者Neph

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:11:20