求助:获取Android设备已连接所有存储设备的路径列表
获取Android所有已连接存储设备的兼容方案
我完全懂你的困扰——Android的存储系统碎片化问题确实让人头疼,想要统一获取所有挂载的存储设备(包括带Downloads/DCIM的内部存储、SD卡、OTG),官方没有一刀切的标准方法,而且不同版本、不同厂商的ROM还经常搞特殊。我踩过不少坑,整理了一套兼容性不错的方案,覆盖从Android 6.0(API 23)到最新版本的设备,分享给你:
核心思路
从Android 6.0开始,官方逐步引入StorageManager和StorageVolume类来管理存储设备,这是最推荐的方向。但不同API版本的方法差异很大,我们需要做版本兼容处理,同时兼顾旧版本的Environment相关方法,还要处理权限问题。
步骤1:权限准备
首先得确保应用有足够的权限:
- Android 6.0 - 12(API 23-32):需要动态申请
READ_EXTERNAL_STORAGE权限 - Android 13+(API 33):如果只访问媒体文件,申请
READ_MEDIA_IMAGES/READ_MEDIA_VIDEO/READ_MEDIA_AUDIO;如果需要访问非媒体文件(比如Downloads里的文档),则需要MANAGE_EXTERNAL_STORAGE权限(这个权限需要用户手动在设置里开启,Google Play审核严格,仅适合文件管理器类应用)
步骤2:兼容多版本的代码实现
下面是封装好的方法,能返回所有可访问的存储设备信息,包括路径、显示名称、是否为内部存储、是否为可移动存储(SD卡/OTG):
import android.content.Context; import android.os.Build; import android.os.Environment; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; import java.io.File; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; import java.util.List; public class StorageUtils { // 存储设备信息模型 public static class StorageInfo { public String displayName; // 存储显示名称(比如"内部存储"、"SD卡") public String rootPath; // 存储根路径 public boolean isInternal; // 是否为内部存储 public boolean isRemovable;// 是否为可移动存储(SD卡/OTG) } /** * 获取所有已挂载且可访问的存储设备列表 */ public static List<StorageInfo> getAllAccessibleStorages(Context context) { List<StorageInfo> storageList = new ArrayList<>(); StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 11+ 官方API直接支持 List<StorageVolume> volumes = storageManager.getStorageVolumes(); for (StorageVolume volume : volumes) { if (volume.isAccessible()) { // 只保留可访问的存储 StorageInfo info = new StorageInfo(); info.displayName = volume.getDescription(context); File rootDir = volume.getDirectory(); if (rootDir != null) { info.rootPath = rootDir.getAbsolutePath(); } info.isInternal = volume.isPrimary(); info.isRemovable = volume.isRemovable(); storageList.add(info); } } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // Android 7.0-10 用反射获取路径(官方API未暴露) List<StorageVolume> volumes = storageManager.getStorageVolumes(); for (StorageVolume volume : volumes) { try { Method getPathMethod = StorageVolume.class.getMethod("getPath"); String rootPath = (String) getPathMethod.invoke(volume); StorageInfo info = new StorageInfo(); info.displayName = volume.getDescription(context); info.rootPath = rootPath; info.isInternal = volume.isPrimary(); info.isRemovable = volume.isRemovable(); storageList.add(info); } catch (Exception e) { e.printStackTrace(); } } } else { // Android 6.0 用旧API解析 File[] externalFilesDirs = context.getExternalFilesDirs(null); for (File appDir : externalFilesDirs) { if (appDir != null) { // 从应用专属目录解析出存储根路径(比如/storage/emulated/0/Android/data/xxx/files → /storage/emulated/0) String rootPath = appDir.getAbsolutePath().split("/Android/data")[0]; StorageInfo info = new StorageInfo(); info.rootPath = rootPath; info.isInternal = Environment.isExternalStorageEmulated(appDir); info.isRemovable = Environment.isExternalStorageRemovable(appDir); // 设置显示名称 info.displayName = info.isInternal ? "内部存储" : "可移动存储"; storageList.add(info); } } } // 去重处理(部分设备可能重复返回存储项) HashSet<String> pathSet = new HashSet<>(); List<StorageInfo> uniqueList = new ArrayList<>(); for (StorageInfo info : storageList) { if (pathSet.add(info.rootPath)) { uniqueList.add(info); } } return uniqueList; } }
步骤3:区分存储类型与获取公共目录
- 内部存储:
isInternal为true,其根路径下的Downloads、DCIM等公共目录,在API 29+推荐用MediaStore访问,旧版本可以用Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) - SD卡/OTG:
isRemovable为true,两者的区分可以通过displayName(通常OTG会显示类似"USB存储"的名称),或者在Android 12+用StorageVolume.getStorageUuid()进一步判断,但大部分场景下不需要精确区分。
注意事项
- OTG设备:部分OTG需要用户手动在系统设置中挂载,未挂载的设备不会被
StorageManager检测到 - 分区存储(Android 10+):默认开启后,应用无法直接访问其他应用的私有文件,只能通过
MediaStore或SAF(存储访问框架)访问公共文件 - 定制ROM兼容性:反射部分在极少数定制ROM可能失效,如果遇到这种情况,可以尝试解析
/proc/mounts文件获取挂载点,但这种方式需要读取系统文件,部分设备可能限制访问。
内容的提问来源于stack exchange,提问作者Rahulrr2602
相关产品推荐
相关产品推荐

