【问题标题】:Get the list of paths of all the Storage Devices connected to an Android device获取连接到 Android 设备的所有存储设备的路径列表
【发布时间】:2018-07-02 01:35:35
【问题描述】:

我想获取连接到 Android 设备的所有存储设备的列表。

例如 - 内部存储(其中存在所有文件夹,如下载、DCIM 等的存储)、SD 卡和 OTG 设备。

我知道有很多 StackOverflow 帖子都在讨论这个话题,但没有一个可以达到我上述目的。

我可以通过调用Environment.getExternalStorageDirectory().getPath() 来获取内部存储,它会返回内部存储的路径。

对此的任何帮助将不胜感激,因为没有标准的 AFAIK 可以使用它来检索所有连接的存储设备的列表。

此外,许多解决方案不适用于不同的设备和 Android 版本。

【问题讨论】:

    标签: java android android-sdcard sd-card usb-otg


    【解决方案1】:

    我运气不错

    ContextCompat.getExternalFilesDirs
    

    这允许找到外部驱动器上的应用程序文件夹。我还没有找到比这更好的解决方案。

    在我的用例中,我使用的是Environment.DIRECTORY_MOVIES,但如果您需要,还有其他定义,包括通用的DIRECTORY_DOCUMENTS

    【讨论】:

    • 感谢您的回答。我应该通过什么来代替第二个参数ContextCompat.getExternalFilesDir(this," ? ")
    【解决方案2】:

    你可以创建一个类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 或未知当前是否与设备连接

    这样就可以得到连接的sd卡、usb等了

    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;
    }
    

    【讨论】:

    • 谢谢你会尝试回来。
    • 那是很多反射调用......如果设备供应商没有在Environment中正确注册存储磁盘,他们也不太可能在StorageManager中注册它,所以我不认为这种方法是富有成效的。您是否有设备示例,它实际上对旧 Android 版本产生了影响?
    • 感谢您的回答。您的答案完美无缺。将等待其他一些答案,如果没有答案将接受它作为正确答案并奖励赏金。
    • 好的,没问题,您也可以尝试不同的解决方案 :)
    • 谢谢。已授予赏金并接受您的答案作为正确答案。再次感谢。
    【解决方案3】:

    从 API 级别 9 开始就有 android.os.storage.StorageManager。调用 getStorageVolumes()(从 API 级别 24 开始可用)以获取存储卷列表。正如文档所说:

    返回当前用户可用的共享/外部存储卷列表。这包括主共享存储设备和任何附加的外部卷,包括 SD 卡和 USB 驱动器。

    结果是List&lt;StorageVolume&gt;。现在,看看android.os.storage.StorageVolume

    有关特定用户的共享/外部存储卷的信息。

    例如,您可以通过调用getDescription() 获取用户可见的卷描述。请参阅createAccessIntent() 如何获取访问权限。

    【讨论】:

    • 此方法是否适用于旧版 Android 公开?
    • API 级别 9 表示 Android 2.3 (Gingerbread),于 2010 年 12 月 6 日(七年多前)发布
    • StorageManager 是在 API 9 中引入的,但 getStorageVolumes 直到 API 24 才成为公共 API。此外,它没有列出任何设备供应商未明确列出的内容。换句话说,如果您的设备没有在公共 API 中列出某些存储位置,那么您又回到了原点。
    【解决方案4】:

    这是@Sagar 关于从/proc 获取坐骑的答案的补充。请注意使用 /proc/self/mountinfo 而不是 /proc/mountinfo 或 /proc/mounts。您可以在man 5 procfs 中阅读有关/proc/self/mountinfo 格式的更多信息。虽然以下代码在技术上解析文件,但在主线程上运行是安全的(因为 /proc 是内存中的文件系统)。

    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 来增加上述代码的输出,以便在可用时(通过挂载路径匹配)获得更用户友好的文件系统名称(如“SD 卡”)。您还可以使用StatFs 来获取分区上的可用空间——无用的虚拟文件系统通常会在查询时返回零可用空间和零可用空间。最后,如果您愿意,可以在决定是否向用户显示文件系统时考虑文件系统挂载选项。例如。 rorw,- 只读文件系统挂载通常对您的用户没有多大用处。


    当我向人们解释这种方法时,他们经常关心它的鲁棒性……它会在一些随机的垃圾电话上工作吗?它会在未来的操作系统版本中保持可用吗?这是我的看法:这种方法仍然比许多基于反射的建议要好——在最坏的情况下,从 /proc/ 文件中读取会返回 IOException。它不会使您的应用崩溃或导致不可预知的行为,例如一些基于反射的黑客攻击。

    /proc 文件系统是官方的 Linux API,由 Linux 内核开发人员维护。无法通过指定不同的内核构建选项来删除它(例如,它是操作系统内核的必需部分)。它已经可用多年,并且比大多数 Android API 保持更好的向后兼容性。特别是 /proc/self/mountinfo 是在 10 多年前创建的,并且将在大多数现有 Android 版本中可用,但最古老的版本除外。

    Android 开发人员并未正式支持 Linux 特定的 API。但他们也不会不遗余力地破坏它们。后棒棒糖 Android 中最近的一些 SELinux 更改限制了对 /proc/ 中某些文件的访问,因为它们允许应用程序秘密监视其他应用程序。这些更改专门使 /proc/self 可访问,因为 /proc/self 旨在仅公开应用程序自己的信息(包括有关文件系统的信息,可供应用程序使用)。

    如果 Google 从 Linux 过渡到 Fuchensa 或其他一些本土 BSD 分支,/proc/ 和其他 Linux 特定 API 可能会中断。我关心的?不是真的。

    【讨论】:

    • 试过你的方法,但我在 Android Studio 中遇到了一些错误。有些是Mount 类未找到,throwawayBuffer.get() 显示为错误,pfd.getFileDescriptor() 显示为错误,scanner.hasNextLine() 显示为错误,等等错误显示。你能看看和帮助吗?
    • @Rahulrr2602 我删除了对throwawayBufferMount 的引用——它们只是一些ByteBuffer,一个简单的POJO,分别用于存储挂载点信息。这并不是一个真正完整的即用型库——我希望您能够根据自己的环境调整代码。
    猜你喜欢
    • 2017-02-12
    • 2018-04-03
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-13
    相关资源
    最近更新 更多