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
相关产品推荐
相关产品推荐

