Android API19+:如何通过Tree URI获取USB存储卷信息?
嘿,针对你要识别通过Intent.ACTION_OPEN_DOCUMENT_TREE选中的USB存储设备信息的需求,我整理了适配API19及以上的可行方案,分不同Android版本来处理,毕竟不同版本的系统API差异不小:
核心思路
通过Intent.ACTION_OPEN_DOCUMENT_TREE拿到的Tree URI,本质上对应系统的Documents Provider。要获取设备的描述、容量等信息,核心是把这个Tree URI关联到具体的存储卷,再通过系统API提取对应的属性——不同API版本的实现方式有明显区别,得分开处理。
适配方案
API 24+(Android 7.0及以上)
从Android 7.0开始,系统引入了StorageVolume类,能直接获取存储设备的大部分信息,这部分实现相对规整:
- 从Tree URI中提取文档ID,再拆分出存储卷ID(通常是文档ID的前半部分,格式类似
primary:path或xxxx-xxxx:path); - 通过
StorageManager获取所有存储卷列表,遍历匹配对应的存储卷; - 从匹配到的
StorageVolume中直接读取描述、容量等信息。
代码示例:
// 假设你已经通过onActivityResult拿到了选中的treeUri String treeDocId = DocumentsContract.getTreeDocumentId(treeUri); String volumeId = treeDocId.split(":")[0]; StorageManager storageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE); List<StorageVolume> storageVolumes = storageManager.getStorageVolumes(); for (StorageVolume volume : storageVolumes) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 11+直接用getVolumeId匹配 if (volume.getVolumeId().equals(volumeId)) { String deviceDesc = volume.getDescription(this); long totalStorage = volume.getTotalBytes(); long freeStorage = volume.getFreeBytes(); // 在这里处理获取到的设备信息 break; } } else { // Android 7.0-10用getUuid匹配(外部存储通常有UUID) UUID uuid = volume.getUuid(); if (uuid != null && uuid.toString().equals(volumeId)) { String deviceDesc = volume.getDescription(this); // 容量需要通过StatFs获取,因为这几个版本的StorageVolume没有直接的容量方法 String storagePath = volume.getDirectory().getAbsolutePath(); StatFs statFs = new StatFs(storagePath); long totalStorage = (long) statFs.getBlockCountLong() * statFs.getBlockSizeLong(); long freeStorage = (long) statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong(); // 处理信息 break; } } }
API 19-23(Android 4.4-6.0)
这部分系统没有提供StorageVolume类,只能通过解析URI、查询文件系统或USB设备信息来实现:
- 先判断URI对应的是否是外部存储的Documents Provider(通常Authority为
com.android.externalstorage.documents); - 拆分文档ID得到存储卷名称(比如
USB1234或sdcard1),对应/storage/目录下的文件夹; - 通过
StatFs获取该目录的容量信息; - 设备描述可以结合
UsbManager获取连接的USB设备信息,或者直接用存储卷名称作为描述(因为不同厂商的关联逻辑差异大,这是最稳妥的方式)。
代码示例:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { String authority = treeUri.getAuthority(); if ("com.android.externalstorage.documents".equals(authority)) { String docId = DocumentsContract.getTreeDocumentId(treeUri); String[] parts = docId.split(":"); String volumeName = parts[0]; // 构建存储路径 String storagePath = "/storage/" + volumeName; File storageDir = new File(storagePath); if (storageDir.exists()) { // 获取容量信息 StatFs statFs = new StatFs(storagePath); long totalStorage; long freeStorage; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { totalStorage = (long) statFs.getBlockCountLong() * statFs.getBlockSizeLong(); freeStorage = (long) statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong(); } else { // API19用int类型的方法,注意可能溢出,建议强制转long totalStorage = (long) statFs.getBlockCount() * statFs.getBlockSize(); freeStorage = (long) statFs.getAvailableBlocks() * statFs.getBlockSize(); } // 获取设备描述 UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); HashMap<String, UsbDevice> usbDevices = usbManager.getDeviceList(); String deviceDesc = "USB存储设备(" + volumeName + ")"; // 如果需要更精准的设备名称,可以遍历USB设备列表尝试关联,不过不同厂商实现差异大,按需处理 for (UsbDevice device : usbDevices.values()) { // 这里可以根据设备的vendorId、productId等信息匹配,逻辑需自行调整 // deviceDesc = device.getDeviceName(); } // 处理获取到的信息 } } }
关键注意事项
- 权限问题:API19-28需要申请
READ_EXTERNAL_STORAGE权限,API23+还需要动态申请;API29+不需要该权限,但建议在Manifest中声明以兼容低版本; - 厂商适配:不同品牌的Documents Provider可能有特殊的文档ID格式,测试时要覆盖主流厂商的设备;
- 容量计算溢出:API19的
StatFs方法返回int类型,计算时要强制转为long避免溢出; - USB设备关联:API19-23中,存储卷名称和USB设备的关联逻辑没有统一标准,若不需要精准名称,用存储卷名称作为描述是最稳妥的方案。
内容的提问来源于stack exchange,提问作者Nuno V.
相关产品推荐
相关产品推荐

