如何获取 Android 4.0+ 的外部 SD 卡路径?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11281010/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 06:42:07  来源:igfitidea点击:

How can I get the external SD card path for Android 4.0+?

androidandroid-sdcard

提问by Romulus Urakagi Ts'ai

Samsung Galaxy S3has an external SD card slot, which is mounted to /mnt/extSdCard.

三星 Galaxy S3有一个外部 SD 卡插槽,安装在/mnt/extSdCard.

How can I get this path by something like Environment.getExternalStorageDirectory()?

我怎样才能通过类似的方式获得这条路径Environment.getExternalStorageDirectory()

This will return mnt/sdcard, and I can't find the API for the external SD card. (Or removable USB storage on some tablets.)

这将返回mnt/sdcard,我找不到外部 SD 卡的 API。(或某些平板电脑上的可移动 USB 存储设备。)

采纳答案by Gnathonic

I have a variation on a solution I found here

我在这里找到的解决方案有所不同

public static HashSet<String> getExternalMounts() {
    final HashSet<String> out = new HashSet<String>();
    String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
    String s = "";
    try {
        final Process process = new ProcessBuilder().command("mount")
                .redirectErrorStream(true).start();
        process.waitFor();
        final InputStream is = process.getInputStream();
        final byte[] buffer = new byte[1024];
        while (is.read(buffer) != -1) {
            s = s + new String(buffer);
        }
        is.close();
    } catch (final Exception e) {
        e.printStackTrace();
    }

    // parse output
    final String[] lines = s.split("\n");
    for (String line : lines) {
        if (!line.toLowerCase(Locale.US).contains("asec")) {
            if (line.matches(reg)) {
                String[] parts = line.split(" ");
                for (String part : parts) {
                    if (part.startsWith("/"))
                        if (!part.toLowerCase(Locale.US).contains("vold"))
                            out.add(part);
                }
            }
        }
    }
    return out;
}

The original method was tested and worked with

原始方法经过测试并使用

  • Huawei X3 (stock)
  • Galaxy S2 (stock)
  • Galaxy S3 (stock)
  • 华为X3(现货)
  • Galaxy S2(现货)
  • Galaxy S3(现货)

I'm not certain which android version these were on when they were tested.

我不确定它们在测试时使用的是哪个 android 版本。

I've tested my modified version with

我已经测试了我的修改版本

  • Moto Xoom 4.1.2 (stock)
  • Galaxy Nexus (cyanogenmod 10) using an otg cable
  • HTC Incredible (cyanogenmod 7.2) this returned both the internal and external. This device is kinda an oddball in that its internal largely goes unused as getExternalStorage() returns a path to the sdcard instead.
  • Moto Xoom 4.1.2(现货)
  • 使用 otg 电缆的 Galaxy Nexus(cyanogenmod 10)
  • HTC Incredible (cyanogenmod 7.2) 这返回了内部和外部。这个设备有点奇怪,因为它的内部在很大程度上没有被使用,因为 getExternalStorage() 返回一个到 SD 卡的路径。

and some single storage devices that use an sdcard as their main storage

以及一些使用 sdcard 作为主存储的单一存储设备

  • HTC G1 (cyanogenmod 6.1)
  • HTC G1 (stock)
  • HTC Vision/G2 (stock)
  • HTC G1(氰基 6.1)
  • HTC G1(现货)
  • HTC Vision/G2(现货)

Excepting the Incredible all these devices only returned their removable storage. There are probably some extra checks I should be doing, but this is at least a bit better than any solution I've found thus far.

除了 Incredible,所有这些设备都只返回了它们的可移动存储。我可能应该做一些额外的检查,但这至少比我迄今为止找到的任何解决方案都要好一些。

回答by Dmitriy Lozenko

I found more reliable way to get paths to all SD-CARDs in system. This works on all Android versions and return paths to all storages (include emulated).

我找到了更可靠的方法来获取系统中所有 SD-CARD 的路径。这适用于所有 Android 版本并返回所有存储(包括模拟)的路径。

Works correctly on all my devices.

在我所有的设备上都能正常工作。

P.S.: Based on source code of Environment class.

PS:基于 Environment 类的源代码。

private static final Pattern DIR_SEPORATOR = Pattern.compile("/");

/**
 * Raturns all available SD-Cards in the system (include emulated)
 *
 * Warning: Hack! Based on Android source code of version 4.3 (API 18)
 * Because there is no standart way to get it.
 * TODO: Test on future Android versions 4.4+
 *
 * @return paths to all available SD-Cards in the system (include emulated)
 */
public static String[] getStorageDirectories()
{
    // Final set of paths
    final Set<String> rv = new HashSet<String>();
    // Primary physical SD-CARD (not emulated)
    final String rawExternalStorage = System.getenv("EXTERNAL_STORAGE");
    // All Secondary SD-CARDs (all exclude primary) separated by ":"
    final String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE");
    // Primary emulated SD-CARD
    final String rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET");
    if(TextUtils.isEmpty(rawEmulatedStorageTarget))
    {
        // Device has physical external storage; use plain paths.
        if(TextUtils.isEmpty(rawExternalStorage))
        {
            // EXTERNAL_STORAGE undefined; falling back to default.
            rv.add("/storage/sdcard0");
        }
        else
        {
            rv.add(rawExternalStorage);
        }
    }
    else
    {
        // Device has emulated storage; external storage paths should have
        // userId burned into them.
        final String rawUserId;
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
        {
            rawUserId = "";
        }
        else
        {
            final String path = Environment.getExternalStorageDirectory().getAbsolutePath();
            final String[] folders = DIR_SEPORATOR.split(path);
            final String lastFolder = folders[folders.length - 1];
            boolean isDigit = false;
            try
            {
                Integer.valueOf(lastFolder);
                isDigit = true;
            }
            catch(NumberFormatException ignored)
            {
            }
            rawUserId = isDigit ? lastFolder : "";
        }
        // /storage/emulated/0[1,2,...]
        if(TextUtils.isEmpty(rawUserId))
        {
            rv.add(rawEmulatedStorageTarget);
        }
        else
        {
            rv.add(rawEmulatedStorageTarget + File.separator + rawUserId);
        }
    }
    // Add all secondary storages
    if(!TextUtils.isEmpty(rawSecondaryStoragesStr))
    {
        // All Secondary SD-CARDs splited into array
        final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator);
        Collections.addAll(rv, rawSecondaryStorages);
    }
    return rv.toArray(new String[rv.size()]);
}

回答by FabianCook

I guess to use the external sdcard you need to use this:

我想使用您需要使用的外部 SD 卡:

new File("/mnt/external_sd/")

OR

或者

new File("/mnt/extSdCard/")

in your case...

在你的情况...

in replace of Environment.getExternalStorageDirectory()

代替 Environment.getExternalStorageDirectory()

Works for me. You should check whats in the directory mnt first and work from there..

为我工作。您应该首先检查目录 mnt 中的内容,然后从那里开始工作..



You should use some type of selection method to choose which sdcard to use:

您应该使用某种类型的选择方法来选择要使用的 SD 卡:

File storageDir = new File("/mnt/");
if(storageDir.isDirectory()){
    String[] dirList = storageDir.list();
    //TODO some type of selecton method?
}

回答by Paolo Rovelli

In order to retrieve all the External Storages(whether they are SD cardsor internal non-removable storages), you can use the following code:

为了检索所有外部存储(无论是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");
            }
        }
    }
}

Alternatively, you might use System.getenv("EXTERNAL_STORAGE")to retrieve the primary External Storage directory (e.g. "/storage/sdcard0") and System.getenv("SECONDARY_STORAGE")to retieve the list of all the secondary directories (e.g. "/storage/extSdCard:/storage/UsbDriveA:/storage/UsbDriveB"). Remember that, also in this case, you might want to filter the list of secondary directories in order to exclude the USB drives.

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

In any case, please note that using hard-coded paths is always a bad approach (expecially when every manufacturer may change it as pleased).

在任何情况下,请注意使用硬编码路径始终是一种糟糕的方法(尤其是当每个制造商都可以随心所欲地更改它时)。

回答by HendraWD

I was using Dmitriy Lozenko's solution until i checked on an Asus Zenfone2, Marshmallow 6.0.1and the solution is not working. The solution failed when getting EMULATED_STORAGE_TARGET, specifically for microSD path, i.e: /storage/F99C-10F4/. I edited the code to get the emulated root paths directly from emulated application paths with context.getExternalFilesDirs(null);and add more known phone-model-specificphysical paths.

我一直在使用Dmitriy Lozenko的解决方案,直到我检查了Asus Zenfone2Marshmallow 6.0.1并且该解决方案不起作用。获取EMULATED_STORAGE_TARGET时解决方案失败,特别是对于 microSD 路径,即:/storage/F99C-10F4/。我编辑了代码以直接从模拟的应用程序路径中获取模拟的根路径,context.getExternalFilesDirs(null);并添加更多已知的特定于手机型号的物理路径。

To make our life easier, I made a library here. You can use it via gradle, maven, sbt, and leiningen build system.

为了让我们的生活更轻松,我在这里建了一个图书馆。您可以通过 gradle、maven、sbt 和 leiningen 构建系统使用它。

If you like the old-fashioned way, you can also copy paste the file directly from here, but you will not know if there is an update in the future without checking it manually.

如果你喜欢老式的方式,你也可以直接从这里复制粘贴文件,但是如果不手动检查,你将不知道将来是否有更新。

If you have any question or suggestion, please let me know

如果您有任何问题或建议,请告诉我

回答by Jeff Sharkey

Good news! In KitKat there's now a public API for interacting with these secondary shared storage devices.

好消息!在 KitKat 中,现在有一个公共 API 用于与这些辅助共享存储设备进行交互。

The new Context.getExternalFilesDirs()and Context.getExternalCacheDirs()methods can return multiple paths, including both primary and secondary devices. You can then iterate over them and check Environment.getStorageState()and File.getFreeSpace()to determine the best place to store your files. These methods are also available on ContextCompatin the support-v4 library.

newContext.getExternalFilesDirs()Context.getExternalCacheDirs()方法可以返回多个路径,包括主要和次要设备。然后,您可以遍历它们并检查Environment.getStorageState()File.getFreeSpace()确定存储文件的最佳位置。ContextCompatsupport-v4 库中也提供了这些方法。

Also note that if you're only interested in using the directories returned by Context, you no longer need the READ_or WRITE_EXTERNAL_STORAGEpermissions. Going forward, you'll always have read/write access to these directories with no additional permissions required.

另请注意,如果您只对使用 返回的目录感兴趣,则Context不再需要READ_WRITE_EXTERNAL_STORAGE权限。展望未来,您将始终拥有对这些目录的读/写访问权限,而无需额外的权限。

Apps can also continue working on older devices by end-of-lifing their permission request like this:

应用程序还可以通过像这样终止其许可请求来继续在旧设备上工作:

<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="18" />

回答by android developer

Here's how I get the list of SD-card paths (excluding the primary external storage) :

这是我获取 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;
      }
    }

回答by valenta

I did the following to get acces to all the external sd cards.

我执行以下操作以访问所有外部 SD 卡。

With:

和:

File primaryExtSd=Environment.getExternalStorageDirectory();

you get the path to the primary external SD Then with:

你得到了主外部 SD 的路径然后:

File parentDir=new File(primaryExtSd.getParent());

you get the parent dir of the primary external storage, and it is also the parent of all the external sd. Now, you can list all the storage and select the one that you want.

您获得主要外部存储的父目录,它也是所有外部 sd 的父目录。现在,您可以列出所有存储并选择所需的存储。

Hope it is usefull.

希望它有用。

回答by rml

Thanks for the clues provided by you guys, especially @SmartLemon, I got the solution. In case someone else need it, I put my final solution here( to find the first listed external SD card ):

感谢你们提供的线索,尤其是@SmartLemon,我得到了解决方案。如果其他人需要它,我把我的最终解决方案放在这里(找到第一个列出的外部 SD 卡):

public File getExternalSDCardDirectory()
{
    File innerDir = Environment.getExternalStorageDirectory();
    File rootDir = innerDir.getParentFile();
    File firstExtSdCard = innerDir ;
    File[] files = rootDir.listFiles();
    for (File file : files) {
        if (file.compareTo(innerDir) != 0) {
            firstExtSdCard = file;
            break;
        }
    }
    //Log.i("2", firstExtSdCard.getAbsolutePath().toString());
    return firstExtSdCard;
}

If no external SD card there, then it returns the on board storage. I will use it if the sdcard is not exist, you may need to change it.

如果那里没有外部 SD 卡,则返回板载存储。如果 sdcard 不存在,我将使用它,您可能需要更改它。

回答by cst05001

refer to my code, hope helpful for you:

参考我的代码,希望对你有帮助:

    Runtime runtime = Runtime.getRuntime();
    Process proc = runtime.exec("mount");
    InputStream is = proc.getInputStream();
    InputStreamReader isr = new InputStreamReader(is);
    String line;
    String mount = new String();
    BufferedReader br = new BufferedReader(isr);
    while ((line = br.readLine()) != null) {
        if (line.contains("secure")) continue;
        if (line.contains("asec")) continue;

        if (line.contains("fat")) {//TF card
            String columns[] = line.split(" ");
            if (columns != null && columns.length > 1) {
                mount = mount.concat("*" + columns[1] + "\n");
            }
        } else if (line.contains("fuse")) {//internal storage
            String columns[] = line.split(" ");
            if (columns != null && columns.length > 1) {
                mount = mount.concat(columns[1] + "\n");
            }
        }
    }
    txtView.setText(mount);