Получите список путей всех устройств хранения, подключенных к устройству Android.

Я хочу получить список всех устройств хранения, подключенных к устройству Android.

Например, внутреннее хранилище (хранилище, в котором присутствуют все папки, такие как «Загрузки», DCIM и т. д.), SD-карта и устройство OTG.

Я знаю, что существует множество сообщений StackOverflow, в которых обсуждается эта тема, но ни одно из них не может служить моей цели, как указано выше.

Я могу получить внутреннее хранилище, вызвав Environment.getExternalStorageDirectory().getPath(), который возвращает путь к внутреннему хранилищу.

Любая помощь в этом будет очень благодарна, так как нет стандартного AFAIK, с помощью которого можно получить список всех подключенных устройств хранения.

Также многие решения не работают на разных устройствах и версиях Android.


person Rahulrr2602    schedule 23.01.2018    source источник


Ответы (4)


Вы можете создать один класс 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;
}
person Amjad Khan    schedule 30.03.2018
comment
Спасибо постараюсь вернуться. - person Rahulrr2602; 30.03.2018
comment
Это очень много вызовов рефлексии… Если производитель устройства неправильно пропишет накопитель в Environment, он вряд ли зарегистрирует его и в StorageManager, так что я не думаю, что этот метод продуктивный. У вас есть примеры устройств, когда это действительно отличалось от старых версий Android? - person user1643723; 30.03.2018
comment
Спасибо за ответ. Ваш ответ работает отлично. Буду ждать других ответов, если ни один ответ не сработает, примет его как правильный ответ и присудит награду. - person Rahulrr2602; 30.03.2018
comment
Хорошо, нет проблем, вы также можете попробовать другое решение :) - person Amjad Khan; 31.03.2018
comment
Спасибо. Присудили награду и приняли ваш ответ как правильный ответ. Еще раз спасибо. - person Rahulrr2602; 31.03.2018

мне повезло с

ContextCompat.getExternalFilesDirs

Это позволяет найти папки приложений на внешних дисках. Я еще не нашел рабочего лучшего решения, чем это.

В моем случае использования я использую Environment.DIRECTORY_MOVIES Но если вам нужно, есть и другие определения, включая общее DIRECTORY_DOCUMENTS

person mksteve    schedule 27.03.2018
comment
Спасибо за ответ. Что я должен передать вместо второго аргумента ContextCompat.getExternalFilesDir(this," ? ") - person Rahulrr2602; 27.03.2018

Начиная с уровня API 9 существует android.os.storage.StorageManager. Вызовите getStorageVolumes() (доступен с уровня API 24), чтобы получить список томов хранилища. Как говорит документ:

Возвращает список общих/внешних томов хранения, доступных текущему пользователю. Сюда входят как основное совместно используемое запоминающее устройство, так и любые подключенные внешние тома, включая SD-карты и USB-накопители.

Результат List<StorageVolume>. Теперь взгляните на android.os.storage.StorageVolume:

Информация об общем/внешнем хранилище для конкретного пользователя.

Например, вы можете получить видимое пользователю описание тома, вызвав getDescription(). См. createAccessIntent(), как получить доступ.

person Community    schedule 30.03.2018
comment
Доступен ли этот метод для более старых версий Android публично? - person Rahulrr2602; 30.03.2018
comment
Уровень API 9 означает Android 2.3 (Gingerbread), выпущенный 6 декабря 2010 г. (более семи лет назад). - person ; 30.03.2018
comment
StorageManager был представлен в API 9, но getStorageVolumes не стал общедоступным API до API 24. Кроме того, в нем нет ничего, что не было бы указано производителем устройства. Другими словами, если ваше устройство не указывает какое-либо место хранения в общедоступном API, вы вернулись к исходной точке. - person user1643723; 30.03.2018

Это дополнение к ответу @Sagar о получении маунтов от /proc. Обратите внимание на использование /proc/self/mountinfo вместо /proc/mountinfo или /proc/mounts. Вы можете прочитать больше о формате /proc/self/mountinfo в man 5 procfs. Хотя следующий код технически анализирует файлы, его безопасно запускать в основном потоке (поскольку /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);
    }
}

Вы можете найти более полезную информацию (например, общий путь к бесполезной файловой системе) в этом ответе. Обратите внимание, что форматы /proc/mounts и /proc/mountinfo отличаются, позже были введены после первого, чтобы улучшить его формат без нарушения обратной совместимости.

Приведенный выше код не является золотой пулей — он ничего не говорит вам об отдельных файловых системах, только их пути и имя файловой системы. Вы можете быть уверены, что "vfat" и "ext4" - полезные файловые системы, а "procfs" бесполезна, но что-то вроде "fuse" останется загадкой. Вы можете расширить вывод приведенного выше кода, используя android.os.storage.StorageManager, чтобы получить более удобные для пользователя имена файловых систем (например, «SD-карта»), когда они доступны (соответствуют путям монтирования). Вы также можете использовать StatFs для получения свободного места на разделе — бесполезно виртуальные файловые системы обычно возвращают нулевое свободное пространство и нулевое доступное пространство при запросе. Наконец, если вы так склонны, вы можете рассмотреть параметры монтирования файловой системы при принятии решения о том, показывать ли файловую систему пользователю. Например. ro против rw — монтирование файловой системы только для чтения обычно будет намного менее полезным для ваших пользователей.


Когда я объясняю этот метод людям, они часто интересуются его надежностью... Будет ли он работать на каком-нибудь дрянном телефоне? Будет ли он доступен в будущих версиях ОС? Вот мой взгляд на это: этот метод все же лучше, чем многие советы, основанные на отражении, — в худшем случае чтение из /proc/file вернет вам IOException. Это не приведет к сбою вашего приложения и не приведет к непредсказуемому поведению, как некоторые хаки на основе отражения.

/proc файловая система — это официальный API Linux, поддерживаемый разработчиками ядра Linux. Его невозможно удалить, указав другие параметры сборки ядра (например, он является обязательной частью ядра ОС). Он доступен уже много лет и сохраняет лучшую обратную совместимость, чем большинство Android API. В частности, /proc/self/mountinfo был создан более 10 лет назад и будет доступен в большинстве существующих версий Android, кроме самых старых.

Разработчики Android официально не поддерживают специфичные для Linux API. Но и ломать их тоже не собираются. Некоторые из недавних изменений SELinux в Android после Lollipop ограничили доступ к некоторым файлам в /proc/, потому что они позволяли приложениям скрытно шпионить за другими приложениями. Эти изменения специально сделали /proc/self доступным, потому что /proc/self предназначен для предоставления только собственной информации приложений (включая информацию о файловых системах, доступных приложению).

Если Google когда-нибудь перейдет с Linux на Fuchensa или какой-либо другой доморощенный форк BSD, /proc/ и другие специфичные для Linux API, вероятно, сломаются. Мне все равно? Не совсем.

person user1643723    schedule 30.03.2018
comment
Пробовал ваш метод, но я получаю некоторые ошибки в Android Studio. Некоторые из них: класс Mount не найден, throwawayBuffer.get() отображается как ошибка, pfd.getFileDescriptor() отображается как ошибка, scanner.hasNextLine() отображается как ошибка и т. д. отображаются ошибки. Не могли бы вы посмотреть и помочь. - person Rahulrr2602; 30.03.2018
comment
@ Rahulrr2602 Rahulrr2602 Я удалил ссылки на throwawayBuffer и Mount — это всего лишь некоторый ByteBuffer, простой POJO для хранения информации о точке монтирования соответственно. На самом деле это не полностью готовая к использованию библиотека — я ожидаю, что вы адаптируете код к своей среде. - person user1643723; 02.04.2018