为存储文件在Android中打开外部存储目录(sdcard)

37

我希望能够通过编程打开外部存储目录路径以保存文件。我已经尝试过了,但是无法获取SD卡路径。有什么解决方法吗?

private File path = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "");
或者
private File path = new File(Environment.getExternalStorageDirectory() + "");

我尝试使用上述两种方法获取路径,但两者都指向内部存储器。

当我们打开存储器时,如果sd卡存在,它会显示如下图所示-

设备存储和sd存储卡。

我想通过编码获取sd存储路径。 我已在清单中授予权限-

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

是的,我已经授予了WRITE_EXTERNAL_STORAGE权限。 - yuva ツ
同时添加 READ_EXTERNAL_STORAGE 权限。 - Chirag Ghori
@BlackTiger:仍然无法获取SD卡路径。 - yuva ツ
String root = Environment.getExternalStorageDirectory().toString(); 如果您已经正确完成了所有操作,它将返回路径。请发布您的完整代码。 - Rethinavel
你提供的路径与我尝试的相同。我已经发布了它。我想要SD卡目录路径,而你所说的是内部存储器/设备存储器。 - yuva ツ
@yuvaツ,按照我的回答使用这个:String pathName = "/mnt/"; 或者使用这个:String pathName = "/storage/"; - Farhan Shah
8个回答

61

我曾经遇到过完全相同的问题!

要获取内部SD卡,您可以使用

String extStore = System.getenv("EXTERNAL_STORAGE");
File f_exts = new File(extStore);

要获取外部SD卡,您可以使用

String secStore = System.getenv("SECONDARY_STORAGE");
File f_secs = new File(secStore);

运行代码后

 extStore = "/storage/emulated/legacy"
 secStore = "/storage/extSdCarcd"

运行完美!


区分主要外部存储和次要外部存储是很好的...谢谢。 - neeraj kirola
谢谢你的回答。我对它进行了补充,但是在评论区里放不下。请将你的名字放在我的回答上,作为原始回答。 - Alexandre
7
在安卓模拟器中尝试使用System.getenv("SECONDARY_STORAGE")时,它返回了null。 - bikram
1
@bikrampandit:这很可能是因为您没有将“次要”存储设备连接到模拟器上。此外,我注意到新版本的Android已经将两个内存合并在一起,但我怀疑这不是原因。 - Rijul Gupta
1
在搭载Android 7的Galaxy 7 Edge上无法运行。System.getenv("SECONDARY_STORAGE")返回null,但SD卡已插入。 - BArtWell
显示剩余3条评论

20
在API中,“内部存储”被称为“外部存储”。如Environment文档中所述:
注意:这里的“external”一词可能会让您感到困惑。最好将此目录视为媒体/共享存储。它是一个可以容纳相对大量数据并且在所有应用程序之间共享(不强制执行权限)的文件系统。传统上,这是SD卡,但它也可以作为单独于受保护的内部存储器不同的设备中的内置存储器实现,并且可以在计算机上作为文件系统挂载。
要区分“Environment.getExternalStorageDirectory()”实际返回的是物理上的内部还是外部存储,请调用Environment.isExternalStorageEmulated()。如果它是模拟的,则为内部存储。在具有内部存储和sdcard插槽的较新设备上,Environment.getExternalStorageDirectory()将始终返回内部存储。而在仅具有sdcard作为媒体存储选项的旧设备上,它将始终返回sdcard。
使用当前Android API无法检索所有存储。
我已经创建了一个基于Vitaliy Polchuk答案中的方法的帮助器。 如何获取Android设备的已安装外部存储列表 注意:从KitKat开始,次要存储仅作为只读存储器可访问,您可能需要使用以下方法检查可写性。
/**
 * Checks whether the StorageVolume is read-only
 * 
 * @param volume
 *            StorageVolume to check
 * @return true, if volume is mounted read-only
 */
public static boolean isReadOnly(@NonNull final StorageVolume volume) {
    if (volume.mFile.equals(Environment.getExternalStorageDirectory())) {
        // is a primary storage, check mounted state by Environment
        return android.os.Environment.getExternalStorageState().equals(
                android.os.Environment.MEDIA_MOUNTED_READ_ONLY);
    } else {
        if (volume.getType() == Type.USB) {
            return volume.isReadOnly();
        }
        //is not a USB storagem so it's read-only if it's mounted read-only or if it's a KitKat device
        return volume.isReadOnly() || Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
    }
}

StorageHelper 类

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;

import android.os.Environment;

public final class StorageHelper {

    //private static final String TAG = "StorageHelper";

    private StorageHelper() {
    }

    private static final String STORAGES_ROOT;

    static {
        final String primaryStoragePath = Environment.getExternalStorageDirectory()
                .getAbsolutePath();
        final int index = primaryStoragePath.indexOf(File.separatorChar, 1);
        if (index != -1) {
            STORAGES_ROOT = primaryStoragePath.substring(0, index + 1);
        } else {
            STORAGES_ROOT = File.separator;
        }
    }

    private static final String[] AVOIDED_DEVICES = new String[] {
        "rootfs", "tmpfs", "dvpts", "proc", "sysfs", "none"
    };

    private static final String[] AVOIDED_DIRECTORIES = new String[] {
        "obb", "asec"
    };

    private static final String[] DISALLOWED_FILESYSTEMS = new String[] {
        "tmpfs", "rootfs", "romfs", "devpts", "sysfs", "proc", "cgroup", "debugfs"
    };

    /**
     * Returns a list of mounted {@link StorageVolume}s Returned list always
     * includes a {@link StorageVolume} for
     * {@link Environment#getExternalStorageDirectory()}
     * 
     * @param includeUsb
     *            if true, will include USB storages
     * @return list of mounted {@link StorageVolume}s
     */
    public static List<StorageVolume> getStorages(final boolean includeUsb) {
        final Map<String, List<StorageVolume>> deviceVolumeMap = new HashMap<String, List<StorageVolume>>();

        // this approach considers that all storages are mounted in the same non-root directory
        if (!STORAGES_ROOT.equals(File.separator)) {
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new FileReader("/proc/mounts"));
                String line;
                while ((line = reader.readLine()) != null) {
                    // Log.d(TAG, line);
                    final StringTokenizer tokens = new StringTokenizer(line, " ");

                    final String device = tokens.nextToken();
                    // skipped devices that are not sdcard for sure
                    if (arrayContains(AVOIDED_DEVICES, device)) {
                        continue;
                    }

                    // should be mounted in the same directory to which
                    // the primary external storage was mounted
                    final String path = tokens.nextToken();
                    if (!path.startsWith(STORAGES_ROOT)) {
                        continue;
                    }

                    // skip directories that indicate tha volume is not a storage volume
                    if (pathContainsDir(path, AVOIDED_DIRECTORIES)) {
                        continue;
                    }

                    final String fileSystem = tokens.nextToken();
                    // don't add ones with non-supported filesystems
                    if (arrayContains(DISALLOWED_FILESYSTEMS, fileSystem)) {
                        continue;
                    }

                    final File file = new File(path);
                    // skip volumes that are not accessible
                    if (!file.canRead() || !file.canExecute()) {
                        continue;
                    }

                    List<StorageVolume> volumes = deviceVolumeMap.get(device);
                    if (volumes == null) {
                        volumes = new ArrayList<StorageVolume>(3);
                        deviceVolumeMap.put(device, volumes);
                    }

                    final StorageVolume volume = new StorageVolume(device, file, fileSystem);
                    final StringTokenizer flags = new StringTokenizer(tokens.nextToken(), ",");
                    while (flags.hasMoreTokens()) {
                        final String token = flags.nextToken();
                        if (token.equals("rw")) {
                            volume.mReadOnly = false;
                            break;
                        } else if (token.equals("ro")) {
                            volume.mReadOnly = true;
                            break;
                        }
                    }
                    volumes.add(volume);
                }

            } catch (IOException ex) {
                ex.printStackTrace();
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException ex) {
                        // ignored
                    }
                }
            }
        }

        // remove volumes that are the same devices
        boolean primaryStorageIncluded = false;
        final File externalStorage = Environment.getExternalStorageDirectory();
        final List<StorageVolume> volumeList = new ArrayList<StorageVolume>();
        for (final Entry<String, List<StorageVolume>> entry : deviceVolumeMap.entrySet()) {
            final List<StorageVolume> volumes = entry.getValue();
            if (volumes.size() == 1) {
                // go ahead and add
                final StorageVolume v = volumes.get(0);
                final boolean isPrimaryStorage = v.file.equals(externalStorage);
                primaryStorageIncluded |= isPrimaryStorage;
                setTypeAndAdd(volumeList, v, includeUsb, isPrimaryStorage);
                continue;
            }
            final int volumesLength = volumes.size();
            for (int i = 0; i < volumesLength; i++) {
                final StorageVolume v = volumes.get(i);
                if (v.file.equals(externalStorage)) {
                    primaryStorageIncluded = true;
                    // add as external storage and continue
                    setTypeAndAdd(volumeList, v, includeUsb, true);
                    break;
                }
                // if that was the last one and it's not the default external
                // storage then add it as is
                if (i == volumesLength - 1) {
                    setTypeAndAdd(volumeList, v, includeUsb, false);
                }
            }
        }
        // add primary storage if it was not found
        if (!primaryStorageIncluded) {
            final StorageVolume defaultExternalStorage = new StorageVolume("", externalStorage, "UNKNOWN");
            defaultExternalStorage.mEmulated = Environment.isExternalStorageEmulated();
            defaultExternalStorage.mType =
                    defaultExternalStorage.mEmulated ? StorageVolume.Type.INTERNAL
                            : StorageVolume.Type.EXTERNAL;
            defaultExternalStorage.mRemovable = Environment.isExternalStorageRemovable();
            defaultExternalStorage.mReadOnly =
                    Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
            volumeList.add(0, defaultExternalStorage);
        }
        return volumeList;
    }

    /**
     * Sets {@link StorageVolume.Type}, removable and emulated flags and adds to
     * volumeList
     * 
     * @param volumeList
     *            List to add volume to
     * @param v
     *            volume to add to list
     * @param includeUsb
     *            if false, volume with type {@link StorageVolume.Type#USB} will
     *            not be added
     * @param asFirstItem
     *            if true, adds the volume at the beginning of the volumeList
     */
    private static void setTypeAndAdd(final List<StorageVolume> volumeList,
            final StorageVolume v,
            final boolean includeUsb,
            final boolean asFirstItem) {
        final StorageVolume.Type type = resolveType(v);
        if (includeUsb || type != StorageVolume.Type.USB) {
            v.mType = type;
            if (v.file.equals(Environment.getExternalStorageDirectory())) {
                v.mRemovable = Environment.isExternalStorageRemovable();
            } else {
                v.mRemovable = type != StorageVolume.Type.INTERNAL;
            }
            v.mEmulated = type == StorageVolume.Type.INTERNAL;
            if (asFirstItem) {
                volumeList.add(0, v);
            } else {
                volumeList.add(v);
            }
        }
    }

    /**
     * Resolved {@link StorageVolume} type
     * 
     * @param v
     *            {@link StorageVolume} to resolve type for
     * @return {@link StorageVolume} type
     */
    private static StorageVolume.Type resolveType(final StorageVolume v) {
        if (v.file.equals(Environment.getExternalStorageDirectory())
                && Environment.isExternalStorageEmulated()) {
            return StorageVolume.Type.INTERNAL;
        } else if (containsIgnoreCase(v.file.getAbsolutePath(), "usb")) {
            return StorageVolume.Type.USB;
        } else {
            return StorageVolume.Type.EXTERNAL;
        }
    }

    /**
     * Checks whether the array contains object
     * 
     * @param array
     *            Array to check
     * @param object
     *            Object to find
     * @return true, if the given array contains the object
     */
    private static <T> boolean arrayContains(T[] array, T object) {
        for (final T item : array) {
            if (item.equals(object)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Checks whether the path contains one of the directories
     * 
     * For example, if path is /one/two, it returns true input is "one" or
     * "two". Will return false if the input is one of "one/two", "/one" or
     * "/two"
     * 
     * @param path
     *            path to check for a directory
     * @param dirs
     *            directories to find
     * @return true, if the path contains one of the directories
     */
    private static boolean pathContainsDir(final String path, final String[] dirs) {
        final StringTokenizer tokens = new StringTokenizer(path, File.separator);
        while (tokens.hasMoreElements()) {
            final String next = tokens.nextToken();
            for (final String dir : dirs) {
                if (next.equals(dir)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Checks ifString contains a search String irrespective of case, handling.
     * Case-insensitivity is defined as by
     * {@link String#equalsIgnoreCase(String)}.
     * 
     * @param str
     *            the String to check, may be null
     * @param searchStr
     *            the String to find, may be null
     * @return true if the String contains the search String irrespective of
     *         case or false if not or {@code null} string input
     */
    public static boolean containsIgnoreCase(final String str, final String searchStr) {
        if (str == null || searchStr == null) {
            return false;
        }
        final int len = searchStr.length();
        final int max = str.length() - len;
        for (int i = 0; i <= max; i++) {
            if (str.regionMatches(true, i, searchStr, 0, len)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Represents storage volume information
     */
    public static final class StorageVolume {

        /**
         * Represents {@link StorageVolume} type
         */
        public enum Type {
            /**
             * Device built-in internal storage. Probably points to
             * {@link Environment#getExternalStorageDirectory()}
             */
            INTERNAL,

            /**
             * External storage. Probably removable, if no other
             * {@link StorageVolume} of type {@link #INTERNAL} is returned by
             * {@link StorageHelper#getStorages(boolean)}, this might be
             * pointing to {@link Environment#getExternalStorageDirectory()}
             */
            EXTERNAL,

            /**
             * Removable usb storage
             */
            USB
        }

        /**
         * Device name
         */
        public final String device;

        /**
         * Points to mount point of this device
         */
        public final File file;

        /**
         * File system of this device
         */
        public final String fileSystem;

        /**
         * if true, the storage is mounted as read-only
         */
        private boolean mReadOnly;

        /**
         * If true, the storage is removable
         */
        private boolean mRemovable;

        /**
         * If true, the storage is emulated
         */
        private boolean mEmulated;

        /**
         * Type of this storage
         */
        private Type mType;

        StorageVolume(String device, File file, String fileSystem) {
            this.device = device;
            this.file = file;
            this.fileSystem = fileSystem;
        }

        /**
         * Returns type of this storage
         * 
         * @return Type of this storage
         */
        public Type getType() {
            return mType;
        }

        /**
         * Returns true if this storage is removable
         * 
         * @return true if this storage is removable
         */
        public boolean isRemovable() {
            return mRemovable;
        }

        /**
         * Returns true if this storage is emulated
         * 
         * @return true if this storage is emulated
         */
        public boolean isEmulated() {
            return mEmulated;
        }

        /**
         * Returns true if this storage is mounted as read-only
         * 
         * @return true if this storage is mounted as read-only
         */
        public boolean isReadOnly() {
            return mReadOnly;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((file == null) ? 0 : file.hashCode());
            return result;
        }

        /**
         * Returns true if the other object is StorageHelper and it's
         * {@link #file} matches this one's
         * 
         * @see Object#equals(Object)
         */
        @Override
        public boolean equals(Object obj) {
            if (obj == this) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final StorageVolume other = (StorageVolume) obj;
            if (file == null) {
                return other.file == null;
            }
            return file.equals(other.file);
        }

        @Override
        public String toString() {
            return file.getAbsolutePath() + (mReadOnly ? " ro " : " rw ") + mType + (mRemovable ? " R " : "")
                    + (mEmulated ? " E " : "") + fileSystem;
        }
    }
}

如果您删除StringUtils.containsIgnoreCase()并仅返回找到的第一个读写执行权限,它是否仍会返回null? - Yaroslav Mytkalyk
如果我删除if条件并返回设备目录路径 - yuva ツ
/storage/emulated/legacy - yuva ツ
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/49132/discussion-between-yuva--and-doctoror-drive - yuva ツ
我按照你给的链接尝试了,但仍然没有得到结果。 - yuva ツ
显示剩余7条评论

4

继承@rijul的答案,这在Marshmallow及以上版本中不起作用:

       //for pre-marshmallow versions
       String path = System.getenv("SECONDARY_STORAGE");

       // For Marshmallow, use getExternalCacheDirs() instead of System.getenv("SECONDARY_STORAGE")
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
           File[] externalCacheDirs = mContext.getExternalCacheDirs();
           for (File file : externalCacheDirs) {
               if (Environment.isExternalStorageRemovable(file)) {
                   // Path is in format /storage.../Android....
                   // Get everything before /Android
                   path = file.getPath().split("/Android")[0];
                   break;
               }
           }
       }


        // Android avd emulator doesn't support this variable name so using other one
        if ((null == path) || (path.length() == 0))
            path = Environment.getExternalStorageDirectory().getAbsolutePath();

3
希望这对您有帮助:
File yourFile = new File(Environment.getExternalStorageDirectory(), "textarabics.txt");

这将给您提供SD卡路径:
File path = Environment.getExternalStorageDirectory();

试试这个:

String pathName = "/mnt/";

或者尝试这样做:
String pathName = "/storage/";

2
它是设备相关的。不同的设备有所不同。 - yuva ツ
2
@FarhanShah,前两个选项对于OP没有显示任何新内容。第二个(/mnt或/storage)可以是挂载点,但不是实际的外部存储器,而且挂载点也会有所不同,因此答案在前两个语句中并不有用,在后两个语句中则是误导性的。 - Yaroslav Mytkalyk
@DoctororDrive,我尽力并真诚地回答了OP的问题,所以没有理由给我负评。 - Farhan Shah
3
如果答案无法回答问题或者是错误的,它可以被踩。在我看来,无论你是否尽力了,这都是一个糟糕的答案。人们会犯错误,投票系统是用来衡量答案有用性的。 - Yaroslav Mytkalyk

2

补充 rijul gupta 的回答:

String strSDCardPath = System.getenv("SECONDARY_STORAGE");

    if ((strSDCardPath == null) || (strSDCardPath.length() == 0)) {
        strSDCardPath = System.getenv("EXTERNAL_SDCARD_STORAGE");
    }

    //If may get a full path that is not the right one, even if we don't have the SD Card there. 
    //We just need the "/mnt/extSdCard/" i.e and check if it's writable
    if(strSDCardPath != null) {
        if (strSDCardPath.contains(":")) {
            strSDCardPath = strSDCardPath.substring(0, strSDCardPath.indexOf(":"));
        }
        File externalFilePath = new File(strSDCardPath);

        if (externalFilePath.exists() && externalFilePath.canWrite()){
            //do what you need here
        }
    }

0

是的,它可能在KITKAT中运行。

在KITKAT以上版本中,它将进入内部存储路径,例如(storage/emulated/0)。

请思考,“Xender应用程序”如何获得写入外部SD卡的权限。

所以,幸运的是,在Android 5.0及更高版本中,有一种新的官方方法让应用程序可以写入外部SD卡。应用程序必须要求用户授予对SD卡上文件夹的写访问权限。他们会打开一个系统文件夹选择器对话框。用户需要导航到特定文件夹并选择它。

有关更多详细信息,请参阅https://metactrl.com/docs/sdcard-on-lollipop/


0
我想以编程方式打开外部存储目录路径以保存文件。我尝试过了,但是无法获取SD卡路径。有什么解决方法吗?
为了将应用程序文件存储在SD卡中,您应该使用Context类中的File[] getExternalFilesDirs(String type)方法。通常,第二个返回的路径将是microSD卡的存储路径(如果有的话)。
在我的电话上,传递null作为参数给getExternalFilesDirs(String type)之后,第二个返回的路径是/storage/sdcard1/Android/data/your.application.package.appname/files。但是路径可能因不同手机和不同Android版本而异。
根据Official Android Guide ,根据您手机的型号和Android OS版本,Environment类中的File getExternalStorageDirectory()File getExternalStoragePublicDirectory(String type)均可返回SD卡目录或内部存储器目录。
可移动存储介质(例如SD卡)或内部(不可移动)存储。根据Google/官方Android文档,内部和外部存储术语与我们的想法相当不同

-2

尝试使用

new File(Environment.getExternalStorageDirectory(),"somefilename");

并且不要忘记添加WRITE_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE权限


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