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

求助:获取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()进一步判断,但大部分场景下不需要精确区分。

注意事项

  1. OTG设备:部分OTG需要用户手动在系统设置中挂载,未挂载的设备不会被StorageManager检测到
  2. 分区存储(Android 10+):默认开启后,应用无法直接访问其他应用的私有文件,只能通过MediaStore或SAF(存储访问框架)访问公共文件
  3. 定制ROM兼容性:反射部分在极少数定制ROM可能失效,如果遇到这种情况,可以尝试解析/proc/mounts文件获取挂载点,但这种方式需要读取系统文件,部分设备可能限制访问。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:13:18