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

Posted

技术标签:

【中文标题】Android 打开外部存储目录(sdcard) 用于存储文件【英文标题】:Android Open External Storage directory(sdcard) for storing file 【发布时间】:2014-04-08 18:48:19 【问题描述】:

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

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

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

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

当我们打开存储内存时,如果 sdcard 存在,它将显示如下 -

设备存储和 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 权限 同时添加 READ_EXTERNAL STORAGE 权限 @BlackTiger : 仍然没有得到 sdcard 路径 String root = Environment.getExternalStorageDirectory().toString(); 如果你做的一切都是正确的,它也会返回路径。请发布您的完整代码 你给的路径和我试过的一样。我已经贴出来了。我想要sdcard目录路径。n你说的是内部内存/设备内存 【参考方案1】:

我也遇到了同样的问题!

要获取您可以使用的内部 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"

完美运行!

【讨论】:

区分主要外部存储和辅助外部存储也很好......谢谢...... 感谢您的回答。我为它添加了一个补充,但它不适合 cmets。在我的答案上设置你的名字作为原始答案。 System.getenv("SECONDARY_STORAGE") 在安卓模拟器中尝试时返回 null @bikrampandit :这很可能是因为您没有与模拟器连接的“辅助”存储设备。另外,我注意到新版本的 Android 将这两种记忆结合在一起用于某些应用程序,但我怀疑会是这样。 不适用于搭载 Android 7 的 Galaxy 7 Edge。System.getenv("SECONDARY_STORAGE") 返回 null。但 SDCard 已插入。【参考方案2】:

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

如Environment 文档中所述

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

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

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

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

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

注意:启动 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 StorageVolumes Returned list always
     * includes a @link StorageVolume for
     * @link Environment#getExternalStorageDirectory()
     * 
     * @param includeUsb
     *            if true, will include USB storages
     * @return list of mounted @link StorageVolumes
     */
    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 吗? 如果我删除条件返回设备目录路径 我的意思是不要删除第一个 if 但 StringUtils-one if。你能给我一个“adb shell ls /storage”的输出吗? /storage/emulated/legacy 我的方法确实适用于我尝试过的所有设备,但我想这不是最好的方法。一件好事我想出来了,因为我打算在生产中使用它。在您设备的第三方应用程序中找到的 sdcard 的真实位置是什么(请提供设备名称)?如果外部方法在 /storage/emulated/ 目录上,我的方法有效。 “adb shell ls /storage/emulated/”什么时候打印?【参考方案3】:

采用@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();

【讨论】:

【参考方案4】:

希望它对你有用:

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

这将为您提供 sdcard 路径:

File path = Environment.getExternalStorageDirectory();

试试这个:

String pathName = "/mnt/";

或者试试这个:

String pathName = "/storage/";

【讨论】:

取决于设备。不同设备不同 @FarhanShah 前两个选项没有为 OP 显示任何新内容。后两个(/mnt 或 /storage)可以是挂载点,但不是实际的外部存储,而且挂载点无论如何都会有所不同,因此答案对前两个语句没有用,而对后两个语句会产生误导。 @DoctororDrive 我尽力而为,真诚地回答了 OP,所以没有理由拒绝投票.. @FarhanShah 如果没有回答问题或错误,答案可能会被否决。在我看来,无论你是否尽力而为——这都是一个糟糕的答案。人们会犯错误,并创建投票系统来衡量答案的有用性。【参考方案5】:

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

【讨论】:

【参考方案6】:

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

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

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

Environment 类中的 File getExternalStorageDirectory ()File getExternalStoragePublicDirectory (String type) 都可能返回 SD 卡目录或内存目录,具体取决于您的手机型号和 Android 操作系统版本。

因为根据Official Android Guide外置存储可以

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

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

【讨论】:

【参考方案7】:

是的,它可以在 KITKAT 中工作。

在 KITKAT+ 之上,它将进入内部存储:路径如 (storage/emulated/0)。

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

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

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

【讨论】:

【参考方案8】:

尝试使用

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

别忘了添加 WRITE_EXTERNAL STORAGE 和 READ_EXTERNAL STORAGE 权限

【讨论】:

以上是关于Android 打开外部存储目录(sdcard) 用于存储文件的主要内容,如果未能解决你的问题,请参考以下文章

android Q sdcard权限详解

Android 逆向Android 系统文件分析 ( 外部存储设备文件 | sbin 命令程序目录 | dev 字符设备目录 )

Android 逆向Android 系统文件分析 ( 外部存储设备文件 | sbin 命令程序目录 | dev 字符设备目录 )

android-数据存储之外部file存储(sdcard)

android-get nullpointerexception 尝试访问外部存储(sdcard)上的视频

Android 外部存储权限分析