Trouver l'emplacement d'une carte SD amovible

204

Existe-t-il un moyen universel de trouver l'emplacement d'une carte SD externe?

Veuillez ne pas confondre avec le stockage externe .

Environment.getExternalStorageState()renvoie le chemin vers le point de montage SD interne, comme "/ mnt / sdcard". Mais la question concerne le SD externe. Comment obtenir un chemin comme "/ mnt / sdcard / external_sd" (il peut différer d'un périphérique à l'autre)?

Je suppose que je terminerai par le filtrage de la sortie de la mountcommande par nom de système de fichiers. Mais je ne suis pas sûr que cette méthode soit suffisamment robuste.

borisstr
la source
Voici ma solution qui fonctionne jusqu'à Nougat: stackoverflow.com/a/40205116/5002496
Gokul NC
'Environment.getExternalStorageState () renvoie le chemin vers le point de montage SD interne comme "/ mnt / sdcard".' Eh bien, ce n'est pas interne dans le sens où Android utilise le terme. Je crois que le terme que vous recherchez n'est "pas amovible".
LarsH

Réponses:

162

Environment.getExternalStorageState() retourne le chemin vers le point de montage SD interne comme "/ mnt / sdcard"

Non, Environment.getExternalStorageDirectory()fait référence à tout ce que le fabricant de l'appareil considère comme "stockage externe". Sur certains appareils, il s'agit d'un support amovible, comme une carte SD. Sur certains appareils, il s'agit d'une partie du flash sur l'appareil. Ici, "stockage externe" signifie "les éléments accessibles via le mode de stockage de masse USB lorsqu'ils sont montés sur une machine hôte", au moins pour Android 1.x et 2.x.

Mais la question concerne le SD externe. Comment obtenir un chemin comme "/ mnt / sdcard / external_sd" (il peut différer d'un appareil à l'autre)?

Android n'a pas de concept de «SD externe», à part le stockage externe, comme décrit ci-dessus.

Si un fabricant de périphérique a choisi d'avoir un stockage externe intégré à la mémoire flash et possède également une carte SD, vous devrez contacter ce fabricant pour déterminer si vous pouvez utiliser la carte SD (non garantie) et quelles sont les règles. en l'utilisant, comme le chemin à utiliser pour cela.


METTRE À JOUR

Deux choses récentes à noter:

Tout d'abord, sur Android 4.4+, vous n'avez pas accès en écriture aux supports amovibles (par exemple, "SD externe"), à l'exception des emplacements sur ce support qui pourraient être retournés par getExternalFilesDirs()et getExternalCacheDirs(). Voir l'excellente analyse de Dave Smith à ce sujet, surtout si vous voulez les détails de bas niveau.

Deuxièmement, de peur que quiconque ne se demande si l'accès aux médias amovibles fait partie du SDK Android, voici l'évaluation de Dianne Hackborn :

... garder à l' esprit: jusqu'à Android 4.4, la plate - forme Android officielle n'a pas pris en charge les cartes SD à tous , sauf deux cas particuliers: l'ancienne mise en page de stockage de l' école où le stockage externe est une carte SD (qui est toujours pris en charge par la plate - forme aujourd'hui) , et une petite fonctionnalité ajoutée à Android 3.0 où il analyserait des cartes SD supplémentaires et les ajouterait au fournisseur multimédia et donnerait aux applications un accès en lecture seule à leurs fichiers (qui est également toujours pris en charge sur la plate-forme aujourd'hui).

Android 4.4 est la première version de la plate-forme qui a permis aux applications d'utiliser des cartes SD pour le stockage. Tout accès antérieur à ces informations était via des API privées non prises en charge. Nous avons maintenant une API assez riche dans la plate-forme qui permet aux applications d'utiliser les cartes SD de manière prise en charge, de meilleures façons qu'auparavant: elles peuvent utiliser gratuitement leur zone de stockage spécifique à l'application sans nécessiter aucune autorisations dans l'application et peuvent accéder à tous les autres fichiers sur la carte SD tant qu'ils passent par le sélecteur de fichiers, sans avoir besoin d'autorisations spéciales.

CommonsWare
la source
4
Et ce problème devient de plus en plus problématique car les périphériques HC et ICS sortent ce point "ExternalStorageDirectory" et tout le reste comme ça vers le stockage physique interne. Pour couronner le tout, la plupart des utilisateurs ne savent absolument pas comment localiser leur carte SD dans le système de fichiers.
Tony Maro
284
Votre réponse est donc essentiellement «contacter le fabricant». Pas utile.
dragonroot
6
La dernière partie de la réponse n'est pas tout à fait précise - il est en effet possible de détecter le chemin de la carte SD en suivant les réponses ci-dessous (scan / proc / mounts, /system/etc/vold.fstab, etc ...).
Apprenez OpenGL ES
8
@CommonsWare: Néanmoins, il n'est toujours pas exact que l'on "doive" contacter un fabricant lorsqu'il existe des solutions qui fonctionnent sur de nombreux appareils, et en outre, le SDK lui-même n'est pas cohérent sur tous les appareils, ce qui n'est pas une garantie. Même si ces solutions ne fonctionnent pas sur tous les appareils, elles fonctionnent sur suffisamment d'appareils pour que de nombreuses applications Android sur le marché s'appuient sur ces techniques, entre autres, pour détecter le chemin de la carte SD externe. Je pense qu'il est un peu dur et prématuré d'appeler tous ces développeurs des imbéciles - le client n'est-il pas certainement le juge final de cela?
Apprenez OpenGL ES
5
@CommonsWare C'est assez juste, au fur et à mesure. Je suis définitivement d'accord avec vous qu'un développeur ne peut pas supposer que cela fonctionnera toujours partout, et qu'un tel code ne peut pas être garanti pour fonctionner sur tous les appareils ou pour toutes les versions d'Android. Espérons que cela soit corrigé dans le SDK! En attendant, il existe encore des options qui fonctionnent sur de nombreux appareils et peuvent améliorer l'expérience de l'utilisateur final, et étant donné le choix entre 80% de réussite et 0% de réussite, je prendrai les 80%.
Apprenez OpenGL ES le
64

J'ai trouvé la solution suivante basée sur quelques réponses trouvées ici.

CODE:

public class ExternalStorage {

    public static final String SD_CARD = "sdCard";
    public static final String EXTERNAL_SD_CARD = "externalSdCard";

    /**
     * @return True if the external storage is available. False otherwise.
     */
    public static boolean isAvailable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        return false;
    }

    public static String getSdCardPath() {
        return Environment.getExternalStorageDirectory().getPath() + "/";
    }

    /**
     * @return True if the external storage is writable. False otherwise.
     */
    public static boolean isWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;

    }

    /**
     * @return A map of all storage locations available
     */
    public static Map<String, File> getAllStorageLocations() {
        Map<String, File> map = new HashMap<String, File>(10);

        List<String> mMounts = new ArrayList<String>(10);
        List<String> mVold = new ArrayList<String>(10);
        mMounts.add("/mnt/sdcard");
        mVold.add("/mnt/sdcard");

        try {
            File mountFile = new File("/proc/mounts");
            if(mountFile.exists()){
                Scanner scanner = new Scanner(mountFile);
                while (scanner.hasNext()) {
                    String line = scanner.nextLine();
                    if (line.startsWith("/dev/block/vold/")) {
                        String[] lineElements = line.split(" ");
                        String element = lineElements[1];

                        // don't add the default mount path
                        // it's already in the list.
                        if (!element.equals("/mnt/sdcard"))
                            mMounts.add(element);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            File voldFile = new File("/system/etc/vold.fstab");
            if(voldFile.exists()){
                Scanner scanner = new Scanner(voldFile);
                while (scanner.hasNext()) {
                    String line = scanner.nextLine();
                    if (line.startsWith("dev_mount")) {
                        String[] lineElements = line.split(" ");
                        String element = lineElements[2];

                        if (element.contains(":"))
                            element = element.substring(0, element.indexOf(":"));
                        if (!element.equals("/mnt/sdcard"))
                            mVold.add(element);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


        for (int i = 0; i < mMounts.size(); i++) {
            String mount = mMounts.get(i);
            if (!mVold.contains(mount))
                mMounts.remove(i--);
        }
        mVold.clear();

        List<String> mountHash = new ArrayList<String>(10);

        for(String mount : mMounts){
            File root = new File(mount);
            if (root.exists() && root.isDirectory() && root.canWrite()) {
                File[] list = root.listFiles();
                String hash = "[";
                if(list!=null){
                    for(File f : list){
                        hash += f.getName().hashCode()+":"+f.length()+", ";
                    }
                }
                hash += "]";
                if(!mountHash.contains(hash)){
                    String key = SD_CARD + "_" + map.size();
                    if (map.size() == 0) {
                        key = SD_CARD;
                    } else if (map.size() == 1) {
                        key = EXTERNAL_SD_CARD;
                    }
                    mountHash.add(hash);
                    map.put(key, root);
                }
            }
        }

        mMounts.clear();

        if(map.isEmpty()){
                 map.put(SD_CARD, Environment.getExternalStorageDirectory());
        }
        return map;
    }
}

USAGE:

Map<String, File> externalLocations = ExternalStorage.getAllStorageLocations();
File sdCard = externalLocations.get(ExternalStorage.SD_CARD);
File externalSdCard = externalLocations.get(ExternalStorage.EXTERNAL_SD_CARD);
Richard
la source
1
testé avec nexus 4, nexus s, galaxy s2, galaxy s3, htc desire =)
Richard
2
Salut à nouveau, Richard - croyez-le ou non, je dois vous demander: avez-vous réellement essayé d'écrire et de relire un fichier de cette façon, pas simplement d'obtenir les répertoires? Rappelez-vous notre ancien problème "/ sdcard0"? J'ai essayé ce code et il a échoué sur un S3 lorsque j'ai essayé de relire le fichier qu'il avait écrit. ... c'est très bizarre ... et douloureux :))
Howard Pautz
10
Cela échoue sur les appareils qui n'ont pas 2 cartes SD. Il suppose que le 1er trouvé est interne, et le 2ème trouvé est externe ...
Caner
N'a PAS fonctionné pour les périphériques USB connectés via un câble OTG sur Nexus 5 et Nexus 7.
Khawar Raza
4
/system/etc/vold.fstab n'est pas accessible dans Android 4.3+
Ali
37

J'avais une application qui utilisait un ListPreferenceemplacement où l'utilisateur devait sélectionner l'emplacement où il voulait enregistrer quelque chose.

Dans cette application, j'ai scanné /proc/mountset /system/etc/vold.fstabrecherché des points de montage sdcard. J'ai stocké les points de montage de chaque fichier dans deux ArrayLists distincts .

Ensuite, j'ai comparé une liste avec l'autre et jeté les éléments qui n'étaient pas dans les deux listes. Cela m'a donné une liste de chemins racine vers chaque carte SD.

A partir de là, je l' ai testé les chemins avec File.exists(), File.isDirectory()et File.canWrite(). Si l'un de ces tests était faux, j'ai écarté ce chemin de la liste.

Tout ce qui restait dans la liste, je l'ai converti en un String[]tableau afin qu'il puisse être utilisé par l' ListPreferenceattribut values.

Vous pouvez voir le code ici: http://sapienmobile.com/?p=204

Baron
la source
Pour info, cela ne fonctionne pas sur Galaxy S3, 2 cartes SD, une seule répertoriée dans vold.conf
3c71
1
@ 3c71 - Pouvez-vous m'envoyer les fichiers Vold et Mounts pour Galaxy S3? Je vais modifier le code pour le couvrir.
Baron
Galaxy S, tous les chemins trouvés n'étaient pas inscriptibles, bizarres. Deux stockages ont été trouvés, par défaut / mnt / sdcard et / storage / sdcard0, les deux n'ont pas pu être testés
5
1
J'ai modifié le code pour ignorer le fichier des montures. C'était le problème sur les appareils Motorola et Samsung. Le fichier des montures ne couvrait pas le cas external_sd, mais il est répertorié dans vold. La version initiale de ma classe comparait les montures avec des objets vold et jetés qui n'étaient pas communs aux deux. Prenez la classe mise à jour à partir du même lien ci-dessus.
Baron
1
Merci Baron, c'est "la" réponse; au moins l'utile.
pstoppani
23

Vous pouvez essayer d'utiliser la fonction de bibliothèque de support appelée ContextCompat.getExternalFilesDirs () :

      final File[] appsDir=ContextCompat.getExternalFilesDirs(getActivity(),null);
      final ArrayList<File> extRootPaths=new ArrayList<>();
      for(final File file : appsDir)
        extRootPaths.add(file.getParentFile().getParentFile().getParentFile().getParentFile());

Le premier est le stockage externe principal, et les autres sont censés être de vrais chemins de cartes SD.

La raison du multiple ".getParentFile ()" est de remonter dans un autre dossier, car le chemin d'origine est

.../Android/data/YOUR_APP_PACKAGE_NAME/files/

EDIT: voici un moyen plus complet que j'ai créé, pour obtenir les chemins des cartes SD:

  /**
   * returns a list of all available sd cards paths, or null if not found.
   *
   * @param includePrimaryExternalStorage set to true if you wish to also include the path of the primary external storage
   */
  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
  public static List<String> getSdCardPaths(final Context context, final boolean includePrimaryExternalStorage)
    {
    final File[] externalCacheDirs=ContextCompat.getExternalCacheDirs(context);
    if(externalCacheDirs==null||externalCacheDirs.length==0)
      return null;
    if(externalCacheDirs.length==1)
      {
      if(externalCacheDirs[0]==null)
        return null;
      final String storageState=EnvironmentCompat.getStorageState(externalCacheDirs[0]);
      if(!Environment.MEDIA_MOUNTED.equals(storageState))
        return null;
      if(!includePrimaryExternalStorage&&VERSION.SDK_INT>=VERSION_CODES.HONEYCOMB&&Environment.isExternalStorageEmulated())
        return null;
      }
    final List<String> result=new ArrayList<>();
    if(includePrimaryExternalStorage||externalCacheDirs.length==1)
      result.add(getRootOfInnerSdCardFolder(externalCacheDirs[0]));
    for(int i=1;i<externalCacheDirs.length;++i)
      {
      final File file=externalCacheDirs[i];
      if(file==null)
        continue;
      final String storageState=EnvironmentCompat.getStorageState(file);
      if(Environment.MEDIA_MOUNTED.equals(storageState))
        result.add(getRootOfInnerSdCardFolder(externalCacheDirs[i]));
      }
    if(result.isEmpty())
      return null;
    return result;
    }

  /** Given any file/folder inside an sd card, this will return the path of the sd card */
  private static String getRootOfInnerSdCardFolder(File file)
    {
    if(file==null)
      return null;
    final long totalSpace=file.getTotalSpace();
    while(true)
      {
      final File parentFile=file.getParentFile();
      if(parentFile==null||parentFile.getTotalSpace()!=totalSpace||!parentFile.canRead())
        return file.getAbsolutePath();
      file=parentFile;
      }
    }
développeur android
la source
Cela semble une très bonne réponse, mais comment intégrer cela dans une activité simple? Il y a plusieurs variables non définies, comme App, ContextCompact,EnvironmentCompact
Antonio
@Antonio ContextCompact, EnvironmentCompact sont disponibles via la bibliothèque de support. "App.global ()" est juste le contexte d'application, que j'ai défini globalement car je n'aime pas ajouter un paramètre de contexte partout.
développeur Android
1
Génial! Fonctionne pour le périphérique minier v4.4 Samsung GT S Advance, j'espère que cela fonctionnera pour d'autres
user25
@androiddeveloper La réponse modifiée fonctionnera-t-elle pour tous les appareils et tailles de carte SD?
Rahulrr2602
1
Cela a parfaitement fonctionné pour moi - devrait être la réponse acceptée.
Paradox
17

Afin de récupérer tous les stockages externes (qu'il s'agisse de cartes SD ou de stockages internes non amovibles ), vous pouvez utiliser le code suivant:

final String state = Environment.getExternalStorageState();

if ( Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state) ) {  // we can read the External Storage...           
    //Retrieve the primary External Storage:
    final File primaryExternalStorage = Environment.getExternalStorageDirectory();

    //Retrieve the External Storages root directory:
    final String externalStorageRootDir;
    if ( (externalStorageRootDir = primaryExternalStorage.getParent()) == null ) {  // no parent...
        Log.d(TAG, "External Storage: " + primaryExternalStorage + "\n");
    }
    else {
        final File externalStorageRoot = new File( externalStorageRootDir );
        final File[] files = externalStorageRoot.listFiles();

        for ( final File file : files ) {
            if ( file.isDirectory() && file.canRead() && (file.listFiles().length > 0) ) {  // it is a real directory (not a USB drive)...
                Log.d(TAG, "External Storage: " + file.getAbsolutePath() + "\n");
            }
        }
    }
}

Vous pouvez également utiliser System.getenv ("EXTERNAL_STORAGE") pour récupérer le répertoire de stockage externe principal (par exemple "/ storage / sdcard0" ) et System.getenv ("SECONDARY_STORAGE") pour récupérer la liste de tous les répertoires secondaires (par exemple " / storage / extSdCard: / storage / UsbDriveA: / storage / UsbDriveB " ). N'oubliez pas que, dans ce cas également, vous souhaiterez peut-être filtrer la liste des répertoires secondaires afin d'exclure les lecteurs USB.

Dans tous les cas, veuillez noter que l'utilisation de chemins codés en dur est toujours une mauvaise approche (surtout lorsque chaque fabricant peut le changer à sa guise).

Paolo Rovelli
la source
2
Considérez tout électeur qui ne laisse pas de commentaire comme un troll, j'ai donc voté pour compenser. ;) MAIS, je suppose que votre méthode est plutôt arbitraire: comment savoir que sauter ces "clés USB" mais garder tout le reste est égal à "sdcards", comme demandé dans la question? Votre suggestion System.getenv("SECONDARY_STORAGE")pourrait également concerner certaines références, car elle semble non documentée.
Sz.
1
Pour autant que je sache, dans l'API Android, il n'y a aucune référence à une méthode standard pour récupérer tous les stockages externes. Cependant, la méthode proposée n'est pas du tout arbitraire. Sous Android, comme dans tout système Unix / Linux, TOUS les périphériques de stockage de montage sont stockés / liés dans un répertoire commun: "/ mnt" (le répertoire Unix / Linux standard pour le montage de périphériques de stockage) ou, dans les dernières versions, "/ espace de rangement". C'est pourquoi vous pouvez être sûr que vous trouverez toutes les cartes SD liées dans ce dossier.
Paolo Rovelli
1
Concernant la méthode System.getenv ("EXTERNAL_STORAGE"), je n'ai pas de référence plutôt que la page API (qui n'explique pas grand chose): developer.android.com/reference/java/lang/… je n'ai pas pu en trouver page officielle des variables d'environnement du système Android. Ici, cependant, vous pouvez en trouver une courte liste: herongyang.com/Android/…
Paolo Rovelli
Ce que je voulais dire en n'étant pas sûr des cartes SD, c'est /mntqu'il peut y avoir divers autres arbres fs, pas seulement des cartes SD et des clés USB. Votre code répertorie également tous les supports de système de fichiers internes (peut-être même virtuels), si je comprends bien, alors que la question ne veut que des cartes SD .
Sz.
1
Je vois. Oui, tu as raison. Avec ma méthode, vous récupérerez également les mémoires SD internes (non amovibles).
Paolo Rovelli
15

Comme Richard, j'utilise également le fichier / proc / mounts pour obtenir la liste des options de stockage disponibles

public class StorageUtils {

    private static final String TAG = "StorageUtils";

    public static class StorageInfo {

        public final String path;
        public final boolean internal;
        public final boolean readonly;
        public final int display_number;

        StorageInfo(String path, boolean internal, boolean readonly, int display_number) {
            this.path = path;
            this.internal = internal;
            this.readonly = readonly;
            this.display_number = display_number;
        }

        public String getDisplayName() {
            StringBuilder res = new StringBuilder();
            if (internal) {
                res.append("Internal SD card");
            } else if (display_number > 1) {
                res.append("SD card " + display_number);
            } else {
                res.append("SD card");
            }
            if (readonly) {
                res.append(" (Read only)");
            }
            return res.toString();
        }
    }

    public static List<StorageInfo> getStorageList() {

        List<StorageInfo> list = new ArrayList<StorageInfo>();
        String def_path = Environment.getExternalStorageDirectory().getPath();
        boolean def_path_internal = !Environment.isExternalStorageRemovable();
        String def_path_state = Environment.getExternalStorageState();
        boolean def_path_available = def_path_state.equals(Environment.MEDIA_MOUNTED)
                                    || def_path_state.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
        boolean def_path_readonly = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
        BufferedReader buf_reader = null;
        try {
            HashSet<String> paths = new HashSet<String>();
            buf_reader = new BufferedReader(new FileReader("/proc/mounts"));
            String line;
            int cur_display_number = 1;
            Log.d(TAG, "/proc/mounts");
            while ((line = buf_reader.readLine()) != null) {
                Log.d(TAG, line);
                if (line.contains("vfat") || line.contains("/mnt")) {
                    StringTokenizer tokens = new StringTokenizer(line, " ");
                    String unused = tokens.nextToken(); //device
                    String mount_point = tokens.nextToken(); //mount point
                    if (paths.contains(mount_point)) {
                        continue;
                    }
                    unused = tokens.nextToken(); //file system
                    List<String> flags = Arrays.asList(tokens.nextToken().split(",")); //flags
                    boolean readonly = flags.contains("ro");

                    if (mount_point.equals(def_path)) {
                        paths.add(def_path);
                        list.add(0, new StorageInfo(def_path, def_path_internal, readonly, -1));
                    } else if (line.contains("/dev/block/vold")) {
                        if (!line.contains("/mnt/secure")
                            && !line.contains("/mnt/asec")
                            && !line.contains("/mnt/obb")
                            && !line.contains("/dev/mapper")
                            && !line.contains("tmpfs")) {
                            paths.add(mount_point);
                            list.add(new StorageInfo(mount_point, false, readonly, cur_display_number++));
                        }
                    }
                }
            }

            if (!paths.contains(def_path) && def_path_available) {
                list.add(0, new StorageInfo(def_path, def_path_internal, def_path_readonly, -1));
            }

        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (buf_reader != null) {
                try {
                    buf_reader.close();
                } catch (IOException ex) {}
            }
        }
        return list;
    }    
}
Vitaliy Polchuk
la source
Merci. Fonctionne parfaitement. Et j'aime la façon dont vous avez rendu StorageInfo non mutable. D'un autre côté printStackTrace? Quand on a android.util.Log.e?
Martin
1
N'a PAS fonctionné pour les périphériques USB connectés via un câble OTG sur Nexus 5 et Nexus 7.
Khawar Raza
1
Je ne peux pas l'utiliser pour écrire un fichier sur la carte SD
Eu Vid
même problème que @EuVid fonctionne sur VM / AVD mais pas sur le matériel
espion
11

Il est possible de trouver où toutes les cartes SD supplémentaires sont montées en lisant /proc/mounts(fichier Linux standard) et en recoupant les données vold ( /system/etc/vold.conf). Et notez que l'emplacement renvoyé par Environment.getExternalStorageDirectory()peut ne pas apparaître dans la configuration vold (dans certains appareils, c'est le stockage interne qui ne peut pas être démonté), mais doit toujours être inclus dans la liste. Cependant, nous n'avons pas trouvé un bon moyen de les décrire à l'utilisateur .

Jan Hudec
la source
Imo, l'utilisation de mountest plus compatible que la lecture d'un /procsystème de fichiers. Le problème est que la carte SD n'est pas nécessairement formatée en FAT. De plus, le point de montage de la carte peut varier d'une ROM à l'autre. En outre, il pourrait y avoir plusieurs autres partitions VFAT ...
borisstr
1
@borisstr: Hm, en fait, Android utilise vold , donc regarder sa configuration est également approprié.
Jan Hudec
Le fichier de code que j'ai partagé dans mon article ci-dessus comprend une méthode pour décrire les chemins racine découverts vers l'utilisateur. Regardez la méthode setProperties () .
Baron
1
@borisstr, en fait, non, la lecture de / proc / mounts est plus portable sur les appareils Android que le lancement de l' mountexécutable, d'autant plus que le lancement des exécutables est déconseillé.
Chris Stratton
7

J'essaie toutes les solutions à l'intérieur de ce sujet cette fois. Mais tous ne fonctionnaient pas correctement sur les appareils avec une carte externe (amovible) et une carte interne (non amovible). Le chemin de la carte externe n'est pas possible obtenir à partir de la commande 'mount', du fichier 'proc / mounts' etc.

Et je crée ma propre solution (sur celle de Paulo Luan):

String sSDpath = null;
File   fileCur = null;
for( String sPathCur : Arrays.asList( "ext_card", "external_sd", "ext_sd", "external", "extSdCard",  "externalSdCard")) // external sdcard
{
   fileCur = new File( "/mnt/", sPathCur);
   if( fileCur.isDirectory() && fileCur.canWrite())
   {
     sSDpath = fileCur.getAbsolutePath();
     break;
   }
}
fileCur = null;
if( sSDpath == null)  sSDpath = Environment.getExternalStorageDirectory().getAbsolutePath();
Tapa Save
la source
6

Si vous regardez le code source, android.os.Environmentvous verrez qu'Android dépend fortement des variables d'environnement pour les chemins. Vous pouvez utiliser la variable d'environnement "SECONDARY_STORAGE" pour trouver le chemin d'accès à la carte SD amovible.

/**
 * Get a file using an environmental variable.
 *
 * @param variableName
 *         The Environment variable name.
 * @param paths
 *         Any paths to the file if the Environment variable was not found.
 * @return the File or {@code null} if the File could not be located.
 */
private static File getDirectory(String variableName, String... paths) {
    String path = System.getenv(variableName);
    if (!TextUtils.isEmpty(path)) {
        if (path.contains(":")) {
            for (String _path : path.split(":")) {
                File file = new File(_path);
                if (file.exists()) {
                    return file;
                }
            }
        } else {
            File file = new File(path);
            if (file.exists()) {
                return file;
            }
        }
    }
    if (paths != null && paths.length > 0) {
        for (String _path : paths) {
            File file = new File(_path);
            if (file.exists()) {
                return file;
            }
        }
    }
    return null;
}

Exemple d'utilisation:

public static final File REMOVABLE_STORAGE = getDirectory("SECONDARY_STORAGE");
Jared Rummler
la source
5

Utilisez simplement ceci:

String primary_sd = System.getenv("EXTERNAL_STORAGE");
if(primary_sd != null)
    Log.i("EXTERNAL_STORAGE", primary_sd);
String secondary_sd = System.getenv("SECONDARY_STORAGE");
if(secondary_sd != null)
    Log.i("SECONDARY_STORAGE", secondary_sd)
Behrouz.M
la source
Sur certains appareils, SECONDARY_STORAGEplusieurs chemins sont séparés par deux points (":"). C'est pourquoi j'ai divisé la chaîne (voir ma réponse ci-dessus).
Jared Rummler
Ces deux retournent nul pour moi.
Tim Cooper du
5

Existe-t-il un moyen universel de trouver l'emplacement d'une carte SD externe?

Par voie universelle , si vous voulez dire par voie officielle; Oui il y en a un.

Dans l'API niveau 19, c'est-à-dire dans la version Android 4.4 Kitkat, ils ont ajouté File[] getExternalFilesDirs (String type)en Contextclasse qui permet aux applications de stocker des données / fichiers sur des cartes micro SD.

Android 4.4 est la première version de la plate-forme qui a permis aux applications d'utiliser des cartes SD pour le stockage. Tout accès aux cartes SD avant le niveau 19 de l'API se faisait via des API privées non prises en charge.

getExternalFilesDirs (type String) renvoie des chemins absolus vers des répertoires spécifiques à l'application sur tous les périphériques de stockage partagés / externes. Cela signifie qu'il retournera des chemins vers la mémoire interne et externe. Généralement, le deuxième chemin renvoyé serait le chemin de stockage pour la carte microSD (le cas échéant).

Mais notez que,

Le stockage partagé n'est pas toujours disponible, car les supports amovibles peuvent être éjectés par l'utilisateur. L'état du support peut être vérifié à l'aide de getExternalStorageState(File).

Aucune sécurité n'est appliquée avec ces fichiers. Par exemple, toute application détenant WRITE_EXTERNAL_STORAGEpeut écrire dans ces fichiers.

La terminologie de stockage interne et externe selon Google / documents Android officiels est assez différente de ce que nous pensons.

AnV
la source
"La terminologie du stockage interne et externe selon Google / documents officiels Android est très différente de ce que nous pensons." Oui, en fait le titre de la question précise que l'OP pose des questions sur une carte SD amovible . getExternalFilesDirs()renvoie souvent des cartes SD qui ne sont pas amovibles, donc non, ce n'est pas un moyen universel de trouver l'emplacement d'une carte SD amovible.
LarsH
"getExternalFilesDirs (type String) renvoie des chemins absolus vers des répertoires spécifiques à l'application sur tous les périphériques de stockage partagés / externes. Cela signifie qu'il renverra des chemins vers la mémoire interne et externe." Cette paire de phrases est très trompeuse, car pour qu'elles soient toutes les deux vraies, «externe» doit signifier deux choses différentes et contradictoires.
LarsH
4

Voici la façon dont j'utilise pour trouver la carte externe. Utilisez mount cmd return puis analysez la partie vfat.

String s = "";
try {
Process process = new ProcessBuilder().command("mount")
        .redirectErrorStream(true).start();

process.waitFor();

InputStream is = process.getInputStream();
byte[] buffer = new byte[1024];
while (is.read(buffer) != -1) {
    s = s + new String(buffer);
}
is.close();
} catch (Exception e) {
e.printStackTrace();
}

//用行分隔mount列表
String[] lines = s.split("\n");
for(int i=0; i<lines.length; i++) {
//如果行内有挂载路径且为vfat类型,说明可能是内置或者外置sd的挂载点
if(-1 != lines[i].indexOf(path[0]) && -1 != lines[i].indexOf("vfat")) {
    //再用空格分隔
    String[] blocks = lines[i].split("\\s");
    for(int j=0; j<blocks.length; j++) {
        //判断是否是挂载为vfat类型
        if(-1 != blocks[j].indexOf(path[0])) {
            //Test if it is the external sd card.
        }
    }
}
}
Fakebear
la source
4

Cette solution gère le fait que cela System.getenv("SECONDARY_STORAGE")n'est d'aucune utilité avec Marshmallow.

Testé et travaillant sur:

  • Samsung Galaxy Tab 2 (Android 4.1.1 - Stock)
  • Samsung Galaxy Note 8.0 (Android 4.2.2 - Stock)
  • Samsung Galaxy S4 (Android 4.4 - Stock)
  • Samsung Galaxy S4 (Android 5.1.1 - CyanogenMod)
  • Samsung Galaxy Tab A (Android 6.0.1 - Stock)

    /**
     * Returns all available external SD-Card roots in the system.
     *
     * @return paths to all available external SD-Card roots in the system.
     */
    public static String[] getStorageDirectories() {
        String [] storageDirectories;
        String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE");
    
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            List<String> results = new ArrayList<String>();
            File[] externalDirs = applicationContext.getExternalFilesDirs(null);
            for (File file : externalDirs) {
                String path = file.getPath().split("/Android")[0];
                if((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Environment.isExternalStorageRemovable(file))
                        || rawSecondaryStoragesStr != null && rawSecondaryStoragesStr.contains(path)){
                    results.add(path);
                }
            }
            storageDirectories = results.toArray(new String[0]);
        }else{
            final Set<String> rv = new HashSet<String>();
    
            if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) {
                final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator);
                Collections.addAll(rv, rawSecondaryStorages);
            }
            storageDirectories = rv.toArray(new String[rv.size()]);
        }
        return storageDirectories;
    }
DaveAlden
la source
2

Depuis ma réponse originale ci-dessus, la numérisation de vold n'est plus viable entre les différents fabricants.

J'ai développé une méthode plus fiable et simple.

File mnt = new File("/storage");
if (!mnt.exists())
    mnt = new File("/mnt");

File[] roots = mnt.listFiles(new FileFilter() {

    @Override
    public boolean accept(File pathname) {
        return pathname.isDirectory() && pathname.exists()
                && pathname.canWrite() && !pathname.isHidden()
                && !isSymlink(pathname);
    }
});

les racines contiendront tous les répertoires racine accessibles en écriture sur le système, y compris tous les périphériques USB connectés par USB.

REMARQUE: La méthode canWrite a besoin de l'autorisation android.permission.WRITE_EXTERNAL_STORAGE.

Baron
la source
La méthode isSymlink (File) n'est pas définie pour le type new FileFilter () {}
Omid Omidi
Une idée si cela ne parviendra pas à trouver des cartes SD externes sur Android 4.4 en raison de canWrite?
Anthony
C'est certainement plus simple que votre autre méthode, mais est-ce fiable? Par exemple, j'ai lu que sur certains appareils Samsung, se /external_sdtrouve la carte microSD externe; sur certains LG, c'est /_ExternalSD; sur certains appareils, c'est /sdcard. Peut-être que ce dernier est un lien symbolique vers /storage/sdcard0ou similaire, mais ces autres seront-ils vraiment couverts de manière fiable par /storage/*et /mount/*?
LarsH
Est-il également nécessaire d'utiliser pathname.canWrite()et d'exiger l'autorisation WRITE_EXTERNAL_STORAGE? Pourquoi ne pas simplement appeler pathname.canRead()?
LarsH
1

il a été si tard, mais finalement j'ai obtenu quelque chose que j'ai testé la plupart des appareils (par fabricant et versions Android) son travail sur Android 2.2+. si vous trouvez que cela ne fonctionne pas, commentez-le avec le nom de votre appareil. je le réparerai. si quelqu'un est intéressé, je vais vous expliquer comment cela fonctionne.

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;

import android.util.Log;


/**
 * @author ajeet
 *05-Dec-2014  2014
 *
 */
public class StorageUtil {

    public boolean isRemovebleSDCardMounted() {
        File file = new File("/sys/class/block/");
        File[] files = file.listFiles(new MmcblkFilter("mmcblk\\d$"));
        boolean flag = false;
        for (File mmcfile : files) {
            File scrfile = new File(mmcfile, "device/scr");
            if (scrfile.exists()) {
                flag = true;
                break;
            }
        }
        return flag;
    }

    public String getRemovebleSDCardPath() throws IOException {
        String sdpath = null;
        File file = new File("/sys/class/block/");
        File[] files = file.listFiles(new MmcblkFilter("mmcblk\\d$"));
        String sdcardDevfile = null;
        for (File mmcfile : files) {
            Log.d("SDCARD", mmcfile.getAbsolutePath());
            File scrfile = new File(mmcfile, "device/scr");
            if (scrfile.exists()) {
                sdcardDevfile = mmcfile.getName();
                Log.d("SDCARD", mmcfile.getName());
                break;
            }
        }
        if (sdcardDevfile == null) {
            return null;
        }
        FileInputStream is;
        BufferedReader reader;

        files = file.listFiles(new MmcblkFilter(sdcardDevfile + "p\\d+"));
        String deviceName = null;
        if (files.length > 0) {
            Log.d("SDCARD", files[0].getAbsolutePath());
            File devfile = new File(files[0], "dev");
            if (devfile.exists()) {
                FileInputStream fis = new FileInputStream(devfile);
                reader = new BufferedReader(new InputStreamReader(fis));
                String line = reader.readLine();
                deviceName = line;
            }
            Log.d("SDCARD", "" + deviceName);
            if (deviceName == null) {
                return null;
            }
            Log.d("SDCARD", deviceName);

            final File mountFile = new File("/proc/self/mountinfo");

            if (mountFile.exists()) {
                is = new FileInputStream(mountFile);
                reader = new BufferedReader(new InputStreamReader(is));
                String line = null;
                while ((line = reader.readLine()) != null) {
                    // Log.d("SDCARD", line);
                    // line = reader.readLine();
                    // Log.d("SDCARD", line);
                    String[] mPonts = line.split("\\s+");
                    if (mPonts.length > 6) {
                        if (mPonts[2].trim().equalsIgnoreCase(deviceName)) {
                            if (mPonts[4].contains(".android_secure")
                                    || mPonts[4].contains("asec")) {
                                continue;
                            }
                            sdpath = mPonts[4];
                            Log.d("SDCARD", mPonts[4]);

                        }
                    }

                }
            }

        }

        return sdpath;
    }

    static class MmcblkFilter implements FilenameFilter {
        private String pattern;

        public MmcblkFilter(String pattern) {
            this.pattern = pattern;

        }

        @Override
        public boolean accept(File dir, String filename) {
            if (filename.matches(pattern)) {
                return true;
            }
            return false;
        }

    }

}
Ajeet47
la source
Hé downvoter, essayez d'abord .Si cela ne fonctionne pas, commentez votre appareil. nous l'utilisons sur des milliers d'appareils avec Android 2.2+
Ajeet47
Votre classe me donne / mnt / media_rw / extSdCard sur Samsung Galaxy S4, GT-I9500, Android 5.0.1 (l'appareil n'est PAS enraciné). Mais il n'y a rien de visible dans le dossier / mnt / media_rw avec ES File Manager ...
isabsent
@isabsent use if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT) {File [] file = context.getExternalFilesDirs (null); return file.length> 1? file [1]: null; }
Ajeet47
Que pensez-vous de stackoverflow.com/a/27197248/753575 ? Cette approche est-elle plus complète pour le cas de Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT?
isabsent du
Oui. il est garanti que vous obtenez un chemin significatif à partir de context.getExternalFilesDirs (null), mais vous l'avez coupé pour le chemin racine (il renverra le chemin pour votre répertoire d'application. coupez-le sur "/ Android")
Ajeet47
1

En écrivant le code ci-dessous, vous obtiendrez l'emplacement:

/ stockage / 663D-554E / Android / data / nom_package_app / files /

qui stocke les données de votre application dans / android / data à l'intérieur de la sd_card.

File[] list = ContextCompat.getExternalFilesDirs(MainActivity.this, null);

list[1]+"/fol" 

pour obtenir l'emplacement, passez 0 pour l'interne et 1 pour la carte SD au tableau de fichiers.

J'ai testé ce code sur une moto g4 plus et un appareil Samsung (tout fonctionne bien).

j'espère que cela pourrait être utile.

Rk215 Tech
la source
parfois le chemin de la carte sd n'est pas sur l'index 1, j'ai vu les cas où il était sur l'index 0. mieux suivre autre chose
Raghav Satyadev
1

Voici la méthode que j'utilise pour trouver une carte SD amovible . C'est complexe et probablement exagéré dans certaines situations, mais cela fonctionne sur une grande variété de versions d'Android et de fabricants d'appareils que j'ai testés au cours des dernières années. Je ne connais aucun périphérique depuis le niveau 15 de l'API sur lequel il ne trouve pas la carte SD, s'il y en a une montée. Il ne retournera pas de faux positifs dans la plupart des cas, surtout si vous lui donnez le nom d'un fichier connu à rechercher.

Veuillez me faire savoir si vous rencontrez des cas où cela ne fonctionne pas.

import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.regex.Pattern;

public class SDCard {
    private static final String TAG = "SDCard";

    /** In some scenarios we can expect to find a specified file or folder on SD cards designed
     * to work with this app. If so, set KNOWNFILE to that filename. It will make our job easier.
     * Set it to null otherwise. */
    private static final String KNOWNFILE = null;

    /** Common paths for microSD card. **/
    private static String[] commonPaths = {
            // Some of these taken from
            // /programming/13976982/removable-storage-external-sdcard-path-by-manufacturers
            // These are roughly in order such that the earlier ones, if they exist, are more sure
            // to be removable storage than the later ones.
            "/mnt/Removable/MicroSD",
            "/storage/removable/sdcard1", // !< Sony Xperia Z1
            "/Removable/MicroSD", // Asus ZenPad C
            "/removable/microsd",
            "/external_sd", // Samsung
            "/_ExternalSD", // some LGs
            "/storage/extSdCard", // later Samsung
            "/storage/extsdcard", // Main filesystem is case-sensitive; FAT isn't.
            "/mnt/extsd", // some Chinese tablets, e.g. Zeki
            "/storage/sdcard1", // If this exists it's more likely than sdcard0 to be removable.
            "/mnt/extSdCard",
            "/mnt/sdcard/external_sd",
            "/mnt/external_sd",
            "/storage/external_SD",
            "/storage/ext_sd", // HTC One Max
            "/mnt/sdcard/_ExternalSD",
            "/mnt/sdcard-ext",

            "/sdcard2", // HTC One M8s
            "/sdcard1", // Sony Xperia Z
            "/mnt/media_rw/sdcard1",   // 4.4.2 on CyanogenMod S3
            "/mnt/sdcard", // This can be built-in storage (non-removable).
            "/sdcard",
            "/storage/sdcard0",
            "/emmc",
            "/mnt/emmc",
            "/sdcard/sd",
            "/mnt/sdcard/bpemmctest",
            "/mnt/external1",
            "/data/sdext4",
            "/data/sdext3",
            "/data/sdext2",
            "/data/sdext",
            "/storage/microsd" //ASUS ZenFone 2

            // If we ever decide to support USB OTG storage, the following paths could be helpful:
            // An LG Nexus 5 apparently uses usb://1002/UsbStorage/ as a URI to access an SD
            // card over OTG cable. Other models, like Galaxy S5, use /storage/UsbDriveA
            //        "/mnt/usb_storage",
            //        "/mnt/UsbDriveA",
            //        "/mnt/UsbDriveB",
    };

    /** Find path to removable SD card. */
    public static File findSdCardPath(Context context) {
        String[] mountFields;
        BufferedReader bufferedReader = null;
        String lineRead = null;

        /** Possible SD card paths */
        LinkedHashSet<File> candidatePaths = new LinkedHashSet<>();

        /** Build a list of candidate paths, roughly in order of preference. That way if
         * we can't definitively detect removable storage, we at least can pick a more likely
         * candidate. */

        // Could do: use getExternalStorageState(File path), with and without an argument, when
        // available. With an argument is available since API level 21.
        // This may not be necessary, since we also check whether a directory exists and has contents,
        // which would fail if the external storage state is neither MOUNTED nor MOUNTED_READ_ONLY.

        // I moved hard-coded paths toward the end, but we need to make sure we put the ones in
        // backwards order that are returned by the OS. And make sure the iterators respect
        // the order!
        // This is because when multiple "external" storage paths are returned, it's always (in
        // experience, but not guaranteed by documentation) with internal/emulated storage
        // first, removable storage second.

        // Add value of environment variables as candidates, if set:
        // EXTERNAL_STORAGE, SECONDARY_STORAGE, EXTERNAL_SDCARD_STORAGE
        // But note they are *not* necessarily *removable* storage! Especially EXTERNAL_STORAGE.
        // And they are not documented (API) features. Typically useful only for old versions of Android.

        String val = System.getenv("SECONDARY_STORAGE");
        if (!TextUtils.isEmpty(val)) addPath(val, null, candidatePaths);
        val = System.getenv("EXTERNAL_SDCARD_STORAGE");
        if (!TextUtils.isEmpty(val)) addPath(val, null, candidatePaths);

        // Get listing of mounted devices with their properties.
        ArrayList<File> mountedPaths = new ArrayList<>();
        try {
            // Note: Despite restricting some access to /proc (http://stackoverflow.com/a/38728738/423105),
            // Android 7.0 does *not* block access to /proc/mounts, according to our test on George's Alcatel A30 GSM.
            bufferedReader = new BufferedReader(new FileReader("/proc/mounts"));

            // Iterate over each line of the mounts listing.
            while ((lineRead = bufferedReader.readLine()) != null) {
                Log.d(TAG, "\nMounts line: " + lineRead);
                mountFields = lineRead.split(" ");

                // columns: device, mountpoint, fs type, options... Example:
                // /dev/block/vold/179:97 /storage/sdcard1 vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1000,gid=1015,fmask=0002,dmask=0002,allow_utime=0020,codepage=cp437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
                String device = mountFields[0], path = mountFields[1], fsType = mountFields[2];

                // The device, path, and fs type must conform to expected patterns.
                if (!(devicePattern.matcher(device).matches() &&
                        pathPattern.matcher(path).matches() &&
                        fsTypePattern.matcher(fsType).matches()) ||
                        // mtdblock is internal, I'm told.
                        device.contains("mtdblock") ||
                        // Check for disqualifying patterns in the path.
                        pathAntiPattern.matcher(path).matches()) {
                    // If this mounts line fails our tests, skip it.
                    continue;
                }

                // TODO maybe: check options to make sure it's mounted RW?
                // The answer at http://stackoverflow.com/a/13648873/423105 does.
                // But it hasn't seemed to be necessary so far in my testing.

                // This line met the criteria so far, so add it to candidate list.
                addPath(path, null, mountedPaths);
            }
        } catch (IOException ignored) {
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException ignored) {
                }
            }
        }

        // Append the paths from mount table to candidate list, in reverse order.
        if (!mountedPaths.isEmpty()) {
            // See https://stackoverflow.com/a/5374346/423105 on why the following is necessary.
            // Basically, .toArray() needs its parameter to know what type of array to return.
            File[] mountedPathsArray = mountedPaths.toArray(new File[mountedPaths.size()]);
            addAncestors(candidatePaths, mountedPathsArray);
        }

        // Add hard-coded known common paths to candidate list:
        addStrings(candidatePaths, commonPaths);

        // If the above doesn't work we could try the following other options, but in my experience they
        // haven't added anything helpful yet.

        // getExternalFilesDir() and getExternalStorageDirectory() typically something app-specific like
        //   /storage/sdcard1/Android/data/com.mybackuparchives.android/files
        // so we want the great-great-grandparent folder.

        // This may be non-removable.
        Log.d(TAG, "Environment.getExternalStorageDirectory():");
        addPath(null, ancestor(Environment.getExternalStorageDirectory()), candidatePaths);

        // Context.getExternalFilesDirs() is only available from API level 19. You can use
        // ContextCompat.getExternalFilesDirs() on earlier APIs, but it only returns one dir anyway.
        Log.d(TAG, "context.getExternalFilesDir(null):");
        addPath(null, ancestor(context.getExternalFilesDir(null)), candidatePaths);

        // "Returns absolute paths to application-specific directories on all external storage
        // devices where the application can place persistent files it owns."
        // We might be able to use these to deduce a higher-level folder that isn't app-specific.
        // Also, we apparently have to call getExternalFilesDir[s](), at least in KITKAT+, in order to ensure that the
        // "external files" directory exists and is available.
        Log.d(TAG, "ContextCompat.getExternalFilesDirs(context, null):");
        addAncestors(candidatePaths, ContextCompat.getExternalFilesDirs(context, null));
        // Very similar results:
        Log.d(TAG, "ContextCompat.getExternalCacheDirs(context):");
        addAncestors(candidatePaths, ContextCompat.getExternalCacheDirs(context));

        // TODO maybe: use getExternalStorageState(File path), with and without an argument, when
        // available. With an argument is available since API level 21.
        // This may not be necessary, since we also check whether a directory exists,
        // which would fail if the external storage state is neither MOUNTED nor MOUNTED_READ_ONLY.

        // A "public" external storage directory. But in my experience it doesn't add anything helpful.
        // Note that you can't pass null, or you'll get an NPE.
        final File publicDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
        // Take the parent, because we tend to get a path like /pathTo/sdCard/Music.
        addPath(null, publicDirectory.getParentFile(), candidatePaths);
        // EXTERNAL_STORAGE: may not be removable.
        val = System.getenv("EXTERNAL_STORAGE");
        if (!TextUtils.isEmpty(val)) addPath(val, null, candidatePaths);

        if (candidatePaths.isEmpty()) {
            Log.w(TAG, "No removable microSD card found.");
            return null;
        } else {
            Log.i(TAG, "\nFound potential removable storage locations: " + candidatePaths);
        }

        // Accept or eliminate candidate paths if we can determine whether they're removable storage.
        // In Lollipop and later, we can check isExternalStorageRemovable() status on each candidate.
        if (Build.VERSION.SDK_INT >= 21) {
            Iterator<File> itf = candidatePaths.iterator();
            while (itf.hasNext()) {
                File dir = itf.next();
                // handle illegalArgumentException if the path is not a valid storage device.
                try {
                    if (Environment.isExternalStorageRemovable(dir)
                        // && containsKnownFile(dir)
                            ) {
                        Log.i(TAG, dir.getPath() + " is removable external storage");
                        return dir;
                    } else if (Environment.isExternalStorageEmulated(dir)) {
                        Log.d(TAG, "Removing emulated external storage dir " + dir);
                        itf.remove();
                    }
                } catch (IllegalArgumentException e) {
                    Log.d(TAG, "isRemovable(" + dir.getPath() + "): not a valid storage device.", e);
                }
            }
        }

        // Continue trying to accept or eliminate candidate paths based on whether they're removable storage.
        // On pre-Lollipop, we only have singular externalStorage. Check whether it's removable.
        if (Build.VERSION.SDK_INT >= 9) {
            File externalStorage = Environment.getExternalStorageDirectory();
            Log.d(TAG, String.format(Locale.ROOT, "findSDCardPath: getExternalStorageDirectory = %s", externalStorage.getPath()));
            if (Environment.isExternalStorageRemovable()) {
                // Make sure this is a candidate.
                // TODO: Does this contains() work? Should we be canonicalizing paths before comparing?
                if (candidatePaths.contains(externalStorage)
                    // && containsKnownFile(externalStorage)
                        ) {
                    Log.d(TAG, "Using externalStorage dir " + externalStorage);
                    return externalStorage;
                }
            } else if (Build.VERSION.SDK_INT >= 11 && Environment.isExternalStorageEmulated()) {
                Log.d(TAG, "Removing emulated external storage dir " + externalStorage);
                candidatePaths.remove(externalStorage);
            }
        }

        // If any directory contains our special test file, consider that the microSD card.
        if (KNOWNFILE != null) {
            for (File dir : candidatePaths) {
                Log.d(TAG, String.format(Locale.ROOT, "findSdCardPath: Looking for known file in candidate path, %s", dir));
                if (containsKnownFile(dir)) return dir;
            }
        }

        // If we don't find the known file, still try taking the first candidate.
        if (!candidatePaths.isEmpty()) {
            Log.d(TAG, "No definitive path to SD card; taking the first realistic candidate.");
            return candidatePaths.iterator().next();
        }

        // If no reasonable path was found, give up.
        return null;
    }

    /** Add each path to the collection. */
    private static void addStrings(LinkedHashSet<File> candidatePaths, String[] newPaths) {
        for (String path : newPaths) {
            addPath(path, null, candidatePaths);
        }
    }

    /** Add ancestor of each File to the collection. */
    private static void addAncestors(LinkedHashSet<File> candidatePaths, File[] files) {
        for (int i = files.length - 1; i >= 0; i--) {
            addPath(null, ancestor(files[i]), candidatePaths);
        }
    }

    /**
     * Add a new candidate directory path to our list, if it's not obviously wrong.
     * Supply path as either String or File object.
     * @param strNew path of directory to add (or null)
     * @param fileNew directory to add (or null)
     */
    private static void addPath(String strNew, File fileNew, Collection<File> paths) {
        // If one of the arguments is null, fill it in from the other.
        if (strNew == null) {
            if (fileNew == null) return;
            strNew = fileNew.getPath();
        } else if (fileNew == null) {
            fileNew = new File(strNew);
        }

        if (!paths.contains(fileNew) &&
                // Check for paths known not to be removable SD card.
                // The antipattern check can be redundant, depending on where this is called from.
                !pathAntiPattern.matcher(strNew).matches()) {

            // Eliminate candidate if not a directory or not fully accessible.
            if (fileNew.exists() && fileNew.isDirectory() && fileNew.canExecute()) {
                Log.d(TAG, "  Adding candidate path " + strNew);
                paths.add(fileNew);
            } else {
                Log.d(TAG, String.format(Locale.ROOT, "  Invalid path %s: exists: %b isDir: %b canExec: %b canRead: %b",
                        strNew, fileNew.exists(), fileNew.isDirectory(), fileNew.canExecute(), fileNew.canRead()));
            }
        }
    }

    private static final String ANDROID_DIR = File.separator + "Android";

    private static File ancestor(File dir) {
        // getExternalFilesDir() and getExternalStorageDirectory() typically something app-specific like
        //   /storage/sdcard1/Android/data/com.mybackuparchives.android/files
        // so we want the great-great-grandparent folder.
        if (dir == null) {
            return null;
        } else {
            String path = dir.getAbsolutePath();
            int i = path.indexOf(ANDROID_DIR);
            if (i == -1) {
                return dir;
            } else {
                return new File(path.substring(0, i));
            }
        }
    }

    /** Returns true iff dir contains the special test file.
     * Assumes that dir exists and is a directory. (Is this a necessary assumption?) */
    private static boolean containsKnownFile(File dir) {
        if (KNOWNFILE == null) return false;

        File knownFile = new File(dir, KNOWNFILE);
        return knownFile.exists();
    }

    private static Pattern
            /** Pattern that SD card device should match */
            devicePattern = Pattern.compile("/dev/(block/.*vold.*|fuse)|/mnt/.*"),
    /** Pattern that SD card mount path should match */
    pathPattern = Pattern.compile("/(mnt|storage|external_sd|extsd|_ExternalSD|Removable|.*MicroSD).*",
            Pattern.CASE_INSENSITIVE),
    /** Pattern that the mount path should not match.
     * 'emulated' indicates an internal storage location, so skip it.
     * 'asec' is an encrypted package file, decrypted and mounted as a directory. */
    pathAntiPattern = Pattern.compile(".*(/secure|/asec|/emulated).*"),
    /** These are expected fs types, including vfat. tmpfs is not OK.
     * fuse can be removable SD card (as on Moto E or Asus ZenPad), or can be internal (Huawei G610). */
    fsTypePattern = Pattern.compile(".*(fat|msdos|ntfs|ext[34]|fuse|sdcard|esdfs).*");
}

PS

  • N'oubliez pas <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />dans le manifeste. Et au niveau API 23 et supérieur, assurez-vous d'utiliser checkSelfPermission/ requestPermissions.
  • Définissez KNOWNFILE = "myappfile" s'il y a un fichier ou un dossier que vous vous attendez à trouver sur la carte SD. Cela rend la détection plus précise.
  • De toute évidence, vous souhaiterez mettre en cache la valeur de findSdCardPath(),plutôt que de la recalculer chaque fois que vous en avez besoin.
  • Il y a un tas de logging ( Log.d()) dans le code ci-dessus. Il aide à diagnostiquer tous les cas où le bon chemin n'est pas trouvé. Commentez-le si vous ne voulez pas vous connecter.
LarsH
la source
Downvoters, pouvez-vous suggérer un moyen d'améliorer cette réponse?
LarsH
1

La seule solution de travail que j'ai trouvée était celle-ci qui utilise la réflexion

 /**
 * Get external sd card path using reflection
 * @param mContext
 * @param is_removable is external storage removable
 * @return
 */
private static String getExternalStoragePath(Context mContext, boolean is_removable) {

    StorageManager mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
    Class<?> storageVolumeClazz = null;
    try {
        storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
        Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
        Method getPath = storageVolumeClazz.getMethod("getPath");
        Method isRemovable = storageVolumeClazz.getMethod("isRemovable");
        Object result = getVolumeList.invoke(mStorageManager);
        final int length = Array.getLength(result);
        for (int i = 0; i < length; i++) {
            Object storageVolumeElement = Array.get(result, i);
            String path = (String) getPath.invoke(storageVolumeElement);
            boolean removable = (Boolean) isRemovable.invoke(storageVolumeElement);
            if (is_removable == removable) {
                return path;
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return null;
}
Habib Kazemi
la source
Personnellement, je ne préfère pas utiliser la réflexion car google n'apprécie pas la compatibilité descendante dans les nouvelles versions d'Android!
Behrouz.M
0

Je ne sais pas pourquoi mais je dois appeler .createNewFile () sur un fichier créé dans les répertoires de stockage public avant de l'utiliser. Dans le cadre, les commentaires de cette méthode disent qu'elle n'est pas utile. Voici un exemple ...


 String myPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PODCASTS) + File.separator + "My Directory";
            final File myDir = new File(myPath);
            try {
                myDir.mkdirs();
            } catch (Exception ex) {
                Toast.makeText(this, "error: " + ex.getMessage(), Toast.LENGTH_LONG).show();
            }

        String fname = "whatever";
        File newFile = new File(myDir, fname);

        Log.i(TAG, "File exists --> " + newFile.exists()) //will be false  
    try {
            if (newFile.createNewFile()) {

                 //continue 

              } else {

                Log.e(TAG, "error creating file");

            }

        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }

user1743524
la source
0

J'ai créé une méthode utils pour vérifier qu'une carte SD est disponible sur l'appareil ou non, et obtenir le chemin de la carte SD sur l'appareil si elle est disponible.

Vous pouvez copier 2 méthodes ci-dessous dans la classe de votre projet dont vous avez besoin. C'est tout.

public String isRemovableSDCardAvailable() {
    final String FLAG = "mnt";
    final String SECONDARY_STORAGE = System.getenv("SECONDARY_STORAGE");
    final String EXTERNAL_STORAGE_DOCOMO = System.getenv("EXTERNAL_STORAGE_DOCOMO");
    final String EXTERNAL_SDCARD_STORAGE = System.getenv("EXTERNAL_SDCARD_STORAGE");
    final String EXTERNAL_SD_STORAGE = System.getenv("EXTERNAL_SD_STORAGE");
    final String EXTERNAL_STORAGE = System.getenv("EXTERNAL_STORAGE");

    Map<Integer, String> listEnvironmentVariableStoreSDCardRootDirectory = new HashMap<Integer, String>();
    listEnvironmentVariableStoreSDCardRootDirectory.put(0, SECONDARY_STORAGE);
    listEnvironmentVariableStoreSDCardRootDirectory.put(1, EXTERNAL_STORAGE_DOCOMO);
    listEnvironmentVariableStoreSDCardRootDirectory.put(2, EXTERNAL_SDCARD_STORAGE);
    listEnvironmentVariableStoreSDCardRootDirectory.put(3, EXTERNAL_SD_STORAGE);
    listEnvironmentVariableStoreSDCardRootDirectory.put(4, EXTERNAL_STORAGE);

    File externalStorageList[] = null;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
        externalStorageList = getContext().getExternalFilesDirs(null);
    }
    String directory = null;
    int size = listEnvironmentVariableStoreSDCardRootDirectory.size();
    for (int i = 0; i < size; i++) {
        if (externalStorageList != null && externalStorageList.length > 1 && externalStorageList[1] != null)
            directory = externalStorageList[1].getAbsolutePath();
        else
            directory = listEnvironmentVariableStoreSDCardRootDirectory.get(i);

        directory = canCreateFile(directory);
        if (directory != null && directory.length() != 0) {
            if (i == size - 1) {
                if (directory.contains(FLAG)) {
                    Log.e(getClass().getSimpleName(), "SD Card's directory: " + directory);
                    return directory;
                } else {
                    return null;
                }
            }
            Log.e(getClass().getSimpleName(), "SD Card's directory: " + directory);
            return directory;
        }
    }
    return null;
}

/**
 * Check if can create file on given directory. Use this enclose with method
 * {@link BeginScreenFragement#isRemovableSDCardAvailable()} to check sd
 * card is available on device or not.
 * 
 * @param directory
 * @return
 */
public String canCreateFile(String directory) {
    final String FILE_DIR = directory + File.separator + "hoang.txt";
    File tempFlie = null;
    try {
        tempFlie = new File(FILE_DIR);
        FileOutputStream fos = new FileOutputStream(tempFlie);
        fos.write(new byte[1024]);
        fos.flush();
        fos.close();
        Log.e(getClass().getSimpleName(), "Can write file on this directory: " + FILE_DIR);
    } catch (Exception e) {
        Log.e(getClass().getSimpleName(), "Write file error: " + e.getMessage());
        return null;
    } finally {
        if (tempFlie != null && tempFlie.exists() && tempFlie.isFile()) {
            // tempFlie.delete();
            tempFlie = null;
        }
    }
    return directory;
}
user3161772
la source
-1

Son travail pour tous les périphériques externes, mais assurez-vous d'obtenir uniquement le nom du dossier du périphérique externe, puis vous devez obtenir le fichier à partir d'un emplacement donné à l'aide de la classe File.

public static List<String> getExternalMounts() {
        final List<String> out = new ArrayList<>();
        String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
        String s = "";
        try {
            final Process process = new ProcessBuilder().command("mount")
                    .redirectErrorStream(true).start();
            process.waitFor();
            final InputStream is = process.getInputStream();
            final byte[] buffer = new byte[1024];
            while (is.read(buffer) != -1) {
                s = s + new String(buffer);
            }
            is.close();
        } catch (final Exception e) {
            e.printStackTrace();
        }

        // parse output
        final String[] lines = s.split("\n");
        for (String line : lines) {
            if (!line.toLowerCase(Locale.US).contains("asec")) {
                if (line.matches(reg)) {
                    String[] parts = line.split(" ");
                    for (String part : parts) {
                        if (part.startsWith("/"))
                            if (!part.toLowerCase(Locale.US).contains("vold"))
                                out.add(part);
                    }
                }
            }
        }
        return out;
    }

Appel:

List<String> list=getExternalMounts();
        if(list.size()>0)
        {
            String[] arr=list.get(0).split("/");
            int size=0;
            if(arr!=null && arr.length>0) {
                size= arr.length - 1;
            }
            File parentDir=new File("/storage/"+arr[size]);
            if(parentDir.listFiles()!=null){
                File parent[] = parentDir.listFiles();

                for (int i = 0; i < parent.length; i++) {

                    // get file path as parent[i].getAbsolutePath());

                }
            }
        }

Accéder à un stockage externe

Pour lire ou écrire des fichiers sur le stockage externe, votre application doit acquérir les autorisations système READ_EXTERNAL_STORAGE ou WRITE_EXTERNAL_STORAGE . Par exemple:

<manifest ...>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    ...
</manifest>
Neeraj Singh
la source
-2

/ sdcard => Stockage interne (c'est un lien symbolique mais devrait fonctionner)

/ mnt / extSdCard => Carte Sd externe

C'est pour Samsung Galaxy S3

Vous pouvez probablement miser sur le fait que cela est vrai pour la plupart ... revérifiez cependant!

robbyoconnor
la source
8
J'ai eu plusieurs téléphones Android différents, environ la moitié d'entre eux Samsung, et je n'ai jamais vu cet emplacement utilisé. Cela peut être vrai sur le S3, mais dire "vous pouvez probablement miser sur le fait que cela est vrai pour la plupart" est complètement faux.
Geobits
faux. /sdcardest un lien symbolique vers externe sur mon sony 2305.
jiggunjer
2
N'ai-je pas dit que ce n'était peut-être pas le cas?
robbyoconnor