有没有一种通用的方法来找到外置SD卡的位置?

请不要与外部存储混淆。

Environment.getExternalStorageState()返回内部SD挂载点的路径,例如/mnt/sdcard。但问题是关于外部SD的。我如何获得类似/mnt/sdcard/external_sd的路径(它可能因设备而异)?

我想我将以根据文件系统名称过滤mount命令的输出作为结束。但我不确定这种方式是否足够稳健。


当前回答

你可以尝试使用支持库函数ContextCompat.getExternalFilesDirs():

      final File[] appsDir=ContextCompat.getExternalFilesDirs(getActivity(),null);
      final ArrayList<File> extRootPaths=new ArrayList<>();
      for(final File file : appsDir)
        extRootPaths.add(file.getParentFile().getParentFile().getParentFile().getParentFile());

第一个是主要的外部存储,其余的应该是真正的sd卡路径。

使用多个“.getParentFile()”的原因是要转到另一个文件夹,因为原始路径是

.../Android/data/YOUR_APP_PACKAGE_NAME/files/

编辑:这是一个更全面的方法,我已经创建,以获得sd卡路径:

  /**
   * returns a list of all available sd cards paths, or null if not found.
   *
   * @param includePrimaryExternalStorage set to true if you wish to also include the path of the primary external storage
   */
  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
  public static List<String> getSdCardPaths(final Context context, final boolean includePrimaryExternalStorage)
    {
    final File[] externalCacheDirs=ContextCompat.getExternalCacheDirs(context);
    if(externalCacheDirs==null||externalCacheDirs.length==0)
      return null;
    if(externalCacheDirs.length==1)
      {
      if(externalCacheDirs[0]==null)
        return null;
      final String storageState=EnvironmentCompat.getStorageState(externalCacheDirs[0]);
      if(!Environment.MEDIA_MOUNTED.equals(storageState))
        return null;
      if(!includePrimaryExternalStorage&&VERSION.SDK_INT>=VERSION_CODES.HONEYCOMB&&Environment.isExternalStorageEmulated())
        return null;
      }
    final List<String> result=new ArrayList<>();
    if(includePrimaryExternalStorage||externalCacheDirs.length==1)
      result.add(getRootOfInnerSdCardFolder(externalCacheDirs[0]));
    for(int i=1;i<externalCacheDirs.length;++i)
      {
      final File file=externalCacheDirs[i];
      if(file==null)
        continue;
      final String storageState=EnvironmentCompat.getStorageState(file);
      if(Environment.MEDIA_MOUNTED.equals(storageState))
        result.add(getRootOfInnerSdCardFolder(externalCacheDirs[i]));
      }
    if(result.isEmpty())
      return null;
    return result;
    }

  /** 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||!parentFile.canRead())
        return file.getAbsolutePath();
      file=parentFile;
      }
    }

编辑:这里有更好的解决方案:

https://stackoverflow.com/a/27197248/878126

其他回答

这就是我如何找到外部存储路径。

public static String getExternalStoragePath(Context context){
        File[] f = ContextCompat.getExternalFilesDirs(context, null);
        String internalPath = Environment.getExternalStorageDirectory().getAbsolutePath();
        String excessive = "";
        String externalPath = "";
        for (File file : f) {
            if (file.getAbsolutePath().startsWith(internalPath)) {
                excessive = file.getAbsolutePath().replaceAll(internalPath, "");
            }
        }
        for (File file : f) {
            if (!file.getAbsolutePath().startsWith(excessive)) {
                externalPath = file.getAbsolutePath().replaceAll(excessive, "");
            }
        }
        return  externalPath;
    }

这是我用来找到外部卡的方法。使用mount cmd返回然后解析vfat部分。

String s = "";
try {
Process process = new ProcessBuilder().command("mount")
        .redirectErrorStream(true).start();

process.waitFor();

InputStream is = process.getInputStream();
byte[] buffer = new byte[1024];
while (is.read(buffer) != -1) {
    s = s + new String(buffer);
}
is.close();
} catch (Exception e) {
e.printStackTrace();
}

//用行分隔mount列表
String[] lines = s.split("\n");
for(int i=0; i<lines.length; i++) {
//如果行内有挂载路径且为vfat类型,说明可能是内置或者外置sd的挂载点
if(-1 != lines[i].indexOf(path[0]) && -1 != lines[i].indexOf("vfat")) {
    //再用空格分隔
    String[] blocks = lines[i].split("\\s");
    for(int j=0; j<blocks.length; j++) {
        //判断是否是挂载为vfat类型
        if(-1 != blocks[j].indexOf(path[0])) {
            //Test if it is the external sd card.
        }
    }
}
}

Environment.getExternalStorageState()返回内部SD挂载点的路径,如"/mnt/sdcard"

不,Environment.getExternalStorageDirectory()指的是设备制造商认为是“外部存储”的任何东西。在某些设备上,这是可移动媒体,如SD卡。在某些设备上,这是设备上flash的一部分。这里的“外部存储”指的是“挂载在主机上通过USB大容量存储模式访问的东西”,至少对于Android 1来说是这样。X和2.x。

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

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

如果设备制造商已经选择将外部存储作为板载闪存,并且还具有SD卡,那么您将需要联系该制造商以确定是否可以使用SD卡(不保证)以及使用它的规则,例如使用它的路径。


更新

最近有两件事值得注意:

首先,在Android 4.4+上,你没有对可移动媒体(例如,“外部SD”)的写访问权,除非该媒体上的任何位置可能由getExternalFilesDirs()和getExternalCacheDirs()返回。请参阅Dave Smith对此的出色分析,特别是如果您想了解底层细节的话。

其次,为了避免有人质疑可移动媒体访问是否是Android SDK的一部分,以下是Dianne Hackborn的评估:

...keep in mind: until Android 4.4, the official Android platform has not supported SD cards at all except for two special cases: the old school storage layout where external storage is an SD card (which is still supported by the platform today), and a small feature added to Android 3.0 where it would scan additional SD cards and add them to the media provider and give apps read-only access to their files (which is also still supported in the platform today). Android 4.4 is the first release of the platform that has actually allowed applications to use SD cards for storage. Any access to them prior to that was through private, unsupported APIs. We now have a quite rich API in the platform that allows applications to make use of SD cards in a supported way, in better ways than they have been able to before: they can make free use of their app-specific storage area without requiring any permissions in the app, and can access any other files on the SD card as long as they go through the file picker, again without needing any special permissions.

这个解决方案处理System.getenv(“SECONDARY_STORAGE”)在Marshmallow中没有用的问题。

已测试和正在进行:

Samsung Galaxy Tab 2 (Android 4.1.1 - Stock) Samsung Galaxy Note 8.0 (Android 4.2.2 - Stock) Samsung Galaxy S4 (Android 4.4 - Stock) Samsung Galaxy S4 (Android 5.1.1 - Cyanogenmod) Samsung Galaxy Tab A (Android 6.0.1 - Stock) /** * Returns all available external SD-Card roots in the system. * * @return paths to all available external SD-Card roots in the system. */ public static String[] getStorageDirectories() { String [] storageDirectories; String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { List<String> results = new ArrayList<String>(); File[] externalDirs = applicationContext.getExternalFilesDirs(null); for (File file : externalDirs) { String path = file.getPath().split("/Android")[0]; if((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Environment.isExternalStorageRemovable(file)) || rawSecondaryStoragesStr != null && rawSecondaryStoragesStr.contains(path)){ results.add(path); } } storageDirectories = results.toArray(new String[0]); }else{ final Set<String> rv = new HashSet<String>(); if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) { final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator); Collections.addAll(rv, rawSecondaryStorages); } storageDirectories = rv.toArray(new String[rv.size()]); } return storageDirectories; }

像Richard一样,我也使用/proc/mounts文件来获取可用的存储选项列表

public class StorageUtils {

    private static final String TAG = "StorageUtils";

    public static class StorageInfo {

        public final String path;
        public final boolean internal;
        public final boolean readonly;
        public final int display_number;

        StorageInfo(String path, boolean internal, boolean readonly, int display_number) {
            this.path = path;
            this.internal = internal;
            this.readonly = readonly;
            this.display_number = display_number;
        }

        public String getDisplayName() {
            StringBuilder res = new StringBuilder();
            if (internal) {
                res.append("Internal SD card");
            } else if (display_number > 1) {
                res.append("SD card " + display_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_internal = !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);
        BufferedReader buf_reader = null;
        try {
            HashSet<String> paths = new HashSet<String>();
            buf_reader = new BufferedReader(new FileReader("/proc/mounts"));
            String line;
            int cur_display_number = 1;
            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 (mount_point.equals(def_path)) {
                        paths.add(def_path);
                        list.add(0, new StorageInfo(def_path, def_path_internal, readonly, -1));
                    } else 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, false, readonly, cur_display_number++));
                        }
                    }
                }
            }

            if (!paths.contains(def_path) && def_path_available) {
                list.add(0, new StorageInfo(def_path, def_path_internal, def_path_readonly, -1));
            }

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