获取连接到 Android 设备的所有存储设备的路径列表
Get the list of paths of all the Storage Devices connected to an Android device
我想获取连接到 Android 设备的所有存储设备的列表。
例如 - 内部存储(存储所有文件夹,如下载、DCIM 等)、SD 卡和 OTG 设备。
我知道有很多 Whosebug 帖子讨论了这个话题,但是其中 none 可以达到我上面所说的目的。
我可以通过调用 Environment.getExternalStorageDirectory().getPath()
获取内部存储,其中 return 内部存储的路径。
任何关于这方面的帮助将不胜感激,因为没有标准的 AFAIK 可以用来检索所有连接的存储设备的列表。
此外,许多解决方案不适用于不同的设备和 Android 版本。
我有一些运气
ContextCompat.getExternalFilesDirs
这样可以找到外部驱动器上的应用程序文件夹。我还没有找到比这更好的解决方案。
在我的用例中,我使用 Environment.DIRECTORY_MOVIES
但如果您需要,还有其他定义,包括通用 DIRECTORY_DOCUMENTS
你可以创建一个class EnvironmentSDCardCheck
package com.example.storagecheck;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.support.v4.content.ContextCompat;
import android.support.v4.os.EnvironmentCompat;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class EnvironmentSDCardCheck {
private static final String TAG = "EnvironmentSDCardCheck";
public final static String TYPE_PRIMARY = "primär";
public final static String TYPE_INTERNAL = "intern";
public final static String TYPE_SD = "MicroSD";
public final static String TYPE_USB = "USB";
public final static String TYPE_UNKNOWN = "unbekannt";
public final static String WRITE_NONE = "none";
public final static String WRITE_READONLY = "readonly";
public final static String WRITE_APPONLY = "apponly";
public final static String WRITE_FULL = "readwrite";
private static Device[] devices, externalstorage, storage;
private static BroadcastReceiver receiver;
private static boolean useReceiver = true;
private static String userDir;
public static Device[] getDevices(Context context) {
if (devices == null) initDevices(context);
return devices;
}
public static Device[] getExternalStorage(Context context) {
if (devices == null) initDevices(context);
return externalstorage;
}
public static Device[] getStorage(Context context) {
if (devices == null) initDevices(context);
return storage;
}
public static IntentFilter getRescanIntentFilter() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL);
filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
filter.addAction(Intent.ACTION_MEDIA_REMOVED);
filter.addAction(Intent.ACTION_MEDIA_SHARED);
filter.addDataScheme("file");
return filter;
}
public static void setUseReceiver(Context context, boolean use) {
if (use && receiver == null) {
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Storage " + intent.getAction() + "-" + intent.getData());
initDevices(context);
}
};
context.registerReceiver(receiver, getRescanIntentFilter());
} else if (!use && receiver != null) {
context.unregisterReceiver(receiver);
receiver = null;
}
useReceiver = use;
}
public static void initDevices(Context context) {
if (userDir == null) userDir = "/Android/data/" + context.getPackageName();
setUseReceiver(context, useReceiver);
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Class c = sm.getClass();
Object[] vols;
try {
Method m = c.getMethod("getVolumeList", null);
vols = (Object[]) m.invoke(sm, null); // android.os.Storage.StorageVolume
Device[] temp = new Device[vols.length];
for (int i = 0; i < vols.length; i++) temp[i] = new Device(vols[i]);
Device primary = null;
for (Device d : temp) if (d.mPrimary) primary = d;
if (primary == null) for (Device d : temp)
if (!d.mRemovable) {
d.mPrimary = true;
primary = d;
break;
}
if (primary == null) {
primary = temp[0];
primary.mPrimary = true;
}
File[] files = ContextCompat.getExternalFilesDirs(context, null);
File[] caches = ContextCompat.getExternalCacheDirs(context);
for (Device d : temp) {
if (files != null) for (File f : files)
if (f != null && f.getAbsolutePath().startsWith(d.getAbsolutePath()))
d.mFiles = f;
if (caches != null) for (File f : caches)
if (f != null && f.getAbsolutePath().startsWith(d.getAbsolutePath()))
d.mCache = f;
}
ArrayList<Device> tempDev = new ArrayList<Device>(10);
ArrayList<Device> tempStor = new ArrayList<Device>(10);
ArrayList<Device> tempExt = new ArrayList<Device>(10);
for (Device d : temp) {
tempDev.add(d);
if (d.isAvailable()) {
tempExt.add(d);
tempStor.add(d);
}
}
Device internal = new Device(context);
tempStor.add(0, internal); // bei Storage-Alternativen immer
if (!primary.mEmulated) tempDev.add(0, internal); // bei Devices nur wenn zusätzlich
devices = tempDev.toArray(new Device[tempDev.size()]);
storage = tempStor.toArray(new Device[tempStor.size()]);
externalstorage = tempExt.toArray(new Device[tempExt.size()]);
} catch (Exception e) {
// Fallback auf normale Android-Funktionen
}
}
public static class Device extends File {
String mUserLabel, mUuid, mState, mWriteState, mType;
boolean mPrimary, mRemovable, mEmulated, mAllowMassStorage;
long mMaxFileSize;
File mFiles, mCache;
Device(Context context) {
super(Environment.getDataDirectory().getAbsolutePath());
mState = Environment.MEDIA_MOUNTED;
mFiles = context.getFilesDir();
mCache = context.getCacheDir();
mType = TYPE_INTERNAL;
mWriteState = WRITE_APPONLY;
}
@SuppressWarnings("NullArgumentToVariableArgMethod")
Device(Object storage) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
super((String) storage.getClass().getMethod("getPath", null).invoke(storage, null));
for (Method m : storage.getClass().getMethods()) {
if (m.getName().equals("getUserLabel") && m.getParameterTypes().length == 0 && m.getReturnType() == String.class)
mUserLabel = (String) m.invoke(storage, null); // ab Android 4.4
if (m.getName().equals("getUuid") && m.getParameterTypes().length == 0 && m.getReturnType() == String.class)
mUuid = (String) m.invoke(storage, null); // ab Android 4.4
if (m.getName().equals("getState") && m.getParameterTypes().length == 0 && m.getReturnType() == String.class)
mState = (String) m.invoke(storage, null); // ab Android 4.4
if (m.getName().equals("isRemovable") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
mRemovable = (Boolean) m.invoke(storage, null); // ab Android 4.0
if (m.getName().equals("isPrimary") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
mPrimary = (Boolean) m.invoke(storage, null); // ab Android 4.2
if (m.getName().equals("isEmulated") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
mEmulated = (Boolean) m.invoke(storage, null); // ab Android 4.0
if (m.getName().equals("allowMassStorage") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
mAllowMassStorage = (Boolean) m.invoke(storage, null); // ab Android 4.0
if (m.getName().equals("getMaxFileSize") && m.getParameterTypes().length == 0 && m.getReturnType() == long.class)
mMaxFileSize = (Long) m.invoke(storage, null); // ab Android 4.0
// getDescription (ab 4.1 mit context) liefert keine sinnvollen Werte
// getPathFile (ab 4.2) liefert keine sinnvollen Werte
// getMtpReserveSpace (ab 4.0) für diese Zwecke unwichtig
// getStorageId (ab 4.0) für diese Zwecke unwichtig
}
if (mState == null) mState = getState();
if (mPrimary)
mType = TYPE_PRIMARY;
else {
String n = getAbsolutePath().toLowerCase();
if (n.indexOf("sd") > 0)
mType = TYPE_SD;
else if (n.indexOf("usb") > 0)
mType = TYPE_USB;
else
mType = TYPE_UNKNOWN + " " + getAbsolutePath();
}
}
public String getType() {
return mType;
}
public String getAccess() {
if (mWriteState == null) {
try {
mWriteState = WRITE_NONE;
File[] root = listFiles();
if (root == null || root.length == 0)
throw new IOException("root empty/unreadable");
mWriteState = WRITE_READONLY;
File t = File.createTempFile("jow", null, getFilesDir());
//noinspection ResultOfMethodCallIgnored
t.delete();
mWriteState = WRITE_APPONLY;
t = File.createTempFile("jow", null, this);
//noinspection ResultOfMethodCallIgnored
t.delete();
mWriteState = WRITE_FULL;
} catch (IOException ignore) {
Log.v(TAG, "test " + getAbsolutePath() + " ->" + mWriteState + "<- " + ignore.getMessage());
}
}
return mWriteState;
}
public boolean isAvailable() {
String s = getState();
return (
Environment.MEDIA_MOUNTED.equals(s) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(s)
);
// MEDIA_SHARED: als USB freigegeben; bitte Handy auf MTP umstellen
}
public String getState() {
if (mRemovable || mState == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
// Android 5.0? Da gibts was neues
mState = Environment.getExternalStorageState(this);
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
// Android 4.4? Dann dort nachfragen
mState = Environment.getStorageState(this);
else if (canRead() && getTotalSpace() > 0)
// lesbar und Größe vorhanden => gibt es
mState = Environment.MEDIA_MOUNTED;
else if (mState == null || Environment.MEDIA_MOUNTED.equals(mState))
// nicht lesbar, keine Größe aber noch MOUNTED || oder ungesetzt => UNKNOWN
mState = EnvironmentCompat.MEDIA_UNKNOWN;
}
return mState;
}
public File getFilesDir() {
if (mFiles == null) {
mFiles = new File(this, userDir + "/files");
if (!mFiles.isDirectory())
//noinspection ResultOfMethodCallIgnored
mFiles.mkdirs();
}
return mFiles;
}
public File getCacheDir() {
if (mCache == null) {
mCache = new File(this, userDir + "/cache");
if (!mCache.isDirectory())
//noinspection ResultOfMethodCallIgnored
mCache.mkdirs();
}
return mCache;
}
public boolean isPrimary() {
return mPrimary;
}
public boolean isRemovable() {
return mRemovable;
}
public boolean isEmulated() {
return mEmulated;
}
public boolean isAllowMassStorage() {
return mAllowMassStorage;
}
public long getMaxFileSize() {
return mMaxFileSize;
}
public String getUserLabel() {
return mUserLabel;
}
public String getUuid() {
return mUuid;
}
}
}
然后您可以使用它来检查 sd 卡或 Usb 或未知设备当前是否已与设备连接
This way you can get the connected sd card, usb, etc.
private boolean checkSdCardPermission() {
boolean flag = false;
try {
EnvironmentSDCard.Device[] devices = EnvironmentSDCard.getExternalStorage(MainActivity.this);
for (EnvironmentSDCard.Device d : devices) {
if (d.getType().equals(EnvironmentSDCard.TYPE_SD) || d.getType().contains(EnvironmentSDCard.TYPE_UNKNOWN) || d.getType().contains(EnvironmentSDCard.TYPE_USB)) {
flag = d.isAvailable();
if (flag)
break;
}
}
} catch (Exception e) {
}
return flag;
}
自 API 级 9 开始 android.os.storage.StorageManager
。调用 getStorageVolumes()
(自 API 级别 24 起可用)以获取存储卷列表。正如文档所说:
Return the list of shared/external storage volumes available to the current user. This includes both the primary shared storage device and any attached external volumes including SD cards and USB drives.
结果是List<StorageVolume>
。现在,看看 android.os.storage.StorageVolume
:
Information about a shared/external storage volume for a specific user.
例如,您可以通过调用 getDescription()
获得卷的 user-visible 描述。请参阅 createAccessIntent()
如何获得访问权限。
这是对@Sagar 关于从 /proc
获取坐骑的回答的补充。请注意使用 /proc/self/mountinfo 而不是 /proc/mountinfo 或 /proc/mounts。您可以在 man 5 procfs
中阅读有关 /proc/self/mountinfo
格式的更多信息。虽然以下代码在技术上解析文件,但在主线程上 运行 是安全的(因为 /proc
是 in-memory 文件系统)。
private static final int SANE_SIZE_LIMIT = 200 * 1024 * 1024;
// some hashmap for mapping long values to objects
// personally I am using HPPC maps, the HashMap class is for simplicity
public final HashMap<String> mountMap = new HashMap<>();
public void parse() {
mountMap.clear();
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
parseMounts(decoder, true);
}
private int measure(FileChannel fc) throws IOException {
final ByteBuffer buffer = ByteBuffer.allocate(1024 * 4);
int totalRead = 0, lastRead;
do {
buffer.clear();
lastRead = fc.read(buffer);
totalRead += lastRead;
if (totalRead > SANE_SIZE_LIMIT) {
throw new IOException("/proc/ file appears to be too big!!");
}
} while (lastRead != -1);
fc.position(0);
return totalRead;
}
private void parseMounts(CharsetDecoder d, boolean force) {
File file = new File("/proc/self/mountinfo");
int mode = ParcelFileDescriptor.MODE_READ_ONLY;
try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, mode));
FileChannel fc = new FileInputStream(pfd.getFileDescriptor()).getChannel()) {
// Measure size of file before reading from it.
// Virtual files in /proc/ appear to be zero-sized (because
// their contents are dynamic), but we want to attempt
// reading it in single read() call to avoid inconsistencies
final int totalRead = measure(fc);
try (FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
Reader r = Channels.newReader(fis.getChannel(), d, totalRead);
Scanner scanner = new Scanner(r)) {
while (scanner.hasNextLine()) {
scanner.nextInt();
scanner.nextInt();
final String[] mm = scanner.next().split(":");
final int major = Integer.parseInt(mm[0]);
final int minor = Integer.parseInt(mm[1]);
final long dev_t = makedev(major, minor);
final String source = scanner.next();
// ignore bind-mounts for now
if ("/".equals(source)) {
final String location = scanner.next();
// skip optional parts
scanner.skip("(.+ -)");
// type of file system (such as ext4)
// most useful filesystems can be distinguished by type
// but "fuse" is problematic (because most Android
// distributions implement dynamic permissions on
// external SD card via custom FUSE filesystem).
// To make matters worse, that SD card FUSE filesystem is
// often mounted in several places at once. The code below
// will throw away duplicate mounts by placing them in
// HashMap, keyed by uniqie filesystem type ID,
// but you can do it more cleverly be checking whether
// a mountpoint directory is accessible (via File#list).
// You can throw away rest of useless filesystems (such as
// /mnt/secure/asec) by permission checks and blacklisting
// well-known paths.
final String fsType = scanner.next().intern();
final String subject = scanner.next().intern();
String created = location + subject + fsType;
String prev = mountMap.put(dev_t, created);
if (prev != null) {
created.next = prev;
}
}
scanner.nextLine();
}
return;
} catch (NumberFormatException | NoSuchElementException nse) {
// oops.. either a new row type was introduced (not a big deal)
// or the file changed below our feet (because someone has mounted
// something). Let's retry once more
parseMounts(d, false);
} catch (IOException e) {
throw new WrappedIOException(e);
}
}
您可以在 this answer 中找到更多有用的信息(例如无用文件系统的通用路径)。请注意,/proc/mounts 和 /proc/mountinfo 的格式不同,后者是在前者之后引入的,以在不破坏向后兼容性的情况下改进其格式。
上面的代码不是金弹药——它并没有真正告诉你任何关于单个文件系统的信息,只是告诉你它们的路径和文件系统名称。你可以有理由相信,"vfat" 和 "ext4" 是有用的文件系统,而 "procfs" 是无用的,但像 "fuse" 这样的东西将保持神秘。您可以通过使用 android.os.storage.StorageManager
获取更多 user-friendly 文件系统名称(如 "SD Card")来增加上面代码的输出(当它们可用时(通过挂载路径匹配))。您还可以使用 StatFs 获取分区上的可用空闲 space — 无用的虚拟文件系统通常会 return 零空闲 space 和零可用 space 查询时。最后,如果您愿意,可以在决定是否向用户显示文件系统时考虑文件系统挂载选项。例如。 ro
对比 rw
,— read-only 文件系统挂载通常对您的用户来说用处不大。
当我向人们解释这个方法时,他们通常关心它的稳健性……它能在一些随机的垃圾电话上工作吗?它会在未来的 OS 版本中保持可用吗?这是我的看法:这个方法仍然比许多 reflection-based 建议的要好,— 在最坏的情况下,从 /proc/ 文件中读取将 return 你 IOException。它不会使您的应用程序崩溃或导致不可预知的行为,例如某些 reflection-based 黑客行为。
/proc
文件系统是官方 Linux API,由 Linux 内核开发人员维护。不可能通过指定不同的内核构建选项来删除它(例如,它是 OS 内核的强制性部分)。它已经存在多年,并且比大多数 Android API 保留了更好的向后兼容性。特别是 /proc/self/mountinfo 是 10 多年前创建的,将在除最古老的大多数现有 Android 版本中可用。
Android 开发者不正式支持 Linux-specific APIs。但他们也不会特意打破它们。 post-Lollipop Android 中最近的一些 SELinux 更改限制了对 /proc/
中某些文件的访问 — 因为它们允许应用程序暗中监视其他应用程序。这些更改专门使 /proc/self
可访问,因为 /proc/self 旨在仅公开应用程序自己的信息(包括有关文件系统的信息,可供应用程序使用)。
如果 Google 从 Linux 过渡到 Fuchensa 或其他一些本土 BSD 分支,/proc/ 和其他 Linux-specifc API 可能会崩溃。我关心的?不是真的。
我想获取连接到 Android 设备的所有存储设备的列表。
例如 - 内部存储(存储所有文件夹,如下载、DCIM 等)、SD 卡和 OTG 设备。
我知道有很多 Whosebug 帖子讨论了这个话题,但是其中 none 可以达到我上面所说的目的。
我可以通过调用 Environment.getExternalStorageDirectory().getPath()
获取内部存储,其中 return 内部存储的路径。
任何关于这方面的帮助将不胜感激,因为没有标准的 AFAIK 可以用来检索所有连接的存储设备的列表。
此外,许多解决方案不适用于不同的设备和 Android 版本。
我有一些运气
ContextCompat.getExternalFilesDirs
这样可以找到外部驱动器上的应用程序文件夹。我还没有找到比这更好的解决方案。
在我的用例中,我使用 Environment.DIRECTORY_MOVIES
但如果您需要,还有其他定义,包括通用 DIRECTORY_DOCUMENTS
你可以创建一个class EnvironmentSDCardCheck
package com.example.storagecheck;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.support.v4.content.ContextCompat;
import android.support.v4.os.EnvironmentCompat;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class EnvironmentSDCardCheck {
private static final String TAG = "EnvironmentSDCardCheck";
public final static String TYPE_PRIMARY = "primär";
public final static String TYPE_INTERNAL = "intern";
public final static String TYPE_SD = "MicroSD";
public final static String TYPE_USB = "USB";
public final static String TYPE_UNKNOWN = "unbekannt";
public final static String WRITE_NONE = "none";
public final static String WRITE_READONLY = "readonly";
public final static String WRITE_APPONLY = "apponly";
public final static String WRITE_FULL = "readwrite";
private static Device[] devices, externalstorage, storage;
private static BroadcastReceiver receiver;
private static boolean useReceiver = true;
private static String userDir;
public static Device[] getDevices(Context context) {
if (devices == null) initDevices(context);
return devices;
}
public static Device[] getExternalStorage(Context context) {
if (devices == null) initDevices(context);
return externalstorage;
}
public static Device[] getStorage(Context context) {
if (devices == null) initDevices(context);
return storage;
}
public static IntentFilter getRescanIntentFilter() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL);
filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
filter.addAction(Intent.ACTION_MEDIA_REMOVED);
filter.addAction(Intent.ACTION_MEDIA_SHARED);
filter.addDataScheme("file");
return filter;
}
public static void setUseReceiver(Context context, boolean use) {
if (use && receiver == null) {
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Storage " + intent.getAction() + "-" + intent.getData());
initDevices(context);
}
};
context.registerReceiver(receiver, getRescanIntentFilter());
} else if (!use && receiver != null) {
context.unregisterReceiver(receiver);
receiver = null;
}
useReceiver = use;
}
public static void initDevices(Context context) {
if (userDir == null) userDir = "/Android/data/" + context.getPackageName();
setUseReceiver(context, useReceiver);
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Class c = sm.getClass();
Object[] vols;
try {
Method m = c.getMethod("getVolumeList", null);
vols = (Object[]) m.invoke(sm, null); // android.os.Storage.StorageVolume
Device[] temp = new Device[vols.length];
for (int i = 0; i < vols.length; i++) temp[i] = new Device(vols[i]);
Device primary = null;
for (Device d : temp) if (d.mPrimary) primary = d;
if (primary == null) for (Device d : temp)
if (!d.mRemovable) {
d.mPrimary = true;
primary = d;
break;
}
if (primary == null) {
primary = temp[0];
primary.mPrimary = true;
}
File[] files = ContextCompat.getExternalFilesDirs(context, null);
File[] caches = ContextCompat.getExternalCacheDirs(context);
for (Device d : temp) {
if (files != null) for (File f : files)
if (f != null && f.getAbsolutePath().startsWith(d.getAbsolutePath()))
d.mFiles = f;
if (caches != null) for (File f : caches)
if (f != null && f.getAbsolutePath().startsWith(d.getAbsolutePath()))
d.mCache = f;
}
ArrayList<Device> tempDev = new ArrayList<Device>(10);
ArrayList<Device> tempStor = new ArrayList<Device>(10);
ArrayList<Device> tempExt = new ArrayList<Device>(10);
for (Device d : temp) {
tempDev.add(d);
if (d.isAvailable()) {
tempExt.add(d);
tempStor.add(d);
}
}
Device internal = new Device(context);
tempStor.add(0, internal); // bei Storage-Alternativen immer
if (!primary.mEmulated) tempDev.add(0, internal); // bei Devices nur wenn zusätzlich
devices = tempDev.toArray(new Device[tempDev.size()]);
storage = tempStor.toArray(new Device[tempStor.size()]);
externalstorage = tempExt.toArray(new Device[tempExt.size()]);
} catch (Exception e) {
// Fallback auf normale Android-Funktionen
}
}
public static class Device extends File {
String mUserLabel, mUuid, mState, mWriteState, mType;
boolean mPrimary, mRemovable, mEmulated, mAllowMassStorage;
long mMaxFileSize;
File mFiles, mCache;
Device(Context context) {
super(Environment.getDataDirectory().getAbsolutePath());
mState = Environment.MEDIA_MOUNTED;
mFiles = context.getFilesDir();
mCache = context.getCacheDir();
mType = TYPE_INTERNAL;
mWriteState = WRITE_APPONLY;
}
@SuppressWarnings("NullArgumentToVariableArgMethod")
Device(Object storage) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
super((String) storage.getClass().getMethod("getPath", null).invoke(storage, null));
for (Method m : storage.getClass().getMethods()) {
if (m.getName().equals("getUserLabel") && m.getParameterTypes().length == 0 && m.getReturnType() == String.class)
mUserLabel = (String) m.invoke(storage, null); // ab Android 4.4
if (m.getName().equals("getUuid") && m.getParameterTypes().length == 0 && m.getReturnType() == String.class)
mUuid = (String) m.invoke(storage, null); // ab Android 4.4
if (m.getName().equals("getState") && m.getParameterTypes().length == 0 && m.getReturnType() == String.class)
mState = (String) m.invoke(storage, null); // ab Android 4.4
if (m.getName().equals("isRemovable") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
mRemovable = (Boolean) m.invoke(storage, null); // ab Android 4.0
if (m.getName().equals("isPrimary") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
mPrimary = (Boolean) m.invoke(storage, null); // ab Android 4.2
if (m.getName().equals("isEmulated") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
mEmulated = (Boolean) m.invoke(storage, null); // ab Android 4.0
if (m.getName().equals("allowMassStorage") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
mAllowMassStorage = (Boolean) m.invoke(storage, null); // ab Android 4.0
if (m.getName().equals("getMaxFileSize") && m.getParameterTypes().length == 0 && m.getReturnType() == long.class)
mMaxFileSize = (Long) m.invoke(storage, null); // ab Android 4.0
// getDescription (ab 4.1 mit context) liefert keine sinnvollen Werte
// getPathFile (ab 4.2) liefert keine sinnvollen Werte
// getMtpReserveSpace (ab 4.0) für diese Zwecke unwichtig
// getStorageId (ab 4.0) für diese Zwecke unwichtig
}
if (mState == null) mState = getState();
if (mPrimary)
mType = TYPE_PRIMARY;
else {
String n = getAbsolutePath().toLowerCase();
if (n.indexOf("sd") > 0)
mType = TYPE_SD;
else if (n.indexOf("usb") > 0)
mType = TYPE_USB;
else
mType = TYPE_UNKNOWN + " " + getAbsolutePath();
}
}
public String getType() {
return mType;
}
public String getAccess() {
if (mWriteState == null) {
try {
mWriteState = WRITE_NONE;
File[] root = listFiles();
if (root == null || root.length == 0)
throw new IOException("root empty/unreadable");
mWriteState = WRITE_READONLY;
File t = File.createTempFile("jow", null, getFilesDir());
//noinspection ResultOfMethodCallIgnored
t.delete();
mWriteState = WRITE_APPONLY;
t = File.createTempFile("jow", null, this);
//noinspection ResultOfMethodCallIgnored
t.delete();
mWriteState = WRITE_FULL;
} catch (IOException ignore) {
Log.v(TAG, "test " + getAbsolutePath() + " ->" + mWriteState + "<- " + ignore.getMessage());
}
}
return mWriteState;
}
public boolean isAvailable() {
String s = getState();
return (
Environment.MEDIA_MOUNTED.equals(s) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(s)
);
// MEDIA_SHARED: als USB freigegeben; bitte Handy auf MTP umstellen
}
public String getState() {
if (mRemovable || mState == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
// Android 5.0? Da gibts was neues
mState = Environment.getExternalStorageState(this);
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
// Android 4.4? Dann dort nachfragen
mState = Environment.getStorageState(this);
else if (canRead() && getTotalSpace() > 0)
// lesbar und Größe vorhanden => gibt es
mState = Environment.MEDIA_MOUNTED;
else if (mState == null || Environment.MEDIA_MOUNTED.equals(mState))
// nicht lesbar, keine Größe aber noch MOUNTED || oder ungesetzt => UNKNOWN
mState = EnvironmentCompat.MEDIA_UNKNOWN;
}
return mState;
}
public File getFilesDir() {
if (mFiles == null) {
mFiles = new File(this, userDir + "/files");
if (!mFiles.isDirectory())
//noinspection ResultOfMethodCallIgnored
mFiles.mkdirs();
}
return mFiles;
}
public File getCacheDir() {
if (mCache == null) {
mCache = new File(this, userDir + "/cache");
if (!mCache.isDirectory())
//noinspection ResultOfMethodCallIgnored
mCache.mkdirs();
}
return mCache;
}
public boolean isPrimary() {
return mPrimary;
}
public boolean isRemovable() {
return mRemovable;
}
public boolean isEmulated() {
return mEmulated;
}
public boolean isAllowMassStorage() {
return mAllowMassStorage;
}
public long getMaxFileSize() {
return mMaxFileSize;
}
public String getUserLabel() {
return mUserLabel;
}
public String getUuid() {
return mUuid;
}
}
}
然后您可以使用它来检查 sd 卡或 Usb 或未知设备当前是否已与设备连接
This way you can get the connected sd card, usb, etc.
private boolean checkSdCardPermission() {
boolean flag = false;
try {
EnvironmentSDCard.Device[] devices = EnvironmentSDCard.getExternalStorage(MainActivity.this);
for (EnvironmentSDCard.Device d : devices) {
if (d.getType().equals(EnvironmentSDCard.TYPE_SD) || d.getType().contains(EnvironmentSDCard.TYPE_UNKNOWN) || d.getType().contains(EnvironmentSDCard.TYPE_USB)) {
flag = d.isAvailable();
if (flag)
break;
}
}
} catch (Exception e) {
}
return flag;
}
自 API 级 9 开始 android.os.storage.StorageManager
。调用 getStorageVolumes()
(自 API 级别 24 起可用)以获取存储卷列表。正如文档所说:
Return the list of shared/external storage volumes available to the current user. This includes both the primary shared storage device and any attached external volumes including SD cards and USB drives.
结果是List<StorageVolume>
。现在,看看 android.os.storage.StorageVolume
:
Information about a shared/external storage volume for a specific user.
例如,您可以通过调用 getDescription()
获得卷的 user-visible 描述。请参阅 createAccessIntent()
如何获得访问权限。
这是对@Sagar 关于从 /proc
获取坐骑的回答的补充。请注意使用 /proc/self/mountinfo 而不是 /proc/mountinfo 或 /proc/mounts。您可以在 man 5 procfs
中阅读有关 /proc/self/mountinfo
格式的更多信息。虽然以下代码在技术上解析文件,但在主线程上 运行 是安全的(因为 /proc
是 in-memory 文件系统)。
private static final int SANE_SIZE_LIMIT = 200 * 1024 * 1024;
// some hashmap for mapping long values to objects
// personally I am using HPPC maps, the HashMap class is for simplicity
public final HashMap<String> mountMap = new HashMap<>();
public void parse() {
mountMap.clear();
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
parseMounts(decoder, true);
}
private int measure(FileChannel fc) throws IOException {
final ByteBuffer buffer = ByteBuffer.allocate(1024 * 4);
int totalRead = 0, lastRead;
do {
buffer.clear();
lastRead = fc.read(buffer);
totalRead += lastRead;
if (totalRead > SANE_SIZE_LIMIT) {
throw new IOException("/proc/ file appears to be too big!!");
}
} while (lastRead != -1);
fc.position(0);
return totalRead;
}
private void parseMounts(CharsetDecoder d, boolean force) {
File file = new File("/proc/self/mountinfo");
int mode = ParcelFileDescriptor.MODE_READ_ONLY;
try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, mode));
FileChannel fc = new FileInputStream(pfd.getFileDescriptor()).getChannel()) {
// Measure size of file before reading from it.
// Virtual files in /proc/ appear to be zero-sized (because
// their contents are dynamic), but we want to attempt
// reading it in single read() call to avoid inconsistencies
final int totalRead = measure(fc);
try (FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
Reader r = Channels.newReader(fis.getChannel(), d, totalRead);
Scanner scanner = new Scanner(r)) {
while (scanner.hasNextLine()) {
scanner.nextInt();
scanner.nextInt();
final String[] mm = scanner.next().split(":");
final int major = Integer.parseInt(mm[0]);
final int minor = Integer.parseInt(mm[1]);
final long dev_t = makedev(major, minor);
final String source = scanner.next();
// ignore bind-mounts for now
if ("/".equals(source)) {
final String location = scanner.next();
// skip optional parts
scanner.skip("(.+ -)");
// type of file system (such as ext4)
// most useful filesystems can be distinguished by type
// but "fuse" is problematic (because most Android
// distributions implement dynamic permissions on
// external SD card via custom FUSE filesystem).
// To make matters worse, that SD card FUSE filesystem is
// often mounted in several places at once. The code below
// will throw away duplicate mounts by placing them in
// HashMap, keyed by uniqie filesystem type ID,
// but you can do it more cleverly be checking whether
// a mountpoint directory is accessible (via File#list).
// You can throw away rest of useless filesystems (such as
// /mnt/secure/asec) by permission checks and blacklisting
// well-known paths.
final String fsType = scanner.next().intern();
final String subject = scanner.next().intern();
String created = location + subject + fsType;
String prev = mountMap.put(dev_t, created);
if (prev != null) {
created.next = prev;
}
}
scanner.nextLine();
}
return;
} catch (NumberFormatException | NoSuchElementException nse) {
// oops.. either a new row type was introduced (not a big deal)
// or the file changed below our feet (because someone has mounted
// something). Let's retry once more
parseMounts(d, false);
} catch (IOException e) {
throw new WrappedIOException(e);
}
}
您可以在 this answer 中找到更多有用的信息(例如无用文件系统的通用路径)。请注意,/proc/mounts 和 /proc/mountinfo 的格式不同,后者是在前者之后引入的,以在不破坏向后兼容性的情况下改进其格式。
上面的代码不是金弹药——它并没有真正告诉你任何关于单个文件系统的信息,只是告诉你它们的路径和文件系统名称。你可以有理由相信,"vfat" 和 "ext4" 是有用的文件系统,而 "procfs" 是无用的,但像 "fuse" 这样的东西将保持神秘。您可以通过使用 android.os.storage.StorageManager
获取更多 user-friendly 文件系统名称(如 "SD Card")来增加上面代码的输出(当它们可用时(通过挂载路径匹配))。您还可以使用 StatFs 获取分区上的可用空闲 space — 无用的虚拟文件系统通常会 return 零空闲 space 和零可用 space 查询时。最后,如果您愿意,可以在决定是否向用户显示文件系统时考虑文件系统挂载选项。例如。 ro
对比 rw
,— read-only 文件系统挂载通常对您的用户来说用处不大。
当我向人们解释这个方法时,他们通常关心它的稳健性……它能在一些随机的垃圾电话上工作吗?它会在未来的 OS 版本中保持可用吗?这是我的看法:这个方法仍然比许多 reflection-based 建议的要好,— 在最坏的情况下,从 /proc/ 文件中读取将 return 你 IOException。它不会使您的应用程序崩溃或导致不可预知的行为,例如某些 reflection-based 黑客行为。
/proc
文件系统是官方 Linux API,由 Linux 内核开发人员维护。不可能通过指定不同的内核构建选项来删除它(例如,它是 OS 内核的强制性部分)。它已经存在多年,并且比大多数 Android API 保留了更好的向后兼容性。特别是 /proc/self/mountinfo 是 10 多年前创建的,将在除最古老的大多数现有 Android 版本中可用。
Android 开发者不正式支持 Linux-specific APIs。但他们也不会特意打破它们。 post-Lollipop Android 中最近的一些 SELinux 更改限制了对 /proc/
中某些文件的访问 — 因为它们允许应用程序暗中监视其他应用程序。这些更改专门使 /proc/self
可访问,因为 /proc/self 旨在仅公开应用程序自己的信息(包括有关文件系统的信息,可供应用程序使用)。
如果 Google 从 Linux 过渡到 Fuchensa 或其他一些本土 BSD 分支,/proc/ 和其他 Linux-specifc API 可能会崩溃。我关心的?不是真的。