有没有一种通用的方法来找到外置SD卡的位置?
请不要与外部存储混淆。
Environment.getExternalStorageState()返回内部SD挂载点的路径,例如/mnt/sdcard。但问题是关于外部SD的。我如何获得类似/mnt/sdcard/external_sd的路径(它可能因设备而异)?
我想我将以根据文件系统名称过滤mount命令的输出作为结束。但我不确定这种方式是否足够稳健。
有没有一种通用的方法来找到外置SD卡的位置?
请不要与外部存储混淆。
Environment.getExternalStorageState()返回内部SD挂载点的路径,例如/mnt/sdcard。但问题是关于外部SD的。我如何获得类似/mnt/sdcard/external_sd的路径(它可能因设备而异)?
我想我将以根据文件系统名称过滤mount命令的输出作为结束。但我不确定这种方式是否足够稳健。
当前回答
像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;
}
}
其他回答
这个解决方案处理System.getenv(“SECONDARY_STORAGE”)在Marshmallow中没有用的问题。
已测试和正在进行:
Samsung Galaxy Tab 2 (Android 4.1.1 - Stock) Samsung Galaxy Note 8.0 (Android 4.2.2 - Stock) Samsung Galaxy S4 (Android 4.4 - Stock) Samsung Galaxy S4 (Android 5.1.1 - Cyanogenmod) Samsung Galaxy Tab A (Android 6.0.1 - Stock) /** * Returns all available external SD-Card roots in the system. * * @return paths to all available external SD-Card roots in the system. */ public static String[] getStorageDirectories() { String [] storageDirectories; String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { List<String> results = new ArrayList<String>(); File[] externalDirs = applicationContext.getExternalFilesDirs(null); for (File file : externalDirs) { String path = file.getPath().split("/Android")[0]; if((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Environment.isExternalStorageRemovable(file)) || rawSecondaryStoragesStr != null && rawSecondaryStoragesStr.contains(path)){ results.add(path); } } storageDirectories = results.toArray(new String[0]); }else{ final Set<String> rv = new HashSet<String>(); if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) { final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator); Collections.addAll(rv, rawSecondaryStorages); } storageDirectories = rv.toArray(new String[rv.size()]); } return storageDirectories; }
有没有一种通用的方法来找到外置SD卡的位置?
通用方式,如果你指的是官方方式;是的,有一个。
在API级别19中,即在Android版本4.4 Kitkat中,他们在上下文类中添加了File[] getExternalFilesDirs(字符串类型),允许应用程序在micro SD卡中存储数据/文件。
Android 4.4是第一个允许应用程序使用SD卡存储的平台。API级别19之前对SD卡的任何访问都是通过私有的、不受支持的API。
getExternalFilesDirs(String类型)返回所有共享/外部存储设备上特定于应用程序目录的绝对路径。这意味着,它将返回内部和外部内存的路径。通常,第二个返回路径将是microSD卡的存储路径(如果有的话)。
但请注意,
共享存储可能并不总是可用的,因为可移动媒体可以 被用户弹出。媒体状态可以检查使用 getExternalStorageState(文件)。 这些文件没有安全措施。例如,任何 持有WRITE_EXTERNAL_STORAGE的应用程序可以写入这些文件。
根据谷歌/官方Android文档,内部和外部存储术语与我们想象的大不相同。
像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;
}
}
它适用于所有外部设备,但确保只获得外部设备文件夹名,然后你需要从给定的位置使用文件类获取文件。
public static List<String> getExternalMounts() {
final List<String> out = new ArrayList<>();
String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
String s = "";
try {
final Process process = new ProcessBuilder().command("mount")
.redirectErrorStream(true).start();
process.waitFor();
final InputStream is = process.getInputStream();
final byte[] buffer = new byte[1024];
while (is.read(buffer) != -1) {
s = s + new String(buffer);
}
is.close();
} catch (final Exception e) {
e.printStackTrace();
}
// parse output
final String[] lines = s.split("\n");
for (String line : lines) {
if (!line.toLowerCase(Locale.US).contains("asec")) {
if (line.matches(reg)) {
String[] parts = line.split(" ");
for (String part : parts) {
if (part.startsWith("/"))
if (!part.toLowerCase(Locale.US).contains("vold"))
out.add(part);
}
}
}
}
return out;
}
调用:
List<String> list=getExternalMounts();
if(list.size()>0)
{
String[] arr=list.get(0).split("/");
int size=0;
if(arr!=null && arr.length>0) {
size= arr.length - 1;
}
File parentDir=new File("/storage/"+arr[size]);
if(parentDir.listFiles()!=null){
File parent[] = parentDir.listFiles();
for (int i = 0; i < parent.length; i++) {
// get file path as parent[i].getAbsolutePath());
}
}
}
访问外部存储
为了读写外部存储上的文件,你的应用程序必须获得READ_EXTERNAL_STORAGE或WRITE_EXTERNAL_STORAGE系统权限。例如:
<manifest ...>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
...
</manifest>
你可以尝试使用支持库函数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;
}
}
编辑:这里有更好的解决方案:
https://stackoverflow.com/a/27197248/878126