找到可移动SD卡的位置

bor*_*str 199 android external sd-card

是否有通用的方法来查找外部SD卡的位置?

请不要与外部存储混淆.Environment.getExternalStorageState()返回内部SD挂载点的路径,如"/ mnt/sdcard".但问题是关于外部SD.如何获得像"/ mnt/sdcard/external_sd"这样的路径(它可能因设备而异)?

我想我将以mount文件系统名称过滤命令结束.但我不确定这种方式是否足够强大.

Com*_*are 158

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

不,Environment.getExternalStorageDirectory()指设备制造商认为是"外部存储"的任何内容.在某些设备上,这是可移动媒体,如SD卡.在某些设备上,这是设备上闪存的一部分.这里,"外部存储"意味着"安装在主机上时通过USB大容量存储模式可访问的东西",至少对于Android 1.x和2.x.

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

除了外部存储之外,Android没有"外部SD"的概念,如上所述.

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


UPDATE

最近有两点需要注意:

首先,在Android 4.4+上,您没有可移动媒体的写入权限(例如,"外部SD"),除了该媒体上可能由getExternalFilesDirs()和返回的任何位置getExternalCacheDirs().请参阅Dave Smith对此的出色分析,特别是如果您想要低级细节.

其次,任何人都不知道可移动媒体访问是否是Android SDK的一部分,这是Dianne Hackborn的评估:

......记住:直到Android 4.4系统,官方Android平台不支持SD卡的一切,除了两种特殊情况:老同学存储布局,其中外部存储SD卡(这仍然是由平台支持的今天) ,以及添加到Android 3.0的一个小功能,它将扫描额外的SD卡并将其添加到媒体提供商,并为应用程序提供对其文件的只读访问权限(目前平台仍然支持).

Android 4.4是该平台的第一个版本,它实际上允许应用程序使用SD卡进行存储.在此之前对它们的任何访问都是通过私有的,不受支持的API.我们现在在平台上拥有一个相当丰富的API,允许应用程序以受支持的方式使用SD卡,其方式比以前更好:他们可以免费使用他们的应用专用存储区域而无需任何应用程序中的权限,只要它们通过文件选择器,就可以访问SD卡上的任何其他文件,而无需任何特殊权限.

  • 所以你的答案基本上是"联系制造商".没有用. (271认同)
  • @CommonsWare:尽管如此,当有解决方案在许多设备上工作时,仍然不能准确联系制造商,此外,SDK本身在所有设备上都不一致,因此无法保证.即使这些解决方案不适用于所有设备,它们也可以在足够多的设备上工作,市场上的许多Android应用程序都依赖这些技术来检测外部SD卡路径.我认为将所有这些开发人员称为傻瓜有点苛刻和不成熟 - 客户肯定不是最终的判断吗? (8认同)
  • 答案的最后一部分不太准确 - 确实可以通过遵循下面的答案(扫描/ proc/mounts,/ system/etc/vold.fstab等等)来检测SD卡路径. (6认同)
  • @CommonsWare这很公平,随着时间的推移.我绝对同意你的观点,开发人员不能认为这将始终在任何地方都能正常工作,而且任何此类代码都无法保证能够在所有设备或所有Android版本上运行.希望它能在SDK中修复!与此同时,仍然有许多选项可以在许多设备上运行并且可以改善最终用户体验,并且在80%的成功率和0%的成功率之间做出选择,我将采取80%的选择. (5认同)
  • 这个问题变得越来越成为一个问题,因为HC和ICS设备出现了"ExternalStorageDirectory"以及其他所有类似的内部物理存储.最重要的是,大多数用户完全不知道如何找到他们的SD卡在文件系统中的位置. (4认同)
  • @Kevin:只有傻瓜才会对SDK外部设备的行为做出假设.例如,如果您引用的其他技术可以在具有辅助帐户的Android 4.2+平板电脑上可靠地运行,我会感到惊讶.可能有一些设备缺少`mount`命令,更不用说`PATH`中的一个,正如一个答案所假设的那样.对其他答案(或有时候答案本身)的评论表明该解决方案不可靠,并且不适用于所有设备. (2认同)
  • @Kevin:"我认为把所有这些开发人员称为傻瓜有点苛刻而且为时过早" - 你会对我的警告开发人员反对使用未记录的`content://`值,并通过Gmail锁定进行验证.或者我的建议是不要尝试在设备上访问LogCat的后门方式,在4.2锁定时验证.等等.多个外部存储位置是SDK中不可否认的差距,*希望*将得到修复.与此同时,我将继续建议不要故意编写不可靠的应用程序.当然,欢迎你按照自己的意愿去做. (2认同)

Ric*_*ard 64

我根据这里找到的一些答案提出了以下解决方案.

码:

public class ExternalStorage {

    public static final String SD_CARD = "sdCard";
    public static final String EXTERNAL_SD_CARD = "externalSdCard";

    /**
     * @return True if the external storage is available. False otherwise.
     */
    public static boolean isAvailable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        return false;
    }

    public static String getSdCardPath() {
        return Environment.getExternalStorageDirectory().getPath() + "/";
    }

    /**
     * @return True if the external storage is writable. False otherwise.
     */
    public static boolean isWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;

    }

    /**
     * @return A map of all storage locations available
     */
    public static Map<String, File> getAllStorageLocations() {
        Map<String, File> map = new HashMap<String, File>(10);

        List<String> mMounts = new ArrayList<String>(10);
        List<String> mVold = new ArrayList<String>(10);
        mMounts.add("/mnt/sdcard");
        mVold.add("/mnt/sdcard");

        try {
            File mountFile = new File("/proc/mounts");
            if(mountFile.exists()){
                Scanner scanner = new Scanner(mountFile);
                while (scanner.hasNext()) {
                    String line = scanner.nextLine();
                    if (line.startsWith("/dev/block/vold/")) {
                        String[] lineElements = line.split(" ");
                        String element = lineElements[1];

                        // don't add the default mount path
                        // it's already in the list.
                        if (!element.equals("/mnt/sdcard"))
                            mMounts.add(element);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            File voldFile = new File("/system/etc/vold.fstab");
            if(voldFile.exists()){
                Scanner scanner = new Scanner(voldFile);
                while (scanner.hasNext()) {
                    String line = scanner.nextLine();
                    if (line.startsWith("dev_mount")) {
                        String[] lineElements = line.split(" ");
                        String element = lineElements[2];

                        if (element.contains(":"))
                            element = element.substring(0, element.indexOf(":"));
                        if (!element.equals("/mnt/sdcard"))
                            mVold.add(element);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


        for (int i = 0; i < mMounts.size(); i++) {
            String mount = mMounts.get(i);
            if (!mVold.contains(mount))
                mMounts.remove(i--);
        }
        mVold.clear();

        List<String> mountHash = new ArrayList<String>(10);

        for(String mount : mMounts){
            File root = new File(mount);
            if (root.exists() && root.isDirectory() && root.canWrite()) {
                File[] list = root.listFiles();
                String hash = "[";
                if(list!=null){
                    for(File f : list){
                        hash += f.getName().hashCode()+":"+f.length()+", ";
                    }
                }
                hash += "]";
                if(!mountHash.contains(hash)){
                    String key = SD_CARD + "_" + map.size();
                    if (map.size() == 0) {
                        key = SD_CARD;
                    } else if (map.size() == 1) {
                        key = EXTERNAL_SD_CARD;
                    }
                    mountHash.add(hash);
                    map.put(key, root);
                }
            }
        }

        mMounts.clear();

        if(map.isEmpty()){
                 map.put(SD_CARD, Environment.getExternalStorageDirectory());
        }
        return map;
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

Map<String, File> externalLocations = ExternalStorage.getAllStorageLocations();
File sdCard = externalLocations.get(ExternalStorage.SD_CARD);
File externalSdCard = externalLocations.get(ExternalStorage.EXTERNAL_SD_CARD);
Run Code Online (Sandbox Code Playgroud)

  • 在没有2张SD卡的设备上,此操作失败.假设第1发现是内部的,第2发现是外部的... (10认同)
  • 在android 4.3+中无法访问/system/etc/vold.fstab (4认同)
  • 嗨,理查德(Richard)-信不信由你,我不得不问:您实际上是在尝试写出并以这种方式读回到文件中,而不仅仅是获取目录吗?还记得我们以前的“ / sdcard0”问题吗?我尝试了这段代码,并在S3上“失败” *,当我尝试回读它确实写入的文件时。...这很奇怪...而且很痛苦:)) (2认同)

Bar*_*ron 37

我有一个应用程序使用了ListPreference用户需要选择他们想要保存的位置的位置.在那个应用程序中,我扫描了/ proc/mounts和/system/etc/vold.fstab以获取sdcard挂载点.我将每个文件中的挂载点存储到两个单独的ArrayLists中.

然后,我将一个列表与另一个列表进行比较,并丢弃两个列表中不存在的项目.这给了我一张每张sdcard的根路径列表.

从那里,我测试了路径File.exists(),File.isDirectory()File.canWrite().如果这些测试中的任何一个都是错误的,我就会从列表中丢弃该路径.

无论列表中剩下什么,我都转换为String[]数组,因此ListPreference值属性可以使用它.

您可以在此处查看代码:http://sapienmobile.com/?p = 204


and*_*per 19

您可以尝试使用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());
Run Code Online (Sandbox Code Playgroud)

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

多个".getParentFile()"的原因是上升到另一个文件夹,因为原始路径是

.../Android/data/YOUR_APP_PACKAGE_NAME/files/
Run Code Online (Sandbox Code Playgroud)

编辑:这是我创建的更全面的方法,以获得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)
        return file.getAbsolutePath();
      file=parentFile;
      }
    }
Run Code Online (Sandbox Code Playgroud)


Pao*_*lli 17

要检索所有外部存储(无论是SD卡还是内部不可移动存储),您可以使用以下代码:

final String state = Environment.getExternalStorageState();

if ( Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state) ) {  // we can read the External Storage...           
    //Retrieve the primary External Storage:
    final File primaryExternalStorage = Environment.getExternalStorageDirectory();

    //Retrieve the External Storages root directory:
    final String externalStorageRootDir;
    if ( (externalStorageRootDir = primaryExternalStorage.getParent()) == null ) {  // no parent...
        Log.d(TAG, "External Storage: " + primaryExternalStorage + "\n");
    }
    else {
        final File externalStorageRoot = new File( externalStorageRootDir );
        final File[] files = externalStorageRoot.listFiles();

        for ( final File file : files ) {
            if ( file.isDirectory() && file.canRead() && (file.listFiles().length > 0) ) {  // it is a real directory (not a USB drive)...
                Log.d(TAG, "External Storage: " + file.getAbsolutePath() + "\n");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用System.getenv("EXTERNAL_STORAGE")来检索主外部存储目录(例如"/ storage/sdcard0")和System.getenv("SECONDARY_STORAGE")以查找所有辅助目录的列表(例如"/storage/extSdCard:/ storage/UsbDriveA:/ storage/UsbDriveB").请记住,同样在这种情况下,您可能希望过滤辅助目录列表以排除USB驱动器.

在任何情况下,请注意使用硬编码路径始终是一种不好的方法(特别是当每个制造商可能会高兴地改变它时).

  • 只要考虑任何不发表评论的不愿发表评论的人,那么我就予以弥补。;)但是,我想您的方法相当随意:我们如何知道跳过这些“ USB驱动器”,而实际上保留所有其他内容等同于“ sdcards”,正如问题所问的那样?另外,建议的`System.getenv(“ SECONDARY_STORAGE”)`也可以使用某些引用,因为它似乎没有记载。 (2认同)

Vit*_*huk 13

像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;
    }    
}
Run Code Online (Sandbox Code Playgroud)


Jan*_*dec 11

通过读取/proc/mounts(标准Linux文件)和交叉检查vold数据(/system/etc/vold.conf),可以找到安装任何其他SD卡的位置.请注意,返回的位置Environment.getExternalStorageDirectory()可能不会出现在vold配置中(在某些设备中,它是无法卸载的内部存储),但仍必须包含在列表中.但是我们没有找到向用户描述它们的好方法.


Tap*_*ave 7

我在这个时候尝试了这个主题内的所有解决方案.但是所有这些都无法在具有一个外部(可移动)和一个内部(不可移动)卡的设备上正常工作.无法从"mount"命令,"proc/mounts"文件等获取外部卡的路径.

我创建了自己的解决方案(在Paulo Luan上):

String sSDpath = null;
File   fileCur = null;
for( String sPathCur : Arrays.asList( "ext_card", "external_sd", "ext_sd", "external", "extSdCard",  "externalSdCard")) // external sdcard
{
   fileCur = new File( "/mnt/", sPathCur);
   if( fileCur.isDirectory() && fileCur.canWrite())
   {
     sSDpath = fileCur.getAbsolutePath();
     break;
   }
}
fileCur = null;
if( sSDpath == null)  sSDpath = Environment.getExternalStorageDirectory().getAbsolutePath();
Run Code Online (Sandbox Code Playgroud)


Jar*_*ler 6

如果您查看源代码,android.os.Environment您会发现Android在很大程度上依赖于路径的环境变量.您可以使用"SECONDARY_STORAGE"环境变量来查找可移动SD卡的路径.

/**
 * Get a file using an environmental variable.
 *
 * @param variableName
 *         The Environment variable name.
 * @param paths
 *         Any paths to the file if the Environment variable was not found.
 * @return the File or {@code null} if the File could not be located.
 */
private static File getDirectory(String variableName, String... paths) {
    String path = System.getenv(variableName);
    if (!TextUtils.isEmpty(path)) {
        if (path.contains(":")) {
            for (String _path : path.split(":")) {
                File file = new File(_path);
                if (file.exists()) {
                    return file;
                }
            }
        } else {
            File file = new File(path);
            if (file.exists()) {
                return file;
            }
        }
    }
    if (paths != null && paths.length > 0) {
        for (String _path : paths) {
            File file = new File(_path);
            if (file.exists()) {
                return file;
            }
        }
    }
    return null;
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

public static final File REMOVABLE_STORAGE = getDirectory("SECONDARY_STORAGE");
Run Code Online (Sandbox Code Playgroud)


AnV*_*AnV 6

是否有通用的方法来查找外部SD卡的位置?

通过普遍的方式,如果你的意思是官方方式; 是的,有一个.

在API级别19,即Android版本4.4 Kitkat中,他们添加File[] getExternalFilesDirs (String type)ContextClass,允许应用程序将数据/文件存储在micro SD卡中.

Android 4.4是该平台的第一个版本,它实际上允许应用程序使用SD卡进行存储.在API级别19之前对SD卡的任何访问都是通过私有的,不受支持的API.

getExternalFilesDirs(String type)返回所有共享/外部存储设备上特定于应用程序的目录的绝对路径.这意味着,它将返回内部和外部内存的路径.通常,第二个返回路径将是microSD卡的存储路径(如果有的话).

但请注意,

共享存储可能并不总是可用,因为用户可以弹出可移动媒体.可以使用检查媒体状态 getExternalStorageState(File).

这些文件没有强制执行安全性.例如,任何持有的应用程序WRITE_EXTERNAL_STORAGE都可以写入这些文件.

根据Google /官方Android文档,内部和外部存储术语与我们的想法完全不同.


Beh*_*z.M 5

只需简单地使用:

String primary_sd = System.getenv("EXTERNAL_STORAGE");
if(primary_sd != null)
    Log.i("EXTERNAL_STORAGE", primary_sd);
String secondary_sd = System.getenv("SECONDARY_STORAGE");
if(secondary_sd != null)
    Log.i("SECONDARY_STORAGE", secondary_sd)
Run Code Online (Sandbox Code Playgroud)