Trova la posizione di una scheda SD rimovibile


204

Esiste un modo universale per trovare la posizione di una scheda SD esterna?

Per favore, non essere confuso con la memoria esterna .

Environment.getExternalStorageState()restituisce il percorso al punto di montaggio SD interno, come "/ mnt / sdcard". Ma la domanda riguarda la SD esterna. Come posso ottenere un percorso come "/ mnt / sdcard / external_sd" (può differire da dispositivo a dispositivo)?

Immagino che finirò con il filtraggio dell'output del mountcomando in base al nome del file system. Ma non sono sicuro che questo modo sia abbastanza robusto.


Ecco la mia soluzione che funziona fino a Nougat: stackoverflow.com/a/40205116/5002496
Gokul NC

'Environment.getExternalStorageState () restituisce il percorso al punto di montaggio SD interno come "/ mnt / sdcard".' Bene, non è interno nel senso che Android usa il termine. Credo che il termine che stai cercando non sia "rimovibile".
LarsH,

Risposte:


162

Environment.getExternalStorageState() restituisce il percorso al punto di montaggio SD interno come "/ mnt / sdcard"

No, si Environment.getExternalStorageDirectory()riferisce a ciò che il produttore del dispositivo ha considerato "memoria esterna". Su alcuni dispositivi, si tratta di supporti rimovibili, come una scheda SD. Su alcuni dispositivi, questa è una parte del flash sul dispositivo. Qui, "memoria esterna" significa "materiale accessibile tramite la modalità di archiviazione di massa USB quando montato su una macchina host", almeno per Android 1.xe 2.x.

Ma la domanda riguarda la SD esterna. Come ottenere un percorso come "/ mnt / sdcard / external_sd" (potrebbe differire da dispositivo a dispositivo)?

Android non ha alcun concetto di "SD esterna", a parte l'archiviazione esterna, come descritto sopra.

Se un produttore di dispositivi ha scelto di disporre di una memoria esterna flash integrata e ha anche una scheda SD, sarà necessario contattare tale produttore per determinare se è possibile utilizzare la scheda SD (non garantita) e quali sono le regole usandolo, come quale percorso usare per esso.


AGGIORNARE

Due cose recenti da notare:

Innanzitutto, su Android 4.4+, non hai accesso in scrittura a supporti rimovibili (ad esempio, "SD esterna"), ad eccezione di eventuali posizioni su quel supporto che potrebbero essere restituite da getExternalFilesDirs()e getExternalCacheDirs(). Guarda l'eccellente analisi di Dave Smith su questo, in particolare se vuoi i dettagli di basso livello.

In secondo luogo, affinché nessuno possa discutere se l'accesso ai supporti rimovibili sia o meno parte dell'SDK di Android, ecco la valutazione di Dianne Hackborn :

... tenere a mente: fino Android 4.4, la piattaforma ufficiale di Android non ha sostenuto le schede SD a tutti tranne che per due casi particolari: il vecchio tracciato di stoccaggio scuola dove archiviazione esterna è una scheda SD (che è ancora supportato dalla piattaforma di oggi) e una piccola funzionalità aggiunta ad Android 3.0 in cui scansionava ulteriori schede SD e le aggiungeva al provider multimediale e forniva alle app l'accesso in sola lettura ai propri file (che è ancora supportato nella piattaforma oggi).

Android 4.4 è la prima versione della piattaforma che ha effettivamente consentito alle applicazioni di utilizzare schede SD per l'archiviazione. Qualsiasi accesso ad essi prima di ciò avveniva tramite API private e non supportate. Ora disponiamo di un'API piuttosto ricca nella piattaforma che consente alle applicazioni di utilizzare le schede SD in modo supportato, in modi migliori rispetto a prima: possono utilizzare gratuitamente l'area di archiviazione specifica dell'app senza richiedere alcuna autorizzazioni nell'app e possono accedere a tutti gli altri file sulla scheda SD purché passino attraverso il selettore file, senza bisogno di autorizzazioni speciali.


4
E questo problema sta diventando sempre più un problema man mano che i dispositivi HC e ICS escono quel punto "ExternalStorageDirectory" e tutto il resto piace alla memoria fisica interna. Inoltre, la maggior parte degli utenti non ha assolutamente idea di come individuare la propria sdcard nel filesystem.
Tony Maro,

284
Quindi la tua risposta è sostanzialmente "contatta il produttore". Inutile.
Dragonroot,

6
L'ultima parte della risposta non è del tutto precisa: è infatti possibile rilevare il percorso della scheda SD seguendo le risposte sottostanti (scan / proc / mounts, /system/etc/vold.fstab, ecc ...).
Impara OpenGL ES il

8
@CommonsWare: Nondimeno, non è ancora corretto che uno "abbia bisogno" di contattare un produttore quando ci sono soluzioni che funzionano su molti dispositivi là fuori, e inoltre, l'SDK stesso non è coerente su tutti i dispositivi, quindi non è una garanzia. Anche se queste soluzioni non funzionano su tutti i dispositivi, funzionano su dispositivi sufficienti che molte app Android sul mercato fanno affidamento su queste tecniche, tra le altre, per rilevare il percorso della scheda SD esterna. Penso che sia un po 'duro e prematuro chiamare tutti questi sviluppatori pazzi - non è sicuramente il cliente il giudice finale di quello?
Impara OpenGL ES il

5
@CommonsWare È abbastanza giusto, come vanno le cose. Sono assolutamente d'accordo con te sul fatto che uno sviluppatore non può presumere che funzionerà sempre ovunque e che non è possibile garantire che tale codice funzioni su tutti i dispositivi o per tutte le versioni di Android. Spero che venga corretto nell'SDK! Nel frattempo, ci sono ancora opzioni che funzionano su molti dispositivi e possono migliorare l'esperienza dell'utente finale e, data la scelta tra successo dell'80% e successo dello 0%, prenderò l'80%.
Impara OpenGL ES il

64

Ho trovato la seguente soluzione in base ad alcune risposte trovate qui.

CODICE:

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

USO:

Map<String, File> externalLocations = ExternalStorage.getAllStorageLocations();
File sdCard = externalLocations.get(ExternalStorage.SD_CARD);
File externalSdCard = externalLocations.get(ExternalStorage.EXTERNAL_SD_CARD);

1
testato con nexus 4, nexus s, galaxy s2, galaxy s3, htc desire =)
Richard

2
Ciao di nuovo, Richard, che ci crediate o no, devo chiederlo: hai davvero provato a scrivere e rileggere in un file in questo modo, non hai appena preso le directory? Ricorda il nostro vecchio problema "/ sdcard0"? Ho provato questo codice e non è riuscito su un S3 quando ho provato a rileggere nel file che ha scritto. ... è molto bizzarro ... e doloroso :))
Howard Pautz,

10
Questo non funziona su dispositivi che non hanno 2 schede SD. Presuppone che il 1 ° trovato sia interno e il 2 ° trovato sia esterno ...
Caner,

NON ha funzionato per i dispositivi USB collegati tramite cavo OTG su Nexus 5 e Nexus 7.
Khawar Raza,

4
/system/etc/vold.fstab non è accessibile in Android 4.3+
Ali

37

Avevo un'applicazione che utilizzava un punto in ListPreferencecui l'utente era tenuto a selezionare la posizione in cui voleva salvare qualcosa.

In quell'app, ho scansionato /proc/mounts e /system/etc/vold.fstabcercato punti di montaggio sdcard. Ho memorizzato i punti di montaggio di ciascun file in due ArrayLists separati .

Quindi, ho confrontato un elenco con l'altro e ho scartato gli elementi che non erano in entrambi gli elenchi. Questo mi ha dato un elenco di percorsi di root per ogni sdcard.

Da lì, ho testato i percorsi con File.exists(),File.isDirectory() e File.canWrite(). Se qualcuno di questi test fosse falso, ho scartato quel percorso dall'elenco.

Qualunque cosa fosse rimasta nell'elenco, l'ho convertita in a String[] array in modo che potesse essere utilizzato dall'attributo ListPreferencevalori.

È possibile visualizzare il codice qui: http://sapienmobile.com/?p=204


Cordiali saluti, questo non funziona su Galaxy S3, 2 schede SD, solo una elencata in vold.conf
3c71

1
@ 3c71 - Puoi inviarmi i file vold e monta per Galaxy S3? Modificherò il codice per coprirlo.
Barone,

Galaxy S, tutti i percorsi trovati non erano scrivibili, strani. Sono stati trovati due archivi, default / mnt / sdcard e / storage / sdcard0, entrambi non riusciti a testare
quinto

1
Ho modificato il codice per ignorare il file dei montaggi. Questo era il problema sui dispositivi Motorola e Samsung. Il file mounts non copriva il caso external_sd, ma è elencato in vold. La versione iniziale della mia classe confrontava montaggi con oggetti vecchi e scartati che non erano comuni a entrambi. Prendi la classe aggiornata dallo stesso link sopra.
Barone,

1
Grazie Barone, questa è la "risposta"; almeno quello utile.
pstoppani,

23

Puoi provare a usare la funzione di libreria di supporto chiamata 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());

Il primo è l'archiviazione esterna primaria e il resto dovrebbe essere un vero percorso per schede SD.

Il motivo del multiplo ".getParentFile ()" è quello di andare su un'altra cartella, poiché il percorso originale è

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

EDIT: ecco un modo più completo che ho creato, per ottenere i percorsi delle schede 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;
      }
    }

Sembra un'ottima risposta, ma come si integrerebbe in una semplice attività? Ci sono diverse variabili non definite, come App, ContextCompact,EnvironmentCompact
Antonio

@Antonio ContextCompact, EnvironmentCompact sono disponibili tramite la libreria di supporto. "App.global ()" è solo il contesto dell'applicazione, che ho impostato a livello globale poiché non mi piace aggiungere un parametro Context ovunque.
sviluppatore Android

1
Grande! Funziona con il mio dispositivo v4.4 Samsung GT S Advance, spero che funzionerà per altri
user25

@androiddeveloper La risposta modificata funzionerà per tutti i dispositivi e le dimensioni della scheda SD?
Rahulrr2602

1
Questo ha funzionato perfettamente per me - dovrebbe essere la risposta accettata.
Paradosso

17

Per recuperare tutti gli archivi esterni (siano essi schede SD o interni non rimovibili ), è possibile utilizzare il seguente codice:

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

In alternativa, potresti usare System.getenv ("EXTERNAL_STORAGE") per recuperare la directory di archiviazione esterna primaria (ad es. "/ Storage / sdcard0" ) e System.getenv ("SECONDARY_STORAGE") per recuperare l'elenco di tutte le directory secondarie (ad es. " / storage / extSdCard: / storage / UsbDriveA: / storage / UsbDriveB " ). Ricorda che, anche in questo caso, potresti voler filtrare l'elenco delle directory secondarie per escludere le unità USB.

In ogni caso, tieni presente che l'utilizzo di percorsi codificati è sempre un approccio errato (soprattutto quando ogni produttore può cambiarlo a proprio piacimento).


2
Considera solo un downvoter che non lascia un commento un troll, quindi ho votato per compensarlo. ;) MA, immagino che il tuo metodo sia piuttosto arbitrario: come possiamo sapere che saltare quelle "unità USB" ma mantenere tutto il resto è davvero uguale a "sdcard", come è stato posto nella domanda? Anche il tuo suggerito System.getenv("SECONDARY_STORAGE")potrebbe fare anche con alcuni riferimenti, in quanto sembra non documentato.
Sz.

1
Per quanto ne so, nell'API di Android non esiste alcun riferimento a un metodo standard per recuperare tutti gli archivi esterni. Tuttavia, il metodo proposto non è affatto arbitrario. In Android, come in tutti i sistemi Unix / Linux, TUTTI i dispositivi di archiviazione di montaggio sono memorizzati / collegati in una directory comune: "/ mnt" (la directory standard Unix / Linux per il montaggio di dispositivi di archiviazione) o, nelle versioni più recenti, "/ Conservazione". Ecco perché puoi essere abbastanza sicuro di trovare tutte le schede SD collegate in questa cartella.
Paolo Rovelli,

1
Per quanto riguarda il metodo System.getenv ("EXTERNAL_STORAGE"), non ho alcun riferimento piuttosto che la pagina API (che non spiega molto): developer.android.com/reference/java/lang/… Non sono riuscito a trovare alcun pagina ufficiale per le variabili di ambiente del sistema Android. Qui, tuttavia, puoi trovarne un breve elenco: herongyang.com/Android/…
Paolo Rovelli,

Quello che volevo dire non essere sicuro delle schede SD è che /mntci possono essere anche altri alberi di fs, non solo schede SD e unità USB. Il tuo codice dovrebbe anche elencare qualsiasi mount interno (forse persino virtuale) del filesystem, se ho capito bene, mentre la domanda vuole solo le sdcard .
Sz.

1
Vedo. Si hai ragione. Con il mio metodo recupererai anche le memorie SD interne (non rimovibili).
Paolo Rovelli,

15

Come Richard, uso anche il file / proc / mounts per ottenere l'elenco delle opzioni di archiviazione disponibili

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

Grazie. Ha funzionato perfettamente. E mi piace il modo in cui hai reso StorageInfo non modificabile. D'altra parte printStackTrace? Quando abbiamo android.util.Log.e?
Martin,

1
NON ha funzionato per i dispositivi USB collegati tramite cavo OTG su Nexus 5 e Nexus 7.
Khawar Raza,

1
Non posso usarlo per scrivere file su SDCard
Eu Vid

lo stesso problema di @EuVid funziona su VM / AVD ma non sull'hardware
spia il

11

È possibile trovare dove sono montate eventuali schede SD aggiuntive leggendo /proc/mounts(file Linux standard) e verificando i dati vold ( /system/etc/vold.conf). E nota, che la posizione restituita da Environment.getExternalStorageDirectory()potrebbe non apparire in una configurazione vold (in alcuni dispositivi è la memoria interna che non può essere smontata), ma deve comunque essere inclusa nell'elenco. Tuttavia non abbiamo trovato un buon modo per descriverli all'utente .


Imo, l'uso di mountè più compatibile della lettura del /procfilesystem. Il problema è che la scheda SD non è necessariamente formattata come FAT. Inoltre, il punto di montaggio della scheda può variare da ROM a ROM. Inoltre, potrebbero esserci diverse altre partizioni VFAT ...
Borisstr,

1
@borisstr: Hm, in realtà Android usa Vold , quindi anche guardare la sua configurazione è appropriato.
Jan Hudec,

Il file di codice che ho condiviso dal mio post sopra include un metodo per descrivere all'utente i percorsi root rilevati. Guarda il metodo setProperties () .
Barone,

1
@borisstr, in realtà no, la lettura / proc / mounts è più portatile sui dispositivi Android rispetto al lancio mountdell'eseguibile, soprattutto perché il lancio di eseguibili è scoraggiato.
Chris Stratton,

7

In questo momento provo tutte le soluzioni all'interno di questo argomento. Ma tutti non funzionavano correttamente su dispositivi con una scheda esterna (rimovibile) e una interna (non rimovibile). Il percorso della scheda esterna non è possibile ottenere dal comando 'mount', dal file 'proc / mounts' ecc.

E creo la mia soluzione (su 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();

6

Se guardi il codice sorgente android.os.Environment, vedrai che Android si affida fortemente alle variabili di ambiente per i percorsi. È possibile utilizzare la variabile di ambiente "SECONDARY_STORAGE" per trovare il percorso della scheda SD rimovibile.

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

Esempio di utilizzo:

public static final File REMOVABLE_STORAGE = getDirectory("SECONDARY_STORAGE");

5

Basta semplicemente usare questo:

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)

Su alcuni dispositivi SECONDARY_STORAGEha diversi percorsi separati da due punti (":"). Questo è il motivo per cui ho diviso la stringa (vedi la mia risposta sopra).
Jared Rummler,

Entrambi restituiscono null per me.
Tim Cooper

5

Esiste un modo universale per trovare la posizione di una scheda SD esterna?

Per via universale , se intendi via ufficiale; Sì, ce n'è uno.

Nel livello API 19, ovvero nella versione 4.4 di Android Kitkat, sono stati aggiunti File[] getExternalFilesDirs (String type)in Contextclasse che consente alle app di archiviare dati / file su schede micro SD.

Android 4.4 è la prima versione della piattaforma che ha effettivamente consentito alle app di utilizzare schede SD per l'archiviazione. Qualsiasi accesso alle schede SD prima del livello API 19 avveniva tramite API private e non supportate.

getExternalFilesDirs (tipo stringa) restituisce percorsi assoluti alle directory specifiche dell'applicazione su tutti i dispositivi di archiviazione condivisi / esterni. Significa che restituirà i percorsi alla memoria interna ed esterna. Generalmente, il secondo percorso restituito sarebbe il percorso di archiviazione per la scheda microSD (se presente).

Ma nota che,

L'archiviazione condivisa potrebbe non essere sempre disponibile, poiché l'utente può rimuovere il supporto rimovibile. Lo stato dei media può essere verificato utilizzando getExternalStorageState(File).

Non esiste alcuna sicurezza applicata con questi file. Ad esempio, qualsiasi applicazione in attesaWRITE_EXTERNAL_STORAGE può scrivere su questi file.

La terminologia di archiviazione interna ed esterna secondo Google / documenti Android ufficiali è abbastanza diversa da ciò che pensiamo.


"La terminologia di archiviazione interna ed esterna secondo Google / documenti Android ufficiali è molto diversa da ciò che pensiamo." Sì, in effetti il ​​titolo della domanda chiarisce che l'OP chiede una scheda SD rimovibile . getExternalFilesDirs()restituisce spesso schede SD non rimovibili, quindi no, questo non è un modo universale per trovare la posizione di una scheda SD rimovibile.
LarsH

"getExternalFilesDirs (tipo String) restituisce percorsi assoluti alle directory specifiche dell'applicazione su tutti i dispositivi di archiviazione condivisi / esterni. Significa che restituirà i percorsi alla memoria interna ed esterna." Questa coppia di frasi è molto fuorviante, perché per essere entrambe vere, "esterno" deve significare due cose diverse e contrastanti.
LarsH

4

Ecco il modo in cui uso per trovare la scheda esterna. Utilizzare mount cmd return quindi analizzare la parte 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.
        }
    }
}
}

4

Questa soluzione gestisce il fatto che System.getenv("SECONDARY_STORAGE")non è utile con Marshmallow.

Testato e funzionante su:

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

2

Dalla mia risposta originale sopra, la scansione di Vold non è più praticabile tra i vari produttori.

Ho sviluppato un metodo più affidabile e diretto.

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

root conterrà tutte le directory root scrivibili sul sistema, inclusi tutti i dispositivi USB USB collegati.

NOTA: il metodo canWrite richiede l'autorizzazione android.permission.WRITE_EXTERNAL_STORAGE.


Il metodo isSymlink (File) non è definito per il tipo new FileFilter () {}
Omid Omidi

Qualche idea se questo non riuscirà a trovare schede SD esterne su Android 4.4 a causa di canWrite?
Anthony,

Questo è certamente più semplice del tuo altro metodo, ma è affidabile? Ad esempio, l'ho letto su alcuni dispositivi Samsung, /external_sdè la scheda microSD esterna; su alcuni LG, è /_ExternalSD; su alcuni dispositivi è /sdcard. Forse quest'ultimo è un collegamento simbolico /storage/sdcard0o simile, ma questi altri saranno davvero coperti in modo affidabile da /storage/*e /mount/*?
LarsH,

Inoltre, è necessario utilizzare pathname.canWrite()e richiedere l'autorizzazione WRITE_EXTERNAL_STORAGE? Perché non chiamare pathname.canRead()?
LarsH,

1

era così tardi ma finalmente ho ottenuto qualcosa che ho testato la maggior parte dei dispositivi (dal produttore e dalle versioni di Android) funziona su Android 2.2+. se scopri che non funziona, commentalo con il nome del tuo dispositivo. io lo aggiusterò. se qualcuno è interessato spiegherò come funziona.

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

    }

}

Ehi downvoter, prova prima. Se non funziona, commenta il tuo dispositivo. lo stiamo usando su migliaia di dispositivi con Android 2.2+
Ajeet47

La tua classe mi dà / mnt / media_rw / extSdCard su Samsung Galaxy S4, GT-I9500, Android 5.0.1 (il dispositivo NON è rootato). Ma non c'è nulla di visibile nella cartella / mnt / media_rw con 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

Cosa ne pensi di stackoverflow.com/a/27197248/753575 ? Questo approccio è più completo nel caso di Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT?
Isabsent,

sì. è garantito che otterrai un percorso significativo da context.getExternalFilesDirs (null), ma lo hai tagliato per il percorso root (restituirà il percorso per la tua directory dell'app.
taglialo

1

Scrivendo sotto il codice otterrai la posizione:

/ Garage / 663D-554E / Android / dati / app_package_name / files /

che memorizza i dati della tua app in / android / posizione dei dati all'interno della sd_card.

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

list[1]+"/fol" 

per ottenere la posizione passa 0 per interno e 1 per sdcard all'array di file.

Ho testato questo codice su un moto g4 plus e un dispositivo Samsung (tutto funziona bene).

spero che questo possa essere utile.


a volte il percorso della scheda SD non è sull'indice 1, ho visto i casi in cui era sull'indice 0. meglio seguire qualcos'altro
Raghav Satyadev

1

Ecco il metodo che uso per trovare un rimovibile scheda SD . È complesso e probabilmente eccessivo per alcune situazioni, ma funziona su una vasta gamma di versioni di Android e produttori di dispositivi che ho testato negli ultimi anni. Non conosco alcun dispositivo dal livello API 15 su cui non trova la scheda SD, se ne è installata una. Nella maggior parte dei casi non restituirà falsi positivi, soprattutto se gli dai il nome di un file noto da cercare.

Per favore fatemi sapere se vi imbattete in casi in cui non funziona.

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

  • Non dimenticare <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />nel manifest. E a livello di API 23 e versioni successive, assicurati di utilizzare checkSelfPermission/requestPermissions .
  • Imposta KNOWNFILE = "myappfile" se c'è un file o una cartella che ti aspetti di trovare sulla scheda SD. Ciò rende il rilevamento più accurato.
  • Ovviamente ti consigliamo di memorizzare nella cache il valore di findSdCardPath(), anziché ricalcolarlo ogni volta che ne hai bisogno.
  • C'è un sacco di logging ( Log.d()) nel codice sopra. Aiuta a diagnosticare eventuali casi in cui non viene trovato il percorso giusto. Commentalo se non vuoi registrarti.

Downvoter, puoi suggerire un modo per migliorare questa risposta?
LarsH,

1

L'unica soluzione funzionante che ho trovato è stata questa che utilizza la riflessione

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

Personalmente non preferisco usare la riflessione perché google non apprezza la compatibilità con le versioni precedenti di Android!
Behrouz.M

0

Non so perché, ma devo chiamare .createNewFile () su un file creato nelle directory di archiviazione pubblica prima di usarlo. Nel quadro i commenti per quel metodo dicono che non è utile. Ecco un esempio ...


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


0

Ho creato un metodo utils per verificare se una scheda SD è disponibile sul dispositivo o meno e ottenere il percorso della scheda SD sul dispositivo se disponibile.

Puoi copiare 2 metodi sotto la classe del tuo progetto di cui hai bisogno. È tutto.

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

-1

Funziona per tutti i dispositivi esterni, ma assicurati di ottenere solo il nome della cartella del dispositivo esterno e quindi devi ottenere il file da una determinata posizione utilizzando 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;
    }

Calling:

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

                }
            }
        }

Accesso a memoria esterna

Per leggere o scrivere file sulla memoria esterna, l'app deve acquisire le autorizzazioni di sistema READ_EXTERNAL_STORAGE o WRITE_EXTERNAL_STORAGE . Per esempio:

<manifest ...>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    ...
</manifest>

-2

/ sdcard => Memoria interna (è un collegamento simbolico ma dovrebbe funzionare)

/ mnt / extSdCard => Sdcard esterna

Questo è per Samsung Galaxy S3

Probabilmente puoi scommettere che questo è vero per la maggior parte ... doppio controllo comunque!


8
Ho avuto diversi telefoni Android, circa la metà di loro Samsung, e non ho mai visto questa posizione utilizzata. Potrebbe essere vero su S3, ma dire "probabilmente puoi contare su questo essere vero per la maggior parte" è completamente sbagliato.
Geobits il

sbagliato. /sdcardè un link simbolico ad esterno sul mio Sony 2305.
jiggunjer,

2
Non ho detto che potrebbe non essere?
robbyoconnor,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.