Encuentra la ubicación de una tarjeta SD extraíble

204

¿Existe una forma universal de encontrar la ubicación de una tarjeta SD externa?

Por favor, no se confunda con el almacenamiento externo .

Environment.getExternalStorageState()devuelve la ruta al punto de montaje SD interno, como "/ mnt / sdcard". Pero la pregunta es sobre la SD externa. ¿Cómo obtengo una ruta como "/ mnt / sdcard / external_sd" (puede diferir de un dispositivo a otro)?

Supongo que terminaré con el filtrado de la salida del mountcomando por nombre de sistema de archivos. Pero no estoy seguro de que esta manera sea lo suficientemente robusta.

borisstr
fuente
Aquí está mi solución que funciona hasta Turrón: stackoverflow.com/a/40205116/5002496
Gokul NC
'Environment.getExternalStorageState () devuelve la ruta al punto de montaje SD interno como "/ mnt / sdcard". Bueno, no es interno en el sentido de que Android usa el término. Creo que el término que está buscando "no es removible".
LarsH

Respuestas:

162

Environment.getExternalStorageState() devuelve la ruta al punto de montaje SD interno como "/ mnt / sdcard"

No, se Environment.getExternalStorageDirectory()refiere a lo que el fabricante del dispositivo considera "almacenamiento externo". En algunos dispositivos, este es un medio extraíble, como una tarjeta SD. En algunos dispositivos, esta es una parte del flash del dispositivo. Aquí, "almacenamiento externo" significa "las cosas accesibles a través del modo de almacenamiento masivo USB cuando se monta en una máquina host", al menos para Android 1.xy 2.x.

Pero la pregunta es sobre SD externo. ¿Cómo obtener una ruta como "/ mnt / sdcard / external_sd" (puede diferir de un dispositivo a otro)?

Android no tiene el concepto de "SD externo", aparte del almacenamiento externo, como se describió anteriormente.

Si el fabricante de un dispositivo ha elegido que el almacenamiento externo sea flash incorporado y también tenga una tarjeta SD, deberá ponerse en contacto con ese fabricante para determinar si puede usar la tarjeta SD (no garantizada) y cuáles son las reglas para usándolo, como qué ruta usar para él.


ACTUALIZAR

Dos cosas recientes de nota:

Primero, en Android 4.4+, no tiene acceso de escritura a medios extraíbles (p. Ej., "SD externo"), a excepción de cualquier ubicación en ese medio que pueda ser devuelta por getExternalFilesDirs()y getExternalCacheDirs(). Vea el excelente análisis de Dave Smith sobre esto, especialmente si desea los detalles de bajo nivel.

En segundo lugar, para que nadie cuestione si el acceso a medios extraíbles es o no parte del SDK de Android, aquí está la evaluación de Dianne Hackborn :

... tener en cuenta: hasta Android 4.4, la plataforma oficial de Android no ha apoyado las tarjetas SD en todo excepto en dos casos especiales: el diseño de almacenamiento de la vieja escuela, donde el almacenamiento externo es una tarjeta SD (que todavía está soportado por la plataforma en la actualidad) , y una pequeña característica agregada a Android 3.0 donde escanearía tarjetas SD adicionales y las agregaría al proveedor de medios y les daría a las aplicaciones acceso de solo lectura a sus archivos (que también es compatible con la plataforma hoy en día).

Android 4.4 es el primer lanzamiento de la plataforma que ha permitido que las aplicaciones usen tarjetas SD para almacenamiento. Cualquier acceso a ellos antes de eso era a través de API privadas no compatibles. Ahora tenemos una API bastante rica en la plataforma que permite que las aplicaciones hagan uso de las tarjetas SD de una manera compatible, de una mejor manera que antes: pueden hacer uso gratuito del área de almacenamiento específica de su aplicación sin requerir ninguna permisos en la aplicación, y puede acceder a cualquier otro archivo en la tarjeta SD siempre que pase por el selector de archivos, nuevamente sin necesidad de ningún permiso especial.

CommonsWare
fuente
44
Y este problema se está volviendo más problemático a medida que los dispositivos HC e ICS salen en ese punto "ExternalStorageDirectory" y todo lo demás se parece al almacenamiento físico interno. Además, la mayoría de los usuarios no tienen ni idea de cómo ubicar su tarjeta sd en el sistema de archivos.
Tony Maro
284
Entonces su respuesta es básicamente 'contactar al fabricante'. Inútil.
dragonroot
66
La última parte de la respuesta no es del todo precisa: de hecho, es posible detectar la ruta de la tarjeta SD siguiendo las respuestas a continuación (escaneo / proc / montajes, /system/etc/vold.fstab, etc.).
Aprenda OpenGL ES
8
@CommonsWare: Sin embargo, aún no es exacto que uno "necesite" ponerse en contacto con un fabricante cuando hay soluciones que funcionan en muchos dispositivos, y además, el SDK en sí no es consistente en todos los dispositivos, por lo que no es una garantía. Incluso si estas soluciones no funcionan en todos los dispositivos, funcionan en dispositivos suficientes que muchas aplicaciones de Android en el mercado dependen de estas técnicas, entre otras, para detectar la ruta de la tarjeta SD externa. Creo que es un poco duro y prematuro llamar a todos estos desarrolladores tontos. ¿No es el cliente el juez final de eso?
Aprenda OpenGL ES
55
@CommonsWare Eso es bastante justo, como van las cosas. Definitivamente estoy de acuerdo con usted en que un desarrollador no puede asumir que esto siempre funcionará en todas partes, y que no se puede garantizar que dicho código funcione en todos los dispositivos o en todas las versiones de Android. ¡Ojalá se arregle en el SDK! Mientras tanto, todavía hay opciones que funcionan en muchos dispositivos y pueden mejorar la experiencia del usuario final, y dada la opción entre 80% de éxito y 0% de éxito, tomaré el 80%.
Aprenda OpenGL ES
64

Se me ocurrió la siguiente solución basada en algunas respuestas encontradas aquí.

CÓDIGO:

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);
Ricardo
fuente
1
probado con nexus 4, nexus s, galaxy s2, galaxy s3, htc desire =)
Richard
2
Hola de nuevo, Richard. Créalo o no, tengo que preguntar: ¿realmente intentó escribir y volver a leer un archivo de esta manera, no solo obtener los directorios? ¿Recuerdas nuestro viejo problema "/ sdcard0"? Probé este código y falló en un S3 cuando intenté volver a leer el archivo que sí escribió. ... esto es muy extraño ... y doloroso :))
Howard Pautz
10
Esto falla en dispositivos que no tienen 2 tarjetas SD. Se supone que el primer hallazgo es interno y el segundo encontrado es externo ...
Caner
NO funcionó para dispositivos USB conectados a través del cable OTG en Nexus 5 y Nexus 7.
Khawar Raza
44
/system/etc/vold.fstab no es accesible en Android 4.3+
Ali
37

Tenía una aplicación que usaba un lugar ListPreferencedonde el usuario debía seleccionar la ubicación donde deseaba guardar algo.

En esa aplicación, escaneé /proc/mountsy en /system/etc/vold.fstabbusca de puntos de montaje de tarjeta SD. Almacené los puntos de montaje de cada archivo en dos correos separados ArrayList.

Luego, comparé una lista con la otra y descarté elementos que no estaban en ambas listas. Eso me dio una lista de rutas raíz para cada tarjeta SD.

A partir de ahí, he probado con los caminos File.exists(), File.isDirectory()y File.canWrite(). Si alguna de esas pruebas era falsa, descarté esa ruta de la lista.

Lo que quedaba en la lista, lo convertí a una String[]matriz para que pudiera ser utilizado por el ListPreferenceatributo de valores.

Puede ver el código aquí: http://sapienmobile.com/?p=204

Barón
fuente
FYI, esto no funciona en Galaxy S3, 2 tarjetas SD, solo una listada en vold.conf
3c71 el
1
@ 3c71 - ¿Puedes enviarme los archivos vold y de montaje para Galaxy S3? Ajustaré el código para cubrirlo.
Barón
Galaxy S, todos los caminos encontrados no se podían escribir, extraño. Se encontraron dos almacenamientos, por defecto / mnt / sdcard y / storage / sdcard0, ambos no pudieron probar
quinto
1
He modificado el código para ignorar el archivo de montajes. Ese era el problema en los dispositivos Motorola y Samsung. El archivo de montajes no cubrió el caso external_sd, pero se encuentra en vold. La versión inicial de mi clase comparó monturas con elementos vold y descartados que no eran comunes a ambos. Tome la clase actualizada desde el mismo enlace de arriba.
barón
1
Gracias, barón, esta es "la" respuesta; Al menos el útil.
pstoppani
23

Puede intentar usar la función de biblioteca de soporte llamada 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());

El primero es el almacenamiento externo primario, y el resto se supone que son rutas de tarjetas SD reales.

La razón del múltiple ".getParentFile ()" es subir otra carpeta, ya que la ruta original es

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

EDITAR: aquí hay una forma más completa que he creado, para obtener las rutas de las tarjetas 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;
      }
    }
desarrollador de Android
fuente
Parece una muy buena respuesta, pero ¿cómo se integraría esto en una actividad simple? Hay varias variables no definidas, como App, ContextCompact,EnvironmentCompact
Antonio
@Antonio ContextCompact, EnvironmentCompact están disponibles a través de la biblioteca de soporte. El "App.global ()" es solo el contexto de la aplicación, que he configurado globalmente ya que no me gusta agregar un parámetro Context en todas partes.
Desarrollador de Android
1
¡Excelente! Funciona para el dispositivo mío v4.4 Samsung GT S Advance, espero que funcione para otros
usuario25
@androiddeveloper ¿La respuesta editada funcionará para todos los dispositivos y tamaños de tarjeta SD?
Rahulrr2602
1
Esto funcionó perfectamente para mí, debería ser la respuesta aceptada.
Paradoja
17

Para recuperar todos los almacenamientos externos (ya sean tarjetas SD o almacenamientos internos no extraíbles ), puede usar el siguiente código:

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

Alternativamente, puede usar System.getenv ("EXTERNAL_STORAGE") para recuperar el directorio primario de almacenamiento externo (por ejemplo, "/ storage / sdcard0" ) y System.getenv ("SECONDARY_STORAGE") para recuperar la lista de todos los directorios secundarios (por ejemplo, " / storage / extSdCard: / storage / UsbDriveA: / storage / UsbDriveB " ). Recuerde que, también en este caso, es posible que desee filtrar la lista de directorios secundarios para excluir las unidades USB.

En cualquier caso, tenga en cuenta que el uso de rutas codificadas siempre es un mal enfoque (especialmente cuando cada fabricante puede cambiarlo a su gusto).

Paolo Rovelli
fuente
2
Solo considere a cualquier votante que no deje un comentario como un troll, así que voté para compensarlo. ;) PERO, supongo que su método es bastante arbitrario: ¿cómo podemos saber que omitir esas "unidades USB" pero mantener todo lo demás es realmente igual a "tarjetas SD", como se hizo en la pregunta? También su sugerencia System.getenv("SECONDARY_STORAGE")también podría hacer con algunas referencias, ya que parece indocumentado.
Sz.
1
Hasta donde yo sé, en la API de Android no hay referencia de un método estándar para recuperar todos los almacenamientos externos. Sin embargo, el método propuesto no es arbitrario en absoluto. En Android, como en todos los sistemas Unix / Linux, TODOS los dispositivos de almacenamiento de almacenamiento se almacenan / vinculan en un directorio común: "/ mnt" (el directorio estándar de Unix / Linux para dispositivos de almacenamiento de montaje) o, en las versiones más recientes, "/ almacenamiento". Es por eso que puede estar bastante seguro de que encontrará todas las tarjetas SD vinculadas en esta carpeta.
Paolo Rovelli
1
Con respecto al método System.getenv ("EXTERNAL_STORAGE"), no tengo ninguna referencia en lugar de la página API (que no explica mucho): developer.android.com/reference/java/lang/… No pude encontrar ninguna página oficial para las variables de entorno del sistema Android. Aquí, sin embargo, puede encontrar una breve lista de ellos: herongyang.com/Android/…
Paolo Rovelli
Lo que quise decir al no estar seguro acerca de las tarjetas SD es que /mnttambién puede haber varios otros árboles fs, no solo tarjetas SD y unidades USB. Su código también enumeraría cualquier montaje interno (quizás incluso virtual) del sistema de archivos, si lo entiendo bien, mientras que la pregunta solo quiere tarjetas SD .
Sz.
1
Veo. Sí, tiene usted razón. Con mi método, también recuperará las memorias SD internas (no extraíbles).
Paolo Rovelli
15

Al igual que Richard, también uso el archivo / proc / mounts para obtener la lista de opciones de almacenamiento 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
fuente
Gracias. Funcionó perfectamente. Y me gusta la forma en que hizo que StorageInfo no sea mutable. Por otro lado printStackTrace? Cuando tenemos android.util.Log.e?
Martin
1
NO funcionó para dispositivos USB conectados a través del cable OTG en Nexus 5 y Nexus 7.
Khawar Raza
1
No puedo usar esto para escribir un archivo en la tarjeta SD
Eu Vid
mismo problema que @EuVid funciona en VM / AVD pero no en hardware
espía
11

Es posible encontrar dónde se montan las tarjetas SD adicionales mediante la lectura /proc/mounts(archivo estándar de Linux) y la verificación cruzada con datos vold ( /system/etc/vold.conf). Y tenga en cuenta que la ubicación devuelta por Environment.getExternalStorageDirectory()puede no aparecer en la configuración vold (en algunos dispositivos es el almacenamiento interno que no se puede desmontar), pero aún debe incluirse en la lista. Sin embargo, no encontramos una buena manera de describirlos al usuario .

Jan Hudec
fuente
Imo, el uso de mountes más compatible que la lectura del /procsistema de archivos. El problema es que la tarjeta SD no está necesariamente formateada como FAT. Además, el punto de montaje de la tarjeta puede variar de ROM a ROM. Además, podría haber varias otras particiones VFAT ...
borisstr
1
@borisstr: Hm, en realidad Android usa vold , por lo que mirar su configuración también es apropiado.
Jan Hudec
El archivo de código que compartí de mi publicación anterior incluye un método para describir las rutas raíz descubiertas para el usuario. Mira el método setProperties () .
Barón
1
@borisstr, en realidad, no, leer / proc / mounts es más portátil en dispositivos Android que lanzar el mountejecutable, especialmente porque se desaconseja el lanzamiento de ejecutables.
Chris Stratton
7

Intento todas las soluciones dentro de este tema en este momento. Pero todos ellos no funcionaron correctamente en dispositivos con una tarjeta externa (extraíble) y una interna (no extraíble). No es posible obtener la ruta de la tarjeta externa desde el comando 'mount', desde el archivo 'proc / mounts', etc.

Y creo mi propia solución (en la 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
fuente
6

Si observa el código fuente android.os.Environment, verá que Android depende en gran medida de las variables de entorno para las rutas. Puede usar la variable de entorno "SECONDARY_STORAGE" para encontrar la ruta a la tarjeta SD extraíble.

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

Ejemplo de uso:

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

Simplemente use esto:

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
fuente
En algunos dispositivos SECONDARY_STORAGEtiene varias rutas separadas con dos puntos (":"). Es por eso que dividí la cadena (ver mi respuesta más arriba).
Jared Rummler
Ambas vuelven nulas para mí.
Tim Cooper el
5

¿Existe una forma universal de encontrar la ubicación de una tarjeta SD externa?

Por vía universal , si te refieres a vía oficial; sí hay una.

En el nivel API 19, es decir, en Android versión 4.4 Kitkat, se han agregado File[] getExternalFilesDirs (String type)en la Contextclase que permite a las aplicaciones almacenar datos / archivos en tarjetas micro SD.

Android 4.4 es la primera versión de la plataforma que realmente ha permitido que las aplicaciones usen tarjetas SD para almacenamiento. Cualquier acceso a las tarjetas SD antes del nivel de API 19 se realizó a través de API privadas no compatibles.

getExternalFilesDirs (tipo de cadena) devuelve rutas absolutas a directorios específicos de la aplicación en todos los dispositivos de almacenamiento externo / compartido. Significa que devolverá rutas a la memoria interna y externa. Generalmente, el segundo camino devuelto sería la ruta de almacenamiento para la tarjeta microSD (si la hubiera).

Pero ten en cuenta que,

El almacenamiento compartido puede no estar siempre disponible, ya que el usuario puede expulsar los medios extraíbles. El estado de los medios se puede verificar usando getExternalStorageState(File).

No se aplica seguridad con estos archivos. Por ejemplo, cualquier aplicación que contenga WRITE_EXTERNAL_STORAGEpuede escribir en estos archivos.

La terminología de almacenamiento interno y externo según Google / documentos oficiales de Android es bastante diferente de lo que pensamos.

AnV
fuente
"La terminología del almacenamiento interno y externo según Google / documentos oficiales de Android es bastante diferente de lo que pensamos". Sí, de hecho, el título de la pregunta aclara que el OP pregunta por una tarjeta SD extraíble . getExternalFilesDirs()a menudo devuelve tarjetas SD que no son extraíbles, así que no, esta no es una forma universal de encontrar la ubicación de una tarjeta SD extraíble.
LarsH
"getExternalFilesDirs (tipo de cadena) devuelve rutas absolutas a directorios específicos de la aplicación en todos los dispositivos de almacenamiento compartidos / externos. Esto significa que devolverá rutas a la memoria interna y externa". Este par de oraciones es muy engañoso, porque para que ambas sean verdaderas, "externo" tiene que significar dos cosas diferentes y conflictivas.
LarsH
4

Aquí está la forma en que uso para encontrar la tarjeta externa. Use mount cmd return y luego analice 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.
        }
    }
}
}
Oso falso
fuente
4

Esta solución maneja el hecho de que System.getenv("SECONDARY_STORAGE") no sirve para nada con Marshmallow.

Probado y trabajando en:

  • 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
fuente
2

Desde mi respuesta original anterior, el escaneo vold ya no es viable en los distintos fabricantes.

He desarrollado un método más confiable y directo.

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

Las raíces contendrán todos los directorios raíz que se pueden escribir en el sistema, incluidos los dispositivos USB conectados por USB.

NOTA: El método canWrite necesita el permiso android.permission.WRITE_EXTERNAL_STORAGE.

Barón
fuente
El método isSymlink (File) no está definido para el tipo nuevo FileFilter () {}
Omid Omidi
¿Alguna idea de si esto no podrá encontrar tarjetas SD externas en Android 4.4 debido a canWrite?
Anthony
Esto es ciertamente más sencillo que su otro método, pero ¿es confiable? Por ejemplo, he leído que en algunos dispositivos Samsung, /external_sdes la tarjeta microSD externa; en algunos LG, es /_ExternalSD; en algunos dispositivos lo es /sdcard. Tal vez este último sea un enlace simbólico /storage/sdcard0o similar, pero ¿estos otros realmente estarán cubiertos de forma confiable por /storage/*y /mount/*?
LarsH
Además, ¿es necesario usar pathname.canWrite()y requerir el permiso WRITE_EXTERNAL_STORAGE? ¿Por qué no solo llamar pathname.canRead()?
LarsH
1

fue muy tarde, pero finalmente obtuve algo que he probado en la mayoría de los dispositivos (por fabricante y versiones de Android) que funciona en Android 2.2+. si encuentra que no funciona, coméntelo con el nombre de su dispositivo. yo lo arreglare. Si alguien interesado explicaré cómo funciona.

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
fuente
Hola, downvoter, por favor intente primero. Si no funciona, comente su dispositivo. que usarla por miles de dispositivos con Android 2.2 +
Ajeet47
Su clase me da / mnt / media_rw / extSdCard en Samsung Galaxy S4, GT-I9500, Android 5.0.1 (el dispositivo NO está rooteado). Pero no hay nada visible en la carpeta / mnt / media_rw con ES File Manager ...
ausente el
@isabsent use if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT) {Archivo [] archivo = context.getExternalFilesDirs (nulo); return file.length> 1? archivo [1]: nulo; }
Ajeet47
¿Qué opinas sobre stackoverflow.com/a/27197248/753575 ? ¿Es este enfoque más completo para el caso de Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT?
ausente el
si. se garantiza que obtendrá una ruta significativa desde context.getExternalFilesDirs (nulo), pero la ha recortado para la ruta raíz (devolverá la ruta para el directorio de la aplicación.
recórtela
1

Al escribir el siguiente código obtendrá la ubicación:

/ storage / 663D-554E / Android / data / app_package_name / files /

que almacena los datos de su aplicación en / android / data location dentro de la tarjeta sd_card.

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

list[1]+"/fol" 

para obtener la ubicación, pase 0 para interno y 1 para sdcard a matriz de archivos.

He probado este código en un moto g4 plus y un dispositivo Samsung (todo funciona bien).

Espero que esto pueda ser útil.

Rk215 Tech
fuente
a veces trayectoria de la tarjeta SD no está en el índice 1, he visto los casos en los que era el índice 0. mejor seguir algo más
Raghav Satyadev
1

Aquí está el método que uso para encontrar una tarjeta SD extraíble . Es complejo y probablemente exagerado en algunas situaciones, pero funciona en una amplia variedad de versiones de Android y fabricantes de dispositivos que he probado en los últimos años. No conozco ningún dispositivo desde el nivel 15 de API en el que no encuentra la tarjeta SD, si hay una montada. No devolverá falsos positivos en la mayoría de los casos, especialmente si le da el nombre de un archivo conocido para buscar.

Avíseme si se encuentra con algún caso en el que no funciona.

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

PD

  • No lo olvides <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />en el manifiesto. Y en el nivel API 23 y superior, asegúrese de usar checkSelfPermission/requestPermissions .
  • Establezca KNOWNFILE = "myappfile" si hay un archivo o carpeta que espera encontrar en la tarjeta SD. Eso hace que la detección sea más precisa.
  • Obviamente querrás almacenar en caché el valor de findSdCardPath(), lugar de calcularlo cada vez que lo necesite.
  • Hay un montón de registros ( Log.d()) en el código anterior. Ayuda a diagnosticar cualquier caso en el que no se encuentre el camino correcto. Comenta si no quieres iniciar sesión.
LarsH
fuente
Votantes, ¿puede sugerir una forma de mejorar esta respuesta?
LarsH
1

La única solución de trabajo que encontré fue esta que usa reflexión

 /**
 * 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
fuente
¡Personalmente no prefiero usar la reflexión porque Google no aprecia la compatibilidad con versiones anteriores en las nuevas versiones de Android!
Behrouz.M
0

No sé por qué, pero necesito llamar a .createNewFile () en un archivo creado en los directorios de almacenamiento público antes de usarlo. En el marco, los comentarios para ese método dicen que no es útil. Aquí hay una muestra ...


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

usuario1743524
fuente
0

He creado un método de utilidades para verificar que una tarjeta SD esté disponible en el dispositivo o no, y obtener la ruta de la tarjeta SD en el dispositivo si está disponible.

Puede copiar 2 métodos a continuación en la clase de su proyecto que necesite. Eso es todo.

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;
}
usuario3161772
fuente
-1

Funciona para todos los dispositivos externos, pero asegúrese de obtener solo el nombre de la carpeta del dispositivo externo y luego debe obtener el archivo de la ubicación dada usando la clase 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;
    }

Vocación:

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

                }
            }
        }

Obtener acceso a almacenamiento externo

Para leer o escribir archivos en el almacenamiento externo, su aplicación debe adquirir los permisos del sistema READ_EXTERNAL_STORAGE o WRITE_EXTERNAL_STORAGE . Por ejemplo:

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

/ sdcard => Almacenamiento interno (es un enlace simbólico pero debería funcionar)

/ mnt / extSdCard => Tarjeta SD externa

Esto es para Samsung Galaxy S3

Probablemente pueda confiar en que esto sea cierto para la mayoría ... ¡sin embargo, verifique dos veces!

robbyoconnor
fuente
8
He tenido varios teléfonos Android diferentes, aproximadamente la mitad de ellos Samsung, y nunca he visto esta ubicación utilizada. Puede ser cierto en el S3, pero decir "probablemente puedas confiar en que esto sea cierto para la mayoría" es completamente incorrecto.
Geobits
incorrecto. /sdcardes un enlace simbólico a externo en mi sony 2305.
jiggunjer
2
¿No dije que podría no ser así?
robbyoconnor