简体   繁体   English

获取连接到Android设备的所有存储设备的路径列表

[英]Get the list of paths of all the Storage Devices connected to an Android device

I want to get the list of all Storage Devices which are connected to the Android device. 我想获取连接到Android设备的所有存储设备的列表。

For eg- Internal Storage(Storage in which all the folders such as Downloads, DCIM etc are present), SD Card and OTG device. 例如 - 内部存储(存储所有文件夹,如下载,DCIM等),SD卡和OTG设备。

I Know there are a lot of StackOverflow posts which discuss this topic but none of them could serve my purpose as stated above. 我知道有很多StackOverflow帖子讨论了这个主题,但没有一个可以满足我的目的,如上所述。

I am able to get the Internal Storage by calling Environment.getExternalStorageDirectory().getPath() which return the path to the Internal Storage. 我可以通过调用Environment.getExternalStorageDirectory().getPath()来获取内部存储,它返回内部存储的路径。

Any help on this would be really grateful as there is no standard AFAIK using which the list of all the connected Storage devices can be retrieved. 对此的任何帮助都将非常感激,因为没有标准的AFAIK可以使用它来检索所有连接的存储设备的列表。

Also, many solutions do not work on different devices and Android version. 此外,许多解决方案不适用于不同的设备和Android版本。

I have had some luck with 我有一些运气

ContextCompat.getExternalFilesDirs

This allows the application folders on external drives to be found. 这允许找到外部驱动器上的应用程序文件夹。 I haven't yet found a working better solution than this. 我还没有找到比这更好的解决方案。

In my use case I am using Environment.DIRECTORY_MOVIES But if you need, there are other definitions, including the generic DIRECTORY_DOCUMENTS 在我的用例中,我使用的是Environment.DIRECTORY_MOVIES但是如果需要,还有其他定义,包括通用DIRECTORY_DOCUMENTS

This is an addition to @Sagar's answer about getting mounts from /proc . 这是@ Sagar关于从/proc获取坐标的补充。 Note the use of /proc/self/mountinfo instead of /proc/mountinfo or /proc/mounts. 注意使用/ proc / self / mountinfo而不是/ proc / mountinfo或/ proc / mounts。 You can read more about format of /proc/self/mountinfo in man 5 procfs . 您可以在man 5 procfs阅读有关/proc/self/mountinfo格式的更多信息。 While the following code technically parses files, it is safe to run on the main thread (because /proc is in-memory filesystem). 虽然以下代码在技术上解析文件,但在主线程上运行是安全的(因为/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);
    }
}

You can find more useful information (such as common path to useless filesystem), in this answer . 这个答案中 ,您可以找到更多有用的信息(例如无用文件系统的通用路径)。 Note, that formats of /proc/mounts and /proc/mountinfo are different , later was introduced after former to improve upon it's format without breaking backwards compatibility. 注意,/ proc / mounts和/ proc / mountinfo的格式是不同的 ,后来是在前者之后引入的,以改进它的格式而不会破坏向后兼容性。

The code above is not golden bullet — it does not really tell you anything about individual filesystems, just their paths and filesystem name. 上面的代码不是金色的子弹 - 它并没有真正告诉你任何关于单个文件系统的信息,只是它们的路径和文件系统名称。 You can be reasonable confident, that "vfat" and "ext4" are useful filesystems, and "procfs" is useless, but something like "fuse" is going to remain mysterious. 您可以合理地确信,“vfat”和“ext4”是有用的文件系统,而“procfs”是无用的,但像“fuse”这样的东西仍将是神秘的。 You can augment output of code above by using android.os.storage.StorageManager to get more user-friendly filesystem names (like "SD Card") when they are available (match by mount paths). 您可以通过使用android.os.storage.StorageManager来增加上面代码的输出,以便在它们可用时获得更多用户友好的文件系统名称(如“SD卡”)(通过装载路径匹配)。 You can also use StatFs to obtain available free space on partition — useless virtual filesystems typically will return zero free space and zero available space when queried. 您还可以使用StatF来获取分区上的可用空间 - 无用的虚拟文件系统通常会在查询时返回零可用空间和零可用空间。 Finally, if you are so inclined, you can consider filesystem mount options when deciding whether to show filesystem to user. 最后,如果您如此倾向,可以在决定是否向用户显示文件系统时考虑文件系统挂载选项。 Eg ro vs rw , — read-only filesystem mounts typically will be a lot less useful for your users. 例如ro vs rw , - 只读文件系统挂载通常对用户来说不那么有用。


When I explain this method to people, they are often concerned with it's robustness… Will it work on some random junkphone? 当我向人们解释这种方法时,他们经常关注它的稳健性......它会在一些随机的垃圾电话上运行吗? Will it remain available in future OS versions? 它将在未来的OS版本中保持可用吗? Here is my take on it: this method is still better than many reflection-based advises, — in the worst case, reading from /proc/ file will return you IOException. 这是我的看法:这个方法仍然比许多基于反射的建议更好, - 在最坏的情况下,从/ proc / file读取将返回IOException。 It will not crash your app or result in unpredictable behavior like some reflection-based hacks. 它不会崩溃你的应用程序或导致不可预测的行为,如一些基于反射的黑客攻击。

/proc filesystem is official Linux API, maintained by Linux kernel developers. /proc filesystem是由Linux内核开发人员维护的官方Linux API。 It is not possible to remove it by specifying different kernel build options (eg it is a mandatory part of OS kernel). 通过指定不同的内核构建选项(例如,它是OS内核的必需部分)无法将其删除。 It has been available for many years and retains better backwards compatibility than most Android APIs. 它已经使用多年,并且保留了比大多数Android API更好的向后兼容性。 In particular /proc/self/mountinfo was created over 10 years ago and will be available in most existing Android versions except most ancient. 特别是/ proc / self / mountinfo是在10年前创建的,除了大多数古代版本之外,它将在大多数现有的Android版本中提供。

Android developers do not officially support Linux-specific APIs. Android开发人员不正式支持特定于Linux的API。 But they don't go out of their way to break them either. 但他们也不会竭尽全力打破他们。 Some of recent SELinux changes in post-Lollipop Android have restricted access to some files in /proc/ , — because they allowed applications to covertly spy on other applications. 后Lollipop Android中最近的一些SELinux更改限制了对/proc/某些文件的访问,因为它们允许应用程序隐蔽地监视其他应用程序。 Those changes specifically kept /proc/self accessible, because /proc/self is designed to expose only applications own information (including information about file systems, available to the application). 这些更改专门保持/proc/self可访问,因为/ proc / self旨在仅公开应用程序自己的信息(包括有关文件系统的信息,可供应用程序使用)。

If Google ever transitions from Linux to Fuchensa or some other homegrown BSD fork, the /proc/ and other Linux-specifc APIs will probably break. 如果谷歌从Linux转换到Fuchensa或其他一些本土的BSD分支,/ proc /和其他特定于Linux的API可能会破解。 Do I care? 我关心的? Not really. 并不是的。

You can create one class EnvironmentSDCardCheck 您可以创建一个类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;
        }
    }
}

and then you can use it to check sd card or Usb or unknown is presently connected or not with device 然后你可以用它来检查SD卡或Usb或者目前是否与设备连接

This way you can get the connected sd card, usb, etc. 这样你就可以获得连接的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;
}

Since API level 9 there is android.os.storage.StorageManager . 从API级别9开始,有android.os.storage.StorageManager Call getStorageVolumes() (available since API level 24) to get a list of storage volumes. 调用getStorageVolumes() (自API级别24起可用)以获取存储卷列表。 As the doc puts it: 正如文件所说:

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. 这包括主共享存储设备和任何连接的外部卷,包括SD卡和USB驱动器。

The result is List<StorageVolume> . 结果是List<StorageVolume> Now, take a look at android.os.storage.StorageVolume : 现在,看一下android.os.storage.StorageVolume

Information about a shared/external storage volume for a specific user. 有关特定用户的共享/外部存储卷的信息。

You can for example get a user-visible description of the volume by calling getDescription() . 例如,您可以通过调用getDescription()获取卷的用户可见描述。 See createAccessIntent() how to get access. 请参阅createAccessIntent()如何获取访问权限。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM