如何获取安卓设备已挂载的外部存储列表

24

我们正在为安卓电视盒开发应用程序。如何获取已挂载的外部存储设备列表,例如USB闪存驱动器、SD卡和SATA硬盘


我知道这个话题很久远了,但这可能会有所帮助。你应该使用这种方法。System.getenv();请参阅项目Environment3以访问连接到您设备的所有存储。https://github.com/omidfaraji/Environment3 - Omid Faraji
6个回答

67

我使用 /proc/mounts 文件获取可用存储选项列表。

public class StorageUtils {

    private static final String TAG = "StorageUtils";

    public static class StorageInfo {

        public final String path;
        public final boolean readonly;
        public final boolean removable;     
        public final int number;

        StorageInfo(String path, boolean readonly, boolean removable, int number) {
            this.path = path;
            this.readonly = readonly;
            this.removable = removable;         
            this.number = number;
        }

        public String getDisplayName() {
            StringBuilder res = new StringBuilder();
            if (!removable) {
                res.append("Internal SD card");
            } else if (number > 1) {
                res.append("SD card " + number);
            } else {
                res.append("SD card");
            }
            if (readonly) {
                res.append(" (Read only)");
            }
            return res.toString();
        }
    }

    public static List<StorageInfo> getStorageList() {

        List<StorageInfo> list = new ArrayList<StorageInfo>();
        String def_path = Environment.getExternalStorageDirectory().getPath();
        boolean def_path_removable = Environment.isExternalStorageRemovable();
        String def_path_state = Environment.getExternalStorageState();
        boolean def_path_available = def_path_state.equals(Environment.MEDIA_MOUNTED)
                                    || def_path_state.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
        boolean def_path_readonly = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);

        HashSet<String> paths = new HashSet<String>();
        int cur_removable_number = 1;

        if (def_path_available) {
            paths.add(def_path);
            list.add(0, new StorageInfo(def_path, def_path_readonly, def_path_removable, def_path_removable ? cur_removable_number++ : -1));
        }

        BufferedReader buf_reader = null;
        try {
            buf_reader = new BufferedReader(new FileReader("/proc/mounts"));
            String line;
            Log.d(TAG, "/proc/mounts");
            while ((line = buf_reader.readLine()) != null) {
                Log.d(TAG, line);
                if (line.contains("vfat") || line.contains("/mnt")) {
                    StringTokenizer tokens = new StringTokenizer(line, " ");
                    String unused = tokens.nextToken(); //device
                    String mount_point = tokens.nextToken(); //mount point
                    if (paths.contains(mount_point)) {
                        continue;
                    }
                    unused = tokens.nextToken(); //file system
                    List<String> flags = Arrays.asList(tokens.nextToken().split(",")); //flags
                    boolean readonly = flags.contains("ro");

                    if (line.contains("/dev/block/vold")) {
                        if (!line.contains("/mnt/secure")
                            && !line.contains("/mnt/asec")
                            && !line.contains("/mnt/obb")
                            && !line.contains("/dev/mapper")
                            && !line.contains("tmpfs")) {
                            paths.add(mount_point);
                            list.add(new StorageInfo(mount_point, readonly, true, cur_removable_number++));
                        }
                    }
                }
            }

        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (buf_reader != null) {
                try {
                    buf_reader.close();
                } catch (IOException ex) {}
            }
        }
        return list;
    }
}

6
实际上,这是最好的答案,精确地做到了提问者要求的内容。 - WonderCsabo
1
这不适用于使用exfat文件系统的大容量SD卡,请参考:https://dev59.com/zGgu5IYBdhLWcg3wfHI_ - joecks
@WonderCsabo,有更短的方法。请看我的回答。 - android developer
谢谢,它运行得很好。但是以防万一“proc/mounts”文件不存在,我可以通过这个脚本获取文件(如果此文件不存在,则使用它作为备用)。我们可以像这个答案https://dev59.com/4XHYa4cB1Zd3GeqPMH4L#17085125中所述获取相同的文件。现在只需添加一个修改,即分区以“/storage/”和“/dev/block/”开头,可能是“/dev/fuse”。 - Diljeet
嗯,我偷了这段代码…… 我在项目中使用这段代码还可以吗? - BeChris 100
显示剩余2条评论

13

要获取所有可用的外部存储文件夹(包括SD卡),您可以使用以下代码:

File[] externalStorageFiles=ContextCompat.getExternalFilesDirs(this,null);

如果你想要前往每个外部存储的“根目录”(即真正的挂载文件夹路径),你可以使用我写的这个函数:

  /** Given any file/folder inside an sd card, this will return the path of the sd card */
  private static String getRootOfInnerSdCardFolder(File file)
    {
    if(file==null)
      return null;
    final long totalSpace=file.getTotalSpace();
    while(true)
      {
      final File parentFile=file.getParentFile();
      if(parentFile==null||parentFile.getTotalSpace()!=totalSpace)
        return file.getAbsolutePath();
      file=parentFile;
      }
    }

这将需要支持库4的支持(使用ContextCompat),getTotalSpace()需要最低API Level 9,不适用于API Level 8。 - A.B.
@A.B. 好的。现在使用API 9及以上版本已经相当不错了。 - android developer
这也可能返回私有文件系统文件夹,这些文件夹对应用程序不可读或不可写。 - david.schreiber
@androiddeveloper 是的,我测试的第一台设备失败了,并且生成了至少一个无效路径(带插入SD卡的SM-T580)。 - david.schreiber
@androiddeveloper 无法告诉您确切的路径,因为我一旦注意到问题就立即转移了,留下了我的评论。我不仅授予了正确的权限,还手动检查了adb的路径有效性。现在我正在使用类似于pedja提出的方法,通过构造可能的路径并测试它们的读写访问来访问所有可能的位置(应用程序私有和公共)。 - david.schreiber
显示剩余6条评论

8

Environment.getExternalStorageState() 返回内部SD卡挂载点的路径,例如“/mnt/sdcard”。

不,Environment.getExternalStorageDirectory() 指的是设备制造商认为的“外部存储”。在某些设备上,这是可移动介质,如SD卡。在一些设备上,这是设备闪存的一部分。在这里,“外部存储”指的是“通过USB大容量存储模式访问的内容,当它们被安装在主机上时”,至少对于Android 1.x和2.x。

但问题是关于外部SD卡的。如何获得类似“/mnt/sdcard/external_sd”的路径(它可能因设备而异)?

除了上述的外部存储,Android没有“外部SD卡”的概念。

如果设备制造商选择将外部存储设置为内置闪存,并且又有SD卡,则需要联系该制造商,以确定是否可以使用SD卡(不能保证),以及使用它的规则,例如要使用哪个路径。

试试这个:

manifest.xml:

<receiver android:name=".MyReceiver">
    <intent-filter>
         <action android:name="android.intent.action.ums_connected" />
     </intent-filter>
  </receiver>

我的接收器:

public class MyReceiver extends BroadcastReceiver{
if (intent.getAction().equalsIgnoreCase(
        "android.intent.action.UMS_CONNECTED")) {...}
}

嗨Shishir...谢谢... 但是这个设备有Sdcard插槽和两个USB主机端口。因此,最终用户可以使用任何USB大容量存储器。在这种情况下,我必须提供选项。现在,Sdcard与“Environment.getExternalStorageDirectory()”一起运行得很好。 - Android_Qmax
Manifest.xml: <receiver android:name=".MyReceiver"> <intent-filter> <action android:name="android.intent.action.ums_connected" /> </intent-filter> </receiver> - Shishir Shetty
你好,我该如何获取类似于 /mnt/sda1 这样的路径?因为我正在尝试从代码中获取挂载闪存的路径,但是 Environment.getExternalStorageDirectory() 给出的是 /mnt/sdcard。谢谢。 - Darko Rodic
@DarkoRodic 请看我的回答。 - android developer

3

另一个选择。

首先获取所有外部文件目录。

File[] externalStorageFiles=ContextCompat.getExternalFilesDirs(this,null);

然后对每个文件夹调用此函数。
private static String getRootOfExternalStorage(File file)
{
    if (file == null)
        return null;
    String path = file.getAbsolutePath();
    return path.replaceAll("/Android/data/" + getPackageName() + "/files", "");
}

1
getExternalFilesDirs 不会获取 USB 存储路径。 - Tom

1

非常感谢您,Shiraz。默认情况下,它只显示SD卡,但在某些情况下可能是USB闪存驱动器或SATA硬盘。为此,我该怎么做? - Android_Qmax
再次感谢您的努力...但它是API Level 12(Android 3.0)。但我正在使用2.2和2.3的Android。在2.2或2.3的Android中是否可能? - Android_Qmax
2
对我来说,getExternalStorageDirectory() 不好用,因为它返回的是 sdcard0 - 这是内部存储。我使用的是三星 Galaxy Note 2。 - Peter

-3

我知道这个话题已经有些年头了,但这可能会有所帮助。你应该使用这种方法。

System.getenv();

请查看项目Environment3,以访问连接到您设备的所有存储。

https://github.com/omidfaraji/Environment3


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接