有没有一种通用的方法来查找外部SD卡的位置?
请不要与外部存储混淆。
Environment.getExternalStorageState()
返回内部SD挂载点的路径,例如/mnt/sdcard
。但是问题是关于外部SD卡的。我怎么才能获得像/mnt/sdcard/external_sd
这样的路径(它可能因设备而异)?
我想我最终会通过文件系统名称过滤mount
命令的输出。但我不确定这种方法是否足够健壮。
有没有一种通用的方法来查找外部SD卡的位置?
请不要与外部存储混淆。
Environment.getExternalStorageState()
返回内部SD挂载点的路径,例如/mnt/sdcard
。但是问题是关于外部SD卡的。我怎么才能获得像/mnt/sdcard/external_sd
这样的路径(它可能因设备而异)?
我想我最终会通过文件系统名称过滤mount
命令的输出。但我不确定这种方法是否足够健壮。
Environment.getExternalStorageState()
返回内部SD卡挂载点的路径,例如“/mnt/sdcard”
不是的,Environment.getExternalStorageDirectory()
指的是设备制造商认为的“外部存储”。在某些设备上,这是可移动介质,例如SD卡;在某些设备上,这是设备内部闪存的一部分。这里,“外部存储”指的是“通过USB大容量存储模式在主机上挂载时可以访问的内容”,至少适用于Android 1.x和2.x。
但问题是关于外部SD卡的。如何获取像“/mnt/sdcard/external_sd”这样的路径(它可能因设备而异)?
Android没有“外部SD卡”的概念,除了上面描述的外部存储。
如果设备制造商选择将外部存储放在内置闪存上并且还有一个SD卡,则需要联系该制造商以确定是否可以使用SD卡(不保证),以及使用它的规则,例如要使用什么路径。
更新
两个最近值得注意的事情:
首先,在Android 4.4+上,您无法写入可移动介质(例如“外部SD卡”),除非该介质上可能由getExternalFilesDirs()
和getExternalCacheDirs()
返回的任何位置。请参见Dave Smith的出色分析,特别是如果您想了解低级细节。
其次,为了避免任何人对可移动介质访问是否属于Android SDK进行争论,在此Dianne Hackborn做了评估:
请记住:在Android 4.4之前,官方的Android平台根本不支持SD卡,除了两种特殊情况:旧的存储布局中外部存储是SD卡(该平台仍然支持),以及在Android 3.0中添加的一个小功能,它会扫描额外的SD卡并将其添加到媒体提供程序中,并使应用程序只能读取其文件(该平台今天仍然支持)。
Android 4.4是该平台实际允许应用程序使用SD卡存储的第一个版本。在此之前的任何访问都是通过私有的、不受支持的API进行的。现在我们在平台上拥有了一个非常丰富的API,允许应用程序以支持的方式使用SD卡,比以前更好地利用它们的应用程序特定存储区域,而无需在应用程序中需要任何权限,并且可以访问SD卡上的任何其他文件,只要它们通过文件选择器即可,也无需任何特殊权限。
我根据在这里找到的一些答案,想出了以下解决方案。
代码:
public class ExternalStorage {
public static final String SD_CARD = "sdCard";
public static final String EXTERNAL_SD_CARD = "externalSdCard";
/**
* @return True if the external storage is available. False otherwise.
*/
public static boolean isAvailable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
public static String getSdCardPath() {
return Environment.getExternalStorageDirectory().getPath() + "/";
}
/**
* @return True if the external storage is writable. False otherwise.
*/
public static boolean isWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
/**
* @return A map of all storage locations available
*/
public static Map<String, File> getAllStorageLocations() {
Map<String, File> map = new HashMap<String, File>(10);
List<String> mMounts = new ArrayList<String>(10);
List<String> mVold = new ArrayList<String>(10);
mMounts.add("/mnt/sdcard");
mVold.add("/mnt/sdcard");
try {
File mountFile = new File("/proc/mounts");
if(mountFile.exists()){
Scanner scanner = new Scanner(mountFile);
while (scanner.hasNext()) {
String line = scanner.nextLine();
if (line.startsWith("/dev/block/vold/")) {
String[] lineElements = line.split(" ");
String element = lineElements[1];
// don't add the default mount path
// it's already in the list.
if (!element.equals("/mnt/sdcard"))
mMounts.add(element);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
try {
File voldFile = new File("/system/etc/vold.fstab");
if(voldFile.exists()){
Scanner scanner = new Scanner(voldFile);
while (scanner.hasNext()) {
String line = scanner.nextLine();
if (line.startsWith("dev_mount")) {
String[] lineElements = line.split(" ");
String element = lineElements[2];
if (element.contains(":"))
element = element.substring(0, element.indexOf(":"));
if (!element.equals("/mnt/sdcard"))
mVold.add(element);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
for (int i = 0; i < mMounts.size(); i++) {
String mount = mMounts.get(i);
if (!mVold.contains(mount))
mMounts.remove(i--);
}
mVold.clear();
List<String> mountHash = new ArrayList<String>(10);
for(String mount : mMounts){
File root = new File(mount);
if (root.exists() && root.isDirectory() && root.canWrite()) {
File[] list = root.listFiles();
String hash = "[";
if(list!=null){
for(File f : list){
hash += f.getName().hashCode()+":"+f.length()+", ";
}
}
hash += "]";
if(!mountHash.contains(hash)){
String key = SD_CARD + "_" + map.size();
if (map.size() == 0) {
key = SD_CARD;
} else if (map.size() == 1) {
key = EXTERNAL_SD_CARD;
}
mountHash.add(hash);
map.put(key, root);
}
}
}
mMounts.clear();
if(map.isEmpty()){
map.put(SD_CARD, Environment.getExternalStorageDirectory());
}
return map;
}
}
用法:
Map<String, File> externalLocations = ExternalStorage.getAllStorageLocations();
File sdCard = externalLocations.get(ExternalStorage.SD_CARD);
File externalSdCard = externalLocations.get(ExternalStorage.EXTERNAL_SD_CARD);
我有一个应用程序,其中使用了ListPreference
,用户需要选择他们希望保存某物的位置。
在该应用中,我扫描了/proc/mounts
和/system/etc/vold.fstab
以获取SD卡的挂载点。我将每个文件中的挂载点存储到两个单独的ArrayList
中。
然后,我将一个列表与另一个列表进行比较,并丢弃两个列表中都不存在的项。这给了我每个SD卡的根路径列表。
从那里,我使用File.exists()
、File.isDirectory()
和File.canWrite()
测试这些路径。如果其中任何一个测试失败,我就会将该路径从列表中丢弃。
剩下的内容,我将其转换为String[]
数组,以便可以通过ListPreference
的values属性使用。
您可以在这里查看代码。
您可以尝试使用支持库函数 ContextCompat.getExternalFilesDirs() :
final File[] appsDir=ContextCompat.getExternalFilesDirs(getActivity(),null);
final ArrayList<File> extRootPaths=new ArrayList<>();
for(final File file : appsDir)
extRootPaths.add(file.getParentFile().getParentFile().getParentFile().getParentFile());
第一个是主要的外部存储器,其余的应该是真正的SD卡路径。
为什么会有多个“ .getParentFile()”呢?因为原始路径需要向上跳到另一个文件夹。
.../Android/data/YOUR_APP_PACKAGE_NAME/files/
编辑:这是我创建的更全面的方法,用于获取SD卡的路径:
/**
* returns a list of all available sd cards paths, or null if not found.
*
* @param includePrimaryExternalStorage set to true if you wish to also include the path of the primary external storage
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static List<String> getSdCardPaths(final Context context, final boolean includePrimaryExternalStorage)
{
final File[] externalCacheDirs=ContextCompat.getExternalCacheDirs(context);
if(externalCacheDirs==null||externalCacheDirs.length==0)
return null;
if(externalCacheDirs.length==1)
{
if(externalCacheDirs[0]==null)
return null;
final String storageState=EnvironmentCompat.getStorageState(externalCacheDirs[0]);
if(!Environment.MEDIA_MOUNTED.equals(storageState))
return null;
if(!includePrimaryExternalStorage&&VERSION.SDK_INT>=VERSION_CODES.HONEYCOMB&&Environment.isExternalStorageEmulated())
return null;
}
final List<String> result=new ArrayList<>();
if(includePrimaryExternalStorage||externalCacheDirs.length==1)
result.add(getRootOfInnerSdCardFolder(externalCacheDirs[0]));
for(int i=1;i<externalCacheDirs.length;++i)
{
final File file=externalCacheDirs[i];
if(file==null)
continue;
final String storageState=EnvironmentCompat.getStorageState(file);
if(Environment.MEDIA_MOUNTED.equals(storageState))
result.add(getRootOfInnerSdCardFolder(externalCacheDirs[i]));
}
if(result.isEmpty())
return null;
return result;
}
/** Given any file/folder inside an sd card, this will return the path of the sd card */
private static String getRootOfInnerSdCardFolder(File file)
{
if(file==null)
return null;
final long totalSpace=file.getTotalSpace();
while(true)
{
final File parentFile=file.getParentFile();
if(parentFile==null||parentFile.getTotalSpace()!=totalSpace||!parentFile.canRead())
return file.getAbsolutePath();
file=parentFile;
}
}
编辑:更好的解决方案在这里:
App
、ContextCompact
、EnvironmentCompact
。 - Antonio为了检索所有的外部存储(无论是SD卡还是内置的不可移动存储),您可以使用以下代码:
final String state = Environment.getExternalStorageState();
if ( Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state) ) { // we can read the External Storage...
//Retrieve the primary External Storage:
final File primaryExternalStorage = Environment.getExternalStorageDirectory();
//Retrieve the External Storages root directory:
final String externalStorageRootDir;
if ( (externalStorageRootDir = primaryExternalStorage.getParent()) == null ) { // no parent...
Log.d(TAG, "External Storage: " + primaryExternalStorage + "\n");
}
else {
final File externalStorageRoot = new File( externalStorageRootDir );
final File[] files = externalStorageRoot.listFiles();
for ( final File file : files ) {
if ( file.isDirectory() && file.canRead() && (file.listFiles().length > 0) ) { // it is a real directory (not a USB drive)...
Log.d(TAG, "External Storage: " + file.getAbsolutePath() + "\n");
}
}
}
}
或者,您可以使用System.getenv("EXTERNAL_STORAGE")来检索主要的外部存储目录(例如"/storage/sdcard0"),并使用System.getenv("SECONDARY_STORAGE")检索所有次要目录的列表(例如"/storage/extSdCard:/storage/UsbDriveA:/storage/UsbDriveB")。请记住,即使在这种情况下,您也可能希望过滤二级目录列表,以排除USB驱动器。
无论如何,请注意使用硬编码路径始终是不好的方法(尤其是当每个制造商都可以随心所欲地更改它时)。
System.getenv("SECONDARY_STORAGE")
也需要一些参考资料,因为它似乎没有文档记录。 - Sz.我也像 Richard 一样使用 /proc/mounts 文件获取可用存储选项列表。
public class StorageUtils {
private static final String TAG = "StorageUtils";
public static class StorageInfo {
public final String path;
public final boolean internal;
public final boolean readonly;
public final int display_number;
StorageInfo(String path, boolean internal, boolean readonly, int display_number) {
this.path = path;
this.internal = internal;
this.readonly = readonly;
this.display_number = display_number;
}
public String getDisplayName() {
StringBuilder res = new StringBuilder();
if (internal) {
res.append("Internal SD card");
} else if (display_number > 1) {
res.append("SD card " + display_number);
} else {
res.append("SD card");
}
if (readonly) {
res.append(" (Read only)");
}
return res.toString();
}
}
public static List<StorageInfo> getStorageList() {
List<StorageInfo> list = new ArrayList<StorageInfo>();
String def_path = Environment.getExternalStorageDirectory().getPath();
boolean def_path_internal = !Environment.isExternalStorageRemovable();
String def_path_state = Environment.getExternalStorageState();
boolean def_path_available = def_path_state.equals(Environment.MEDIA_MOUNTED)
|| def_path_state.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
boolean def_path_readonly = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
BufferedReader buf_reader = null;
try {
HashSet<String> paths = new HashSet<String>();
buf_reader = new BufferedReader(new FileReader("/proc/mounts"));
String line;
int cur_display_number = 1;
Log.d(TAG, "/proc/mounts");
while ((line = buf_reader.readLine()) != null) {
Log.d(TAG, line);
if (line.contains("vfat") || line.contains("/mnt")) {
StringTokenizer tokens = new StringTokenizer(line, " ");
String unused = tokens.nextToken(); //device
String mount_point = tokens.nextToken(); //mount point
if (paths.contains(mount_point)) {
continue;
}
unused = tokens.nextToken(); //file system
List<String> flags = Arrays.asList(tokens.nextToken().split(",")); //flags
boolean readonly = flags.contains("ro");
if (mount_point.equals(def_path)) {
paths.add(def_path);
list.add(0, new StorageInfo(def_path, def_path_internal, readonly, -1));
} else if (line.contains("/dev/block/vold")) {
if (!line.contains("/mnt/secure")
&& !line.contains("/mnt/asec")
&& !line.contains("/mnt/obb")
&& !line.contains("/dev/mapper")
&& !line.contains("tmpfs")) {
paths.add(mount_point);
list.add(new StorageInfo(mount_point, false, readonly, cur_display_number++));
}
}
}
}
if (!paths.contains(def_path) && def_path_available) {
list.add(0, new StorageInfo(def_path, def_path_internal, def_path_readonly, -1));
}
} catch (FileNotFoundException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (buf_reader != null) {
try {
buf_reader.close();
} catch (IOException ex) {}
}
}
return list;
}
}
printStackTrace
呢?我们有android.util.Log.e
啊。 - Martinmount
的方法比读取 /proc
文件系统更加兼容。问题在于 SD 卡的格式不一定是 FAT。此外,卡的挂载点可能因 ROM 而异。此外,可能存在其他几个 VFAT 分区... - borisstrmount
可执行文件更具可移植性,特别是因为启动可执行文件是不鼓励的。 - Chris Stratton我尝试了这个主题中的所有解决方案,但是它们都不能正确地在只有一个可移动外部卡和一个不可移动内部卡的设备上工作。从'mount'命令、'proc/mounts'文件等中获取外部卡的路径是不可能的。
因此,我创建了自己的解决方案(基于Paulo Luan的方案):
String sSDpath = null;
File fileCur = null;
for( String sPathCur : Arrays.asList( "ext_card", "external_sd", "ext_sd", "external", "extSdCard", "externalSdCard")) // external sdcard
{
fileCur = new File( "/mnt/", sPathCur);
if( fileCur.isDirectory() && fileCur.canWrite())
{
sSDpath = fileCur.getAbsolutePath();
break;
}
}
fileCur = null;
if( sSDpath == null) sSDpath = Environment.getExternalStorageDirectory().getAbsolutePath();
如果您查看android.os.Environment
的源代码,您会发现Android在路径上大量依赖环境变量。您可以使用“SECONDARY_STORAGE”环境变量来查找可移动SD卡的路径。
/**
* Get a file using an environmental variable.
*
* @param variableName
* The Environment variable name.
* @param paths
* Any paths to the file if the Environment variable was not found.
* @return the File or {@code null} if the File could not be located.
*/
private static File getDirectory(String variableName, String... paths) {
String path = System.getenv(variableName);
if (!TextUtils.isEmpty(path)) {
if (path.contains(":")) {
for (String _path : path.split(":")) {
File file = new File(_path);
if (file.exists()) {
return file;
}
}
} else {
File file = new File(path);
if (file.exists()) {
return file;
}
}
}
if (paths != null && paths.length > 0) {
for (String _path : paths) {
File file = new File(_path);
if (file.exists()) {
return file;
}
}
}
return null;
}
使用示例:
public static final File REMOVABLE_STORAGE = getDirectory("SECONDARY_STORAGE");
只需简单地使用以下代码:
String primary_sd = System.getenv("EXTERNAL_STORAGE");
if(primary_sd != null)
Log.i("EXTERNAL_STORAGE", primary_sd);
String secondary_sd = System.getenv("SECONDARY_STORAGE");
if(secondary_sd != null)
Log.i("SECONDARY_STORAGE", secondary_sd)
SECONDARY_STORAGE
有几个由冒号(“:”)分隔的路径。这就是为什么我要拆分字符串(请参见我的上面的答案)。 - Jared Rummler
adb shell echo $EXTERNAL_STORAGE
- Charles L.