Android 打开外部存储目录(sdcard)用于存储文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22219312/
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 05:40:40  来源:igfitidea点击:

Android Open External Storage directory(sdcard) for storing file

androidsd-cardandroid-sdcardandroid-external-storage

提问by yuva ツ

I want to open external storage directory path for saving file programatically.I tried but not getting sdcard path. How can i do this?is there any solution for this??

我想以编程方式打开外部存储目录路径以保存文件。我尝试过但没有获得 sdcard 路径。我该怎么做?有什么解决办法吗??

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

or

或者

private File path = new File(Environment.getExternalStorageDirectory() + "");

I tried getting path from above both methods but both are pointing internal memory.

我尝试从上面两种方法获取路径,但都指向内部存储器。

When we open storage memory if sdcard is peresent it will shows like below- enter image description here

当我们打开存储内存时,如果 sdcard 是 peresent 它会显示如下- 在此处输入图片说明

device storage & sd memory card.

设备存储和 SD 存储卡。

I want to get sd memory path through coding. I have given permissions in manifest-

我想通过编码获得 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" />

回答by Rijul Gupta

I had been having the exact same problem!

我一直有完全相同的问题!

To get the internal SD card you can use

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

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

To get the external SD card you can use

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

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

On running the code

在运行代码时

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

works perfectly!

完美运行!

回答by Yaroslav Mytkalyk

The internal storage is referred to as "external storage" in the API.

内部存储在 API 中称为“外部存储”。

As mentioned in the Environmentdocumentation

环境文档中所述

Note: don't be confused by the word "external" here. This directory can better be thought as media/shared storage. It is a filesystem that can hold a relatively large amount of data and that is shared across all applications (does not enforce permissions). Traditionally this is an SD card, but it may also be implemented as built-in storage in a device that is distinct from the protected internal storage and can be mounted as a filesystem on a computer.

注意:不要被这里的“外部”一词混淆。这个目录最好被认为是媒体/共享存储。它是一个文件系统,可以保存相对大量的数据,并在所有应用程序之间共享(不强制执行权限)。传统上这是一张 SD 卡,但它也可以作为设备中的内置存储实现,该设备不同于受保护的内部存储,并且可以作为文件系统安装在计算机上。

To distinguish whether "Environment.getExternalStorageDirectory()" actually returned physically internal or external storage, call Environment.isExternalStorageEmulated(). If it's emulated, than it's internal. On newer devices that have internal storage and sdcard slot Environment.getExternalStorageDirectory() will always return the internal storage. While on older devices that have only sdcard as a media storage option it will always return the sdcard.

要区分“Environment.getExternalStorageDirectory()”实际上返回的是物理内部存储还是外部存储,请调用 Environment.isExternalStorageEmulated()。如果它是模拟的,那么它是内部的。在具有内部存储和 SD 卡插槽的较新设备上, Environment.getExternalStorageDirectory() 将始终返回内部存储。而在只有 sdcard 作为媒体存储选项的旧设备上,它总是会返回 sdcard。

There is no way to retrieve all storages using current Android API.

无法使用当前的 Android API 检索所有存储。

I've created a helper based on Vitaliy Polchuk's method in the answer below

我在下面的答案中根据 Vitaliy Polchuk 的方法创建了一个助手

How can I get the list of mounted external storage of android device

如何获取已安装的 android 设备外部存储列表

NOTE: starting KitKat secondary storage is accessible only as READ-ONLY, you may want to check for writability using the following method

注意:启动 KitKat 二级存储只能作为 READ-ONLY 访问,您可能需要使用以下方法检查可写性

/**
 * 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 class

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

回答by Shubham

taking @rijul's answer forward, it doesn't work in marshmallow and above versions:

继续@rijul 的回答,它在棉花糖及以上版本中不起作用:

       //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();

回答by Farhan Shah

hope it's worked for you:

希望它对你有用:

File yourFile = new File(Environment.getExternalStorageDirectory(), "textarabics.txt");

This will give u sdcard path:

这会给你 sdcard 路径:

File path = Environment.getExternalStorageDirectory();

Try this:

尝试这个:

String pathName = "/mnt/";

or try this:

或者试试这个:

String pathName = "/storage/";

回答by Alexandre

Complementing rijul guptaanswer:

补充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
        }
    }

回答by AnV

I want to open external storage directory path for saving file programatically.I tried but not getting sdcard path. How can i do this?is there any solution for this??

我想以编程方式打开外部存储目录路径以保存文件。我尝试过但没有获得 sdcard 路径。我该怎么做?有什么解决办法吗??

To store your app files in SD card, you shoulduse File[] getExternalFilesDirs (String type)method in Contextclass. Generally, second returned path would be the storage path for microSD card (if any).

要将您的应用程序文件存储在 SD 卡中,您应该File[] getExternalFilesDirs (String type)Context类中使用方法。通常,第二个返回路径是 microSD 卡(如果有)的存储路径。

On my phone, second path returned was /storage/sdcard1/Android/data/your.application.package.appname/filesafter passing nullas argument to getExternalFilesDirs (String type). But path may vary on different phones, different Android versions.

在我的手机上,返回的第二条路径是作为参数/storage/sdcard1/Android/data/your.application.package.appname/files传递nullgetExternalFilesDirs (String type). 但是路径可能会因不同的手机、不同的 Android 版本而异。

Both File getExternalStorageDirectory ()and File getExternalStoragePublicDirectory (String type)in Environmentclass may return SD card directory or internal memory directory depending on your phone's model and Android OS version.

双方File getExternalStorageDirectory ()File getExternalStoragePublicDirectory (String type)Environment类可能返回SD卡目录,或者根据您的手机型号和Android操作系统版本的内部存储器目录。

Because According to Official Android Guideexternal storage can be

因为根据官方Android指南外部存储可以

removable storage media (such as an SD card) or an internal (non-removable) storage.

可移动存储介质(例如 SD 卡)或内部(不可移动)存储。

The Internal and External Storage terminology according to Google/official Android docs is quite differentfrom what we think.

根据谷歌/官方 Android 文档的内部和外部存储术语与我们的想法大不相同

回答by Muhammed Haris

yes, it may work in KITKAT.

是的,它可能适用于 KITKAT。

above KITKAT+ it will go to internal storage:paths like(storage/emulated/0).

在 KITKAT+ 上方,它将转到内部存储:路径如(存储/模拟/0)。

please think, how "Xender app" give permission to write in to external sd card.

请想一想,“Xender 应用程序”如何授予写入外部 SD 卡的权限。

So, Fortunately in Android 5.0 and later there is a new official way for apps to write to the external SD card. Apps must ask the user to grant write access to a folder on the SD card. They open a system folder chooser dialog. The user need to navigate into that specific folder and select it.

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

for more details, please refer https://metactrl.com/docs/sdcard-on-lollipop/

详情请参考https://metactrl.com/docs/sdcard-on-lollipop/

回答by Hafizh Herdi

Try using

尝试使用

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

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

And don't forget to add WRITE_EXTERNAL STORAGE and READ_EXTERNAL STORAGE permissions

并且不要忘记添加 WRITE_EXTERNAL STORAGE 和 READ_EXTERNAL STORAGE 权限