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