web-dev-qa-db-fra.com

Obtenir la liste des chemins de tous les périphériques de stockage connectés à un périphérique Android

Je souhaite obtenir la liste de tous les périphériques de stockage connectés au périphérique Android. 

Par exemple, stockage interne (stockage dans lequel tous les dossiers tels que Téléchargements, DCIM, etc. sont présents), carte SD et périphérique OTG. 

Je sais qu'il y a beaucoup de messages StackOverflow qui traitent de ce sujet, mais aucun d'eux ne pourrait servir mon objectif, comme indiqué ci-dessus.

Je peux obtenir le stockage interne en appelant Environment.getExternalStorageDirectory().getPath() qui renvoie le chemin d'accès au stockage interne.

Toute aide à ce sujet serait vraiment reconnaissante, car il n’existe pas de norme AFAIK sur laquelle la liste de tous les périphériques de stockage connectés peut être récupérée. 

En outre, de nombreuses solutions ne fonctionnent pas sur différents appareils et versions Android.

14
Rahulrr2602

Vous pouvez créer une classe 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;
        }
    }
}

et puis vous pouvez l'utiliser pour vérifier la carte SD ou USB ou inconnu est actuellement connecté ou non avec le périphérique

De cette façon, vous pouvez obtenir la carte SD connectée, 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;
}
0
Amjad Khan

J'ai eu de la chance avec 

ContextCompat.getExternalFilesDirs

Cela permet de trouver les dossiers d’application sur des lecteurs externes. Je n'ai pas encore trouvé de solution plus efficace que celle-ci.

Dans mon cas d'utilisation, j'utilise Environment.DIRECTORY_MOVIES. Mais si vous en avez besoin, il existe d'autres définitions, y compris le DIRECTORY_DOCUMENTS générique.

2
mksteve

Ceci est un ajout à la réponse de @ Sagar sur l'obtention de montages à partir de /proc. Notez l'utilisation de/proc/self/mountinfo à la place de/proc/mountinfo ou/proc/mounts. Vous pouvez en savoir plus sur le format de /proc/self/mountinfo dans man 5 procfs. Tandis que le code suivant analyse techniquement les fichiers, il est sûr de s’exécuter sur le thread principal (car /proc est un système de fichiers en mémoire).

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

Vous pouvez trouver des informations plus utiles (telles que le chemin commun vers un système de fichiers inutile), dans cette réponse . Notez que les formats de/proc/mounts et/proc/mountinfo sont différents , a été introduit plus tard après ancien pour améliorer son format sans rompre la compatibilité avec les versions antérieures.

Le code ci-dessus n'est pas une solution miracle - il ne vous dit rien sur les systèmes de fichiers individuels, mais simplement sur leurs chemins et leurs noms. Vous pouvez être raisonnablement confiant, que "vfat" et "ext4" sont des systèmes de fichiers utiles, et que "procfs" est inutile, mais quelque chose comme "Fuse" va rester mystérieux. Vous pouvez augmenter la sortie du code ci-dessus en utilisant Android.os.storage.StorageManager pour obtenir des noms de système de fichiers plus conviviaux (tels que "Carte SD") lorsqu'ils sont disponibles (correspondance par chemin de montage). Vous pouvez également utiliser StatFs pour obtenir l’espace disponible sur la partition. Les systèmes de fichiers virtuels inutiles ne renvoient généralement aucun espace libre ni aucun espace disponible lorsqu’ils sont interrogés. Enfin, si vous le souhaitez, vous pouvez prendre en compte les options de montage du système de fichiers pour décider si vous souhaitez afficher le système de fichiers à l'utilisateur. Par exemple. ro vs rw, - les montages en système de fichiers en lecture seule seront généralement beaucoup moins utiles pour vos utilisateurs.


Quand j'explique cette méthode aux gens, ils sont souvent préoccupés par sa robustesse… Cela fonctionnera-t-il sur un junkphone aléatoire? Restera-t-il disponible dans les futures versions de système d'exploitation? Voici mon point de vue: cette méthode est toujours meilleure que bien des conseils basés sur la réflexion - dans le pire des cas, lire dans/proc/file vous renverra IOException. Cela ne plantera pas votre application et ne provoquera pas un comportement imprévisible, comme des piratages basés sur la réflexion.

/proc système de fichiers est l'API officielle de Linux, maintenue par les développeurs du noyau Linux. Il n'est pas possible de le supprimer en spécifiant différentes options de construction du noyau (par exemple, il s'agit d'une partie obligatoire du noyau du système d'exploitation). Il est disponible depuis de nombreuses années et conserve une meilleure compatibilité ascendante que la plupart des API Android. En particulier,/proc/self/mountinfo a été créé il y a plus de 10 ans et sera disponible dans la plupart des versions Android existantes, à l'exception de la plus ancienne.

Les développeurs Android ne prennent pas officiellement en charge les API spécifiques à Linux. Mais ils ne font pas leur possible pour les casser non plus. Certains des changements récents de SELinux dans Android post-Lollipop ont restreint l'accès à certains fichiers dans /proc/, car ils permettaient aux applications d'espionner secrètement d'autres applications. Ces modifications ont spécifiquement gardé /proc/self accessible, car/proc/self est conçu pour exposer uniquement les informations propres à l'application (y compris les informations sur les systèmes de fichiers, disponibles pour l'application).

Si Google passe de Linux à Fuchensa ou à un autre fork BSD local, les API/proc/et les autres API spécifiques à Linux se briseront probablement. Est-ce que j'en ai quelque chose à faire? Pas vraiment.

1
user1643723

Depuis l’API de niveau 9, il existe Android.os.storage.StorageManager. Appelez getStorageVolumes() (disponible à partir du niveau 24 de l'API) pour obtenir une liste des volumes de stockage. Comme le dit le doc:

Renvoie la liste des volumes de stockage partagés/externes disponibles pour l'utilisateur actuel. Cela inclut à la fois le périphérique de stockage partagé principal et tous les volumes externes connectés, y compris les cartes SD et les clés USB.

Le résultat est List<StorageVolume>. Maintenant, jetez un oeil à Android.os.storage.StorageVolume:

Informations sur un volume de stockage partagé/externe pour un utilisateur spécifique.

Vous pouvez par exemple obtenir une description visible du volume du volume en appelant getDescription(). Voir createAccessIntent() pour obtenir un accès.

0
Thomas