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

95

三星 Galaxy S3具有外部SD卡插槽,它被挂载到 /mnt/extSdCard

我该如何像Environment.getExternalStorageDirectory()这样获取此路径?

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


为什么你想要这样做?在Android上做这样的事情是非常不可取的。也许如果你分享一下你的动机,我们可以指导你朝着最佳实践的方向前进。 - rharter
2
当用户更换手机时,将SD卡插入新手机,在第一个手机上它是/sdcard,在第二个手机上它是/mnt/extSdCard,任何使用文件路径的东西都会崩溃。我需要从相对路径生成真实路径。 - Romulus Urakagi Ts'ai
我知道这个话题有点老了,但这可能会有所帮助。你应该使用这种方法。System.getenv();请参考Environment3项目以访问连接到您设备的所有存储。https://github.com/omidfaraji/Environment3 - Omid Faraji
可能是查找外部SD卡位置的重复问题。 - Brian Rogers
这是我的解决方案,适用于Nougat版本之前:https://dev59.com/Npbfa4cB1Zd3GeqPrk7J#40205116 - Gokul NC
27个回答

3

以下解决方案是根据其他回答组合而成的(如@ono所提到的),它处理了在Marshmallow版本中System.getenv("SECONDARY_STORAGE")无用的事实。

已测试并可行于:

  • 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;
    }
    

条件rawSecondaryStoragesStr.contains(path)可以允许内部存储也被添加;我们最好删除该条件。我遇到了这个问题,因为我的设备使用System.getenv("SECONDARY_STORAGE")返回内部存储目录,但在KitKat上使用System.getenv(EXTERNAL_STORAGE)返回外部存储卡路径;看起来不同的供应商实现方式不同,没有任何标准。 - Gokul NC
这是我的解决方案,适用于Nougat版本:https://dev59.com/Npbfa4cB1Zd3GeqPrk7J#40205116 - Gokul NC

2
实际上,在某些设备中,外部SD卡的默认名称显示为extSdCard,而在其他设备中,则显示为sdcard1
此代码段可帮助查找确切路径,并帮助检索外部设备的路径。
String sdpath,sd1path,usbdiskpath,sd0path;    
        if(new File("/storage/extSdCard/").exists())
            {
               sdpath="/storage/extSdCard/";
               Log.i("Sd Cardext Path",sdpath);
            }
        if(new File("/storage/sdcard1/").exists())
         {
              sd1path="/storage/sdcard1/";
              Log.i("Sd Card1 Path",sd1path);
         }
        if(new File("/storage/usbcard1/").exists())
         {
              usbdiskpath="/storage/usbcard1/";
              Log.i("USB Path",usbdiskpath);
         }
        if(new File("/storage/sdcard0/").exists())
         {
              sd0path="/storage/sdcard0/";
              Log.i("Sd Card0 Path",sd0path);
         }

这对我帮助很大。 - Droid Chris
有些设备使用"/storage/external_sd"(LG G3 KitKat),Marshmallow则有一些不同的方案,Nexus 5X上使用Android 6.0的内部存储是"/mnt/sdcard",外部SD卡则位于"/storage/XXXX-XXXX"下。 - A.B.

2

是的。不同的制造商使用不同的SD卡名称,例如在三星Tab 3中使用extsd,在其他三星设备中使用sdcard,不同的制造商使用不同的名称。

我有和你一样的需求,所以我从我的项目中创建了一个示例供你参考,可以前往此链接Android目录选择器示例,该示例使用了andriod-dirchooser库。这个示例可以检测SD卡并列出所有子文件夹,并且还可以检测设备是否有多个SD卡。

代码的部分内容如下,完整示例请前往此链接Android目录选择器

/**
* Returns the path to internal storage ex:- /storage/emulated/0
 *
* @return
 */
private String getInternalDirectoryPath() {
return Environment.getExternalStorageDirectory().getAbsolutePath();
 }

/**
 * Returns the SDcard storage path for samsung ex:- /storage/extSdCard
 *
 * @return
 */
    private String getSDcardDirectoryPath() {
    return System.getenv("SECONDARY_STORAGE");
}


 mSdcardLayout.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View view) {
        String sdCardPath;
        /***
         * Null check because user may click on already selected buton before selecting the folder
         * And mSelectedDir may contain some wrong path like when user confirm dialog and swith back again
         */

        if (mSelectedDir != null && !mSelectedDir.getAbsolutePath().contains(System.getenv("SECONDARY_STORAGE"))) {
            mCurrentInternalPath = mSelectedDir.getAbsolutePath();
        } else {
            mCurrentInternalPath = getInternalDirectoryPath();
        }
        if (mCurrentSDcardPath != null) {
            sdCardPath = mCurrentSDcardPath;
        } else {
            sdCardPath = getSDcardDirectoryPath();
        }
        //When there is only one SDcard
        if (sdCardPath != null) {
            if (!sdCardPath.contains(":")) {
                updateButtonColor(STORAGE_EXTERNAL);
                File dir = new File(sdCardPath);
                changeDirectory(dir);
            } else if (sdCardPath.contains(":")) {
                //Multiple Sdcards show root folder and remove the Internal storage from that.
                updateButtonColor(STORAGE_EXTERNAL);
                File dir = new File("/storage");
                changeDirectory(dir);
            }
        } else {
            //In some unknown scenario at least we can list the root folder
            updateButtonColor(STORAGE_EXTERNAL);
            File dir = new File("/storage");
            changeDirectory(dir);
        }


    }
});

1

System.getenv("SECONDARY_STORAGE")在Marshmallow中返回null。这是另一种查找所有外部目录的方法。您可以检查它是否可移动,以确定是内部还是外部。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    File[] externalCacheDirs = context.getExternalCacheDirs();
    for (File file : externalCacheDirs) {
        if (Environment.isExternalStorageRemovable(file)) {
            // It's a removable storage
        }
    }
}

1
       File[] files = null;
    File file = new File("/storage");// /storage/emulated
if (file.exists()) {
        files = file.listFiles();
            }
            if (null != files)
                for (int j = 0; j < files.length; j++) {
                    Log.e(TAG, "" + files[j]);
                    Log.e(TAG, "//--//--// " +             files[j].exists());

                    if (files[j].toString().replaceAll("_", "")
                            .toLowerCase().contains("extsdcard")) {
                        external_path = files[j].toString();
                        break;
                    } else if (files[j].toString().replaceAll("_", "")
                            .toLowerCase()
                            .contains("sdcard".concat(Integer.toString(j)))) {
                        // external_path = files[j].toString();
                    }
                    Log.e(TAG, "--///--///--  " + external_path);
                }

1
在一些设备上(例如三星Galaxy SII),内部存储卡可能采用VFAT格式。在这种情况下,请使用上述代码,我们可以获取内部存储卡的路径(/mnt/sdcad),但无法获取外部存储卡。下面的代码可以解决这个问题。
static String getExternalStorage(){
         String exts =  Environment.getExternalStorageDirectory().getPath();
         try {
            FileReader fr = new FileReader(new File("/proc/mounts"));       
            BufferedReader br = new BufferedReader(fr);
            String sdCard=null;
            String line;
            while((line = br.readLine())!=null){
                if(line.contains("secure") || line.contains("asec")) continue;
            if(line.contains("fat")){
                String[] pars = line.split("\\s");
                if(pars.length<2) continue;
                if(pars[1].equals(exts)) continue;
                sdCard =pars[1]; 
                break;
            }
        }
        fr.close();
        br.close();
        return sdCard;  

     } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return null;
}

0
 String path = Environment.getExternalStorageDirectory()
                        + File.separator + Environment.DIRECTORY_PICTURES;
                File dir = new File(path);

4
不起作用,它返回内部存储的路径。即:/storage/emulated/0 - user2471325

0

我已经尝试了Dmitriy LozenkoGnathonic提供的解决方案,但都没有帮助我检索到外部SD卡目录的路径。mount命令执行包含所需的外部SD卡目录路径(即/Storage/A5F9-15F4),但它与正则表达式不匹配,因此未返回。我不理解三星遵循的目录命名机制。为什么他们偏离标准(即extsdcard),并像在我的情况下一样提出一些非常奇怪的东西(即/Storage/A5F9-15F4)。我是否遗漏了什么?无论如何,更改Gnathonic解决方案的正则表达式后,我成功获取了有效的SD卡目录:

final HashSet<String> out = new HashSet<String>();
        String reg = "(?i).*(vold|media_rw).*(sdcard|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;

我不确定这是否是一个有效的解决方案,以及它是否适用于其他三星平板电脑,但它暂时解决了我的问题。以下是在Android(v6.0)中检索可移动SD卡路径的另一种方法。我已经在Android Marshmallow上测试了该方法,并且它可以正常工作。其中使用的方法非常基本,肯定也适用于其他版本,但测试是必要的。对此有一些了解将会很有帮助:

public static String getSDCardDirPathForAndroidMarshmallow() {

    File rootDir = null;

    try {
        // Getting external storage directory file
        File innerDir = Environment.getExternalStorageDirectory();

        // Temporarily saving retrieved external storage directory as root
        // directory
        rootDir = innerDir;

        // Splitting path for external storage directory to get its root
        // directory

        String externalStorageDirPath = innerDir.getAbsolutePath();

        if (externalStorageDirPath != null
                && externalStorageDirPath.length() > 1
                && externalStorageDirPath.startsWith("/")) {

            externalStorageDirPath = externalStorageDirPath.substring(1,
                    externalStorageDirPath.length());
        }

        if (externalStorageDirPath != null
                && externalStorageDirPath.endsWith("/")) {

            externalStorageDirPath = externalStorageDirPath.substring(0,
                    externalStorageDirPath.length() - 1);
        }

        String[] pathElements = externalStorageDirPath.split("/");

        for (int i = 0; i < pathElements.length - 1; i++) {

            rootDir = rootDir.getParentFile();
        }

        File[] files = rootDir.listFiles();

        for (File file : files) {
            if (file.exists() && file.compareTo(innerDir) != 0) {

                // Try-catch is implemented to prevent from any IO exception
                try {

                    if (Environment.isExternalStorageRemovable(file)) {
                        return file.getAbsolutePath();

                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return null;
}

如果您有其他处理此问题的方法,请分享。谢谢。


这是我的解决方案,适用于Nougat版本之前:https://dev59.com/Npbfa4cB1Zd3GeqPrk7J#40205116 - Gokul NC
@HendraWD,你提出的解决方案可能在Android Marshmallow(6.0)上无法使用,因为它不支持环境变量。我记不太清了,但我想我已经尝试过了。 - Abdul Rehman
@GokulNC 是的,这就是为什么当版本大于Marshmallow时我使用context.getExternalFilesDirs(null);而不是直接使用物理路径的原因。 - HendraWD
@Abdul 可能没有获取到shell来执行命令,或者是grep出现了一些问题。无论如何,逻辑是从mount命令中获取存储卡目录。你可以通过其他方法获取shell并执行命令并处理输出。 - Gokul NC
尝试在终端模拟器应用程序中查看输出结果。同时通过尝试仅使用“mount”命令,检查问题是否是由“grep”命令引起的。 - Gokul NC
显示剩余4条评论

0

在我的HTC One X(Android)中访问SD卡中的文件,我使用以下路径:

file:///storage/sdcard0/folder/filename.jpg

注意三个斜杠!


0

您可以使用类似于Context.getExternalCacheDirs()、Context.getExternalFilesDirs()或Context.getObbDirs()的方法。它们提供了应用程序特定目录,可以在所有外部存储设备中存储其文件。

因此,像这样的代码 - Context.getExternalCacheDirs()[i].getParentFile().getParentFile().getParentFile().getParent() 可以获取外部存储设备的根路径。

我知道这些命令是为不同的目的而设计的,但其他答案对我没有用。

这个链接给了我很好的指导 - https://possiblemobile.com/2014/03/android-external-storage/


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