Forma universal de escribir en una tarjeta SD externa en Android

En mi aplicación, necesito almacenar muchas imágenes en el almacenamiento del dispositivo. Dichos archivos tienden a cumplir con el almacenamiento del dispositivo, y quiero permitir que los usuarios puedan elegir una tarjeta SD externa como la carpeta de destino.

Leo en todas partes que Android no permite a los usuarios escribir en una tarjeta SD externa, con una tarjeta SD me refiero a la tarjeta SD externa y que se puede montar y no al almacenamiento externo , pero las aplicaciones administradoras de archivos logran escribir en SD externa en todas las versiones de Android.

¿Cuál es la mejor manera de otorgar acceso de lectura / escritura a una tarjeta SD externa en diferentes niveles API (PreKitKat, KitKat, Lollipop +)?

Actualización 1

Probé el Método 1 a partir de la respuesta de Doomknight, sin resultado: como puedes ver, estoy verificando los permisos en tiempo de ejecución antes de intentar escribir en SD:

HashSet extDirs = getStorageDirectories(); for(String dir: extDirs) { Log.e("SD",dir); File f = new File(new File(dir),"TEST.TXT"); try { if(ActivityCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)==PackageManager.PERMISSION_GRANTED) { f.createNewFile(); } } catch (IOException e) { e.printStackTrace(); } } 

Pero recibo un error de acceso, probado en dos dispositivos diferentes: HTC10 y Shield K1.

 10-22 14:52:57.329 30280-30280/? E/SD: /mnt/media_rw/F38E-14F8 10-22 14:52:57.329 30280-30280/? W/System.err: java.io.IOException: open failed: EACCES (Permission denied) 10-22 14:52:57.329 30280-30280/? W/System.err: at java.io.File.createNewFile(File.java:939) 10-22 14:52:57.329 30280-30280/? W/System.err: at com.myapp.activities.TestActivity.onResume(TestActivity.java:167) 10-22 14:52:57.329 30280-30280/? W/System.err: at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1326) 10-22 14:52:57.330 30280-30280/? W/System.err: at android.app.Activity.performResume(Activity.java:6338) 10-22 14:52:57.330 30280-30280/? W/System.err: at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3336) 10-22 14:52:57.330 30280-30280/? W/System.err: at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3384) 10-22 14:52:57.330 30280-30280/? W/System.err: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2574) 10-22 14:52:57.330 30280-30280/? W/System.err: at android.app.ActivityThread.access$900(ActivityThread.java:150) 10-22 14:52:57.330 30280-30280/? W/System.err: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1399) 10-22 14:52:57.330 30280-30280/? W/System.err: at android.os.Handler.dispatchMessage(Handler.java:102) 10-22 14:52:57.330 30280-30280/? W/System.err: at android.os.Looper.loop(Looper.java:168) 10-22 14:52:57.330 30280-30280/? W/System.err: at android.app.ActivityThread.main(ActivityThread.java:5885) 10-22 14:52:57.330 30280-30280/? W/System.err: at java.lang.reflect.Method.invoke(Native Method) 10-22 14:52:57.330 30280-30280/? W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:819) 10-22 14:52:57.330 30280-30280/? W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:709) 10-22 14:52:57.330 30280-30280/? W/System.err: Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied) 10-22 14:52:57.330 30280-30280/? W/System.err: at libcore.io.Posix.open(Native Method) 10-22 14:52:57.330 30280-30280/? W/System.err: at libcore.io.BlockGuardOs.open(BlockGuardOs.java:186) 10-22 14:52:57.330 30280-30280/? W/System.err: at java.io.File.createNewFile(File.java:932) 10-22 14:52:57.330 30280-30280/? W/System.err: ... 14 more 

Respuesta corta.

ContextCompat.getExternalFilesDirs resuelve el error de acceso cuando no necesita compartir archivos.

La forma segura de compartirlo es usar un proveedor de contenido o el nuevo Storage Access Framework .


Resumen.

No existe una forma universal de escribir en una tarjeta SD externa en Android debido a cambios continuos :

  • PreKitKat: la plataforma oficial de Android no admite tarjetas SD a excepción de excepciones.

  • KitKat: API introducidas que permiten que las aplicaciones accedan a archivos en directorios específicos de la aplicación en tarjetas SD.

  • Lollipop: API añadidas para permitir que las aplicaciones soliciten acceso a carpetas propiedad de otros proveedores.

  • Turrón: proporcionó una API simplificada para acceder a los directorios de almacenamiento externo comunes.

Según la respuesta de Doomsknight y la mía , y las publicaciones de Dave Smith y Mark Murphy en el blog: 1 , 2 , 3 :

  • Idealmente, utilice el Framework de acceso de almacenamiento y DocumentFile como señaló Jared Rummler . O:
  • Use la ruta específica de su aplicación /storage/extSdCard/Android/data/com.myapp.example/files .
  • Agregue permisos de lectura / escritura para manifestar para preKitKat, no se requiere permiso más adelante para esta ruta.
  • Intenta usar tu ruta de acceso de la aplicación y los métodos de Doomsknight teniendo en cuenta los casos de KitKat y Samsung .
  • Filtra y usa getStorageDirectories , tu ruta de la aplicación y permisos de lectura / escritura antes de KitKat.
  • ContextCompat.getExternalFilesDirs desde KitKat. Teniendo en cuenta los dispositivos que devuelven interno primero.

1. Permisos.

Puede otorgar acceso de lectura / escritura a una tarjeta SD externa en los diferentes niveles de API ( API23 + en tiempo de ejecución ).

Dado que KitKat, los permisos no son necesarios si utiliza directorios específicos de la aplicación, se requiere lo contrario:

Con Kitkat, sus posibilidades de una “solución completa” sin enraizamiento son prácticamente nulas: el proyecto de Android definitivamente se ha estropeado aquí. Ninguna aplicación obtiene acceso completo a la tarjeta SD externa:

  • administradores de archivos: no puede usarlos para administrar su tarjeta SD externa. En la mayoría de las áreas, solo pueden leer pero no escribir.
  • aplicaciones de medios: ya no puede volver a organizar / reorganizar su colección de medios, ya que esas aplicaciones no pueden escribir en ella.
  • aplicaciones de oficina: más o menos lo mismo

El único lugar donde las aplicaciones de terceros pueden escribir en su tarjeta externa son “sus propios directorios” (es decir, /sdcard/Android/data/ ).

Las únicas formas de solucionarlo realmente requieren del fabricante (algunos lo arreglaron, por ejemplo, Huawei con su actualización de Kitkat para el P6) o de la raíz … (la explicación de Izzy continúa aquí)


2. Acerca de la solución universal.

La historia dice que no hay una forma universal de escribir en una tarjeta SD externa, pero continúa …

Este hecho se demuestra con estos ejemplos de configuraciones de almacenamiento externo para dispositivos .

El acceso al almacenamiento externo está protegido por varios permisos de Android. A partir de Android 1.0, el acceso de escritura está protegido con el permiso WRITE_EXTERNAL_STORAGE . A partir de Android 4.1, el acceso de lectura está protegido con el permiso READ_EXTERNAL_STORAGE .

A partir de Android 4.4, el propietario, grupo y modos de archivos en dispositivos de almacenamiento externo ahora se sintetizan en función de la estructura del directorio. Esto permite que las aplicaciones administren sus directorios específicos del paquete en el almacenamiento externo sin requerir que tengan el amplio permiso WRITE_EXTERNAL_STORAGE . Por ejemplo, la aplicación con nombre de paquete com.example.foo ahora puede acceder libremente a Android/data/com.example.foo/ en dispositivos de almacenamiento externos sin permisos. Estos permisos sintetizados se logran envolviendo los dispositivos de almacenamiento sin formato en un daemon FUSE.

Android 6.0 presenta un nuevo modelo de permisos de tiempo de ejecución donde las aplicaciones solicitan capacidades cuando es necesario en tiempo de ejecución. Debido a que el nuevo modelo incluye los permisos READ/WRITE_EXTERNAL_STORAGE , la plataforma necesita otorgar dinámicamente acceso de almacenamiento sin matar o reiniciar las aplicaciones que ya están en ejecución. Lo hace al mantener tres vistas distintas de todos los dispositivos de almacenamiento montados:

  • / mnt / runtime / default se muestra a las aplicaciones sin permisos de almacenamiento especiales …
  • / mnt / runtime / read se muestra a las aplicaciones con READ_EXTERNAL_STORAGE
  • / mnt / runtime / write se muestra a las aplicaciones con WRITE_EXTERNAL_STORAGE

3. Acerca de su actualización 1.

Utilizaría directorios específicos de la aplicación para evitar el problema de su pregunta actualizada y ContextCompat.getExternalFilesDirs() usando la documentación getExternalFilesDir como referencia.

Mejore la heurística para determinar qué representa medios extraíbles en función de los diferentes niveles de api como android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT

Recuerde que Android 6.0 es compatible con dispositivos de almacenamiento portátiles y que las aplicaciones de terceros deben pasar por Storage Access Framework . Sus dispositivos HTC10 y Shield K1 probablemente sean API 23.

Su registro muestra una excepción de permiso denegado que accede a /mnt/media_rw , como esta solución para API 19+:

     // this line is added via root in the link to fix it.  

Nunca lo intenté, así que no puedo compartir el código, pero lo evitaría for tratar de escribir en todos los directorios devueltos y buscar el mejor directorio de almacenamiento disponible para escribir en función del espacio restante .

Tal vez la alternativa de getStorageDirectories() a su método getStorageDirectories() sea ​​un buen punto de partida.

ContextCompat.getExternalFilesDirs resuelve el problema si no necesita acceder a otras carpetas .


4. Solicitar permisos en manifiesto (Api <23) y en tiempo de ejecución (Api> = 23).

Agregue el siguiente código a su AndroidManifest.xml y lea Cómo obtener acceso al almacenamiento externo

Para … escribir archivos en el almacenamiento externo, su aplicación debe adquirir … permisos del sistema:

     

Si necesita ambos …, necesita solicitar solo el permiso WRITE_EXTERNAL_STORAGE .

Ignore la siguiente nota debido a errores, pero trate de usar ContextCompat.getExternalFilesDirs() :

Nota: a partir de Android 4.4, estos permisos no son necesarios si está leyendo o escribiendo solo archivos que son privados para su aplicación. Para obtener más información …, consulte guardar archivos que son privados de la aplicación .

    

Solicite permisos en tiempo de ejecución si el nivel API es 23+ y lee Solicitando permisos en tiempo de ejecución

A partir de Android 6.0 (API de nivel 23), los usuarios conceden permisos a las aplicaciones mientras se ejecuta la aplicación, no cuando instalan la aplicación … o actualizan la aplicación … el usuario puede revocar los permisos.

 // Assume thisActivity is the current activity int permissionCheck = ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE); 

5. Antes de KitKat intente utilizar el método Doomsknight 1, método 2 en caso contrario.

Lee la explicación de Mark Murphy y recomienda los de Dianne Hackborn y Dave Smith

  • Hasta Android 4.4, no había soporte oficial para medios extraíbles en Android. Comenzando en KitKat, el concepto de almacenamiento externo “primario” y “secundario” emerge en la API de FMW.
  • Las aplicaciones anteriores simplemente dependen de la indexación de MediaStore, se envían con el hardware o examinan los puntos de assembly y aplican algunas heurísticas para determinar qué representa los medios extraíbles.
  • Desde Android 4.2, Google ha solicitado a los fabricantes de dispositivos que bloqueen los medios extraíbles para mayor seguridad (soporte multiusuario) y se agregaron nuevas pruebas en 4.4.
  • Como KiKat getExternalFilesDirs() y otros métodos se agregaron para devolver una ruta utilizable en todos los volúmenes de almacenamiento disponibles (el primer elemento devuelto es el volumen principal).
  • La siguiente tabla indica lo que un desarrollador podría intentar y cómo responderá KitKat: enter image description here

Antes de KitKat intente utilizar el método Doomsknight 1 o lea esta respuesta por Gnathonic o esencia :

 public static HashSet getExternalMounts() { final HashSet out = new HashSet(); 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; } 

Lea también la explicación de Paolo Rovelli y trate de usar la solución de Jeff Sharkey desde KitKat:

En KitKat ahora hay una API pública para interactuar con estos dispositivos secundarios de almacenamiento compartido.

Los nuevos Context.getExternalFilesDirs() y Context.getExternalCacheDirs() pueden devolver varias rutas, incluidos los dispositivos primarios y secundarios.

A continuación, puede iterar sobre ellos y comprobar Environment.getStorageState() y File.getFreeSpace() para determinar el mejor lugar para almacenar sus archivos.

Estos métodos también están disponibles en ContextCompat en la biblioteca support-v4.


6. Lollipop también introdujo cambios y la clase de ayuda DocumentFile .

getStorageState Agregado en API 19, desaprobado en API 21, use getExternalStorageState(File)

Aquí hay un gran tutorial para interactuar con el Marco de acceso de almacenamiento en KitKat.

La interacción con las nuevas API en Lollipop es muy similar (explicación de Jeff Sharkey) .


7. Android 7.0 proporciona una API simplificada para acceder a los directorios de almacenamiento externo.

Acceso de directorio con scope En Android 7.0, las aplicaciones pueden usar nuevas API para solicitar acceso a directorios de almacenamiento externo específicos, incluidos directorios en medios extraíbles como tarjetas SD …

Para obtener más información, consulte la capacitación Acceso a directorios con scope .


8. Android O cambios.

A partir de Android O , Storage Access Framework permite a los proveedores de documentos personalizados crear descriptores de archivos buscables para archivos que residen en una fuente de datos remota …

Permisos , antes de Android O , si una aplicación solicitó un permiso en tiempo de ejecución y se otorgó el permiso, el sistema también otorgó incorrectamente a la aplicación el rest de los permisos que pertenecían al mismo grupo de permisos y que se registraron en el manifiesto.

Para las aplicaciones orientadas a Android O , este comportamiento se ha corregido. La aplicación recibe solo los permisos que ha solicitado explícitamente. Sin embargo, una vez que el usuario otorga un permiso a la aplicación, todas las solicitudes posteriores de permisos en ese grupo de permisos se otorgan automáticamente.

Por ejemplo, READ_EXTERNAL_STORAGE y WRITE_EXTERNAL_STORAGE


9. Preguntas relacionadas y respuestas recomendadas.

¿Cómo puedo obtener una ruta de tarjeta SD externa para Android 4.0+?

mkdir () funciona mientras está dentro del almacenamiento flash interno, pero no en la tarjeta SD?

Diferencia entre getExternalFilesDir y getExternalStorageDirectory ()

¿Por qué getExternalFilesDirs () no funciona en algunos dispositivos?

Cómo usar la nueva API de acceso a la tarjeta SD presentada para Android 5.0 (Lollipop)

Escribir en una tarjeta SD externa en Android 5.0 y superior

Android SD Card Write Permission usando SAF (Storage Access Framework)

SAFFAQ: Las preguntas frecuentes sobre el marco de acceso de almacenamiento


10. Errores relacionados y problemas.

Error: en Android 6, al usar getExternalFilesDirs, no le permitirá crear nuevos archivos en sus resultados

La escritura en el directorio devuelto por getExternalCacheDir () en Lollipop falla sin permiso de escritura

Creo que hay dos métodos para lograr esto:

MÉTODO 1: ( NO funciona en 6.0 o superior, debido a cambios de permisos)

He estado usando este método durante años en muchas versiones de dispositivos sin problemas. El crédito se debe a la fuente original, ya que no fui yo quien lo escribió.

Devolverá todos los medios montados (incluidas las Tarjetas Real SD) en una lista de ubicaciones de directorio de cadenas. Con la lista, puede preguntar al usuario dónde guardar, etc.

Puedes llamarlo con lo siguiente:

  HashSet extDirs = getStorageDirectories(); 

Método:

 /** * Returns all the possible SDCard directories */ public static HashSet getStorageDirectories() { final HashSet out = new HashSet(); 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().contains("asec")) { if (line.matches(reg)) { String[] parts = line.split(" "); for (String part : parts) { if (part.startsWith("/")) if (!part.toLowerCase().contains("vold")) out.add(part); } } } } return out; } 

MÉTODO 2:

Use la biblioteca de soporte v4

 import android.support.v4.content.ContextCompat; 

Simplemente llame a la siguiente para obtener una lista de ubicaciones de File de almacenamiento.

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

Sin embargo, las ubicaciones difieren en el uso.

Devuelve rutas absolutas a directorios específicos de la aplicación en todos los dispositivos de almacenamiento externo donde la aplicación puede colocar los archivos persistentes que posee. Estos archivos son internos a la aplicación y no suelen ser visibles para el usuario como medio.

Los dispositivos de almacenamiento externo devueltos aquí se consideran una parte permanente del dispositivo, incluidos el almacenamiento externo emulado y las ranuras de medios físicos, como las tarjetas SD en el compartimiento de la batería. Las rutas devueltas no incluyen dispositivos transitorios, como unidades flash USB.

Una aplicación puede almacenar datos en cualquiera o todos los dispositivos devueltos. Por ejemplo, una aplicación puede elegir almacenar archivos grandes en el dispositivo con el mayor espacio disponible

Más información sobre ContextCompat

Son como archivos específicos de la aplicación. Oculto de otras aplicaciones.

Solo otra respuesta. Esta respuesta solo muestra 5.0+ porque creo que la respuesta de Doomknight publicada aquí es la mejor manera de hacer para Android 4.4 y versiones posteriores.

Esto se publicó originalmente aquí ( ¿Hay alguna forma de obtener el tamaño de la tarjeta SD en Android? ) Para obtener el tamaño de la tarjeta SD externa en Android 5.0+

Para obtener la tarjeta SD externa como un File :

 public File getExternalSdCard() { File externalStorage = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { File storage = new File("/storage"); if(storage.exists()) { File[] files = storage.listFiles(); for (File file : files) { if (file.exists()) { try { if (Environment.isExternalStorageRemovable(file)) { externalStorage = file; break; } } catch (Exception e) { Log.e("TAG", e.toString()); } } } } } else { // do one of many old methods // I believe Doomsknight's method is the best option here } return externalStorage; } 

Nota: solo obtengo la “primera” tarjeta sd externa, sin embargo, puede modificarla y devolver ArrayList lugar de File y dejar que el bucle continúe en lugar de llamar al break después de que se encuentre el primero.

Además de todas las otras buenas respuestas, podría agregar un poco más a esta pregunta para que pueda dar una cobertura más amplia para los lectores. En mi respuesta aquí, usaría 2 recursos contables para presentar almacenamiento externo.

El primer recurso es de Android Programming, The Big Nerd Ranch Guide 2da edición , capítulo 16, página 294.

El libro describe los métodos básicos y externos de archivos y directorios. Trataré de hacer un resumen de lo que podría ser relevante para su pregunta.

La siguiente parte del libro:

Almacenamiento externo

Tu foto necesita más que un lugar en la pantalla. Las imágenes de tamaño completo son demasiado grandes para pegarse dentro de una base de datos SQLite, y mucho menos como una Intent . Necesitarán un lugar para vivir en el sistema de archivos de su dispositivo. Normalmente, los pondría en su almacenamiento privado. Recuerde que utilizó su almacenamiento privado para guardar su base de datos SQLite. Con métodos como Context.getFileStreamPath(String) y Context.getFilesDir() , también puede hacer lo mismo con los archivos normales (que vivirán en una subcarpeta adyacente a la subcarpeta de bases de datos en la que vive su base de datos SQLite)

Métodos básicos de archivos y directorios en contexto

 | Method | |---------------------------------------------------------------------------------------| |File etFilesDir() | | - Returns a handle to the directory for private application files. | | | |FileInputStream openFileInput(String name) | | - Opens an existing file for input (relative to the files directory). | | | |FileOutputStream openFileOutput(String name, int mode) | | - Opens a file for output, possibly creating it (relative to the files directory). | | | |File getDir(String name, int mode) | | - Gets (and possibly creates) a subdirectory within the files directory. | | | |String[] fileList() | | - Gets a list of file names in the main files directory, such as for use with | | openFileInput(String). | | | |File getCacheDir() | | - Returns a handle to a directory you can use specifically for storing cache files. | | You should take care to keep this directory tidy and use as little space as possible| 

Si está almacenando archivos que solo su aplicación actual necesita usar, estos métodos son exactamente lo que necesita.

Por otro lado, si necesita otra aplicación para escribir en esos archivos, no tiene suerte: mientras haya un indicador Context.MODE_WORLD_READABLE , puede pasar a openFileOutput(String, int) , está obsoleto y no es completamente confiable en sus efectos en dispositivos más nuevos. Si está almacenando archivos para compartirlos con otras aplicaciones o para recibir archivos de otras aplicaciones (archivos como imágenes almacenadas), debe almacenarlos en el almacenamiento externo.

Hay dos tipos de almacenamiento externo: primario y todo lo demás. Todos los dispositivos Android tienen al menos una ubicación para el almacenamiento externo: la ubicación principal, que se encuentra en la carpeta devuelta por Environment.getExternalStorageDirectory() . Puede ser una tarjeta SD, pero hoy en día está más comúnmente integrada en el dispositivo. Algunos dispositivos pueden tener almacenamiento externo adicional. Eso caería bajo “todo lo demás”.

El contexto proporciona bastantes métodos para obtener almacenamiento externo también. Estos métodos proporcionan formas sencillas de acceder a su almacenamiento externo principal y formas bastante sencillas de acceder a todo lo demás. Todos estos métodos almacenan archivos en lugares disponibles públicamente, también, así que tenga cuidado con ellos.

Métodos de archivos y directorios externos en contexto

 | Method | | --------------------------------------------------------------------------------------| |File getExternalCacheDir() | | - Returns a handle to a cache folder in primary external storage. Treat it like you do| | getCacheDir(), except a little more carefully. Android is even less likely to clean | | up this folder than the private storage one. | | | |File[] getExternalCacheDirs() | | - Returns cache folders for multiple external storage locations. | | | |File getExternalFilesDir(String) | | - Returns a handle to a folder on primary external storage in which to store regular | | files. If you pass in a type String, you can access a specific subfolder dedicated | | to a particular type of content. Type constants are defined in Environment, where | | they are prefixed with DIRECTORY_. | | For example, pictures go in Environment.DIRECTORY_PICTURES. | | | |File[] getExternalFilesDirs(String) | | - Same as getExternalFilesDir(String), but returns all possible file folders for the | | given type. | | | |File[] getExternalMediaDirs() | | - Returns handles to all the external folders Android makes available for storing | | media – pictures, movies, and music. What makes this different from calling | | getExternalFilesDir(Environment.DIRECTORY_PICTURES) is that the media scanner | | automatically scans this folder. The media scanner makes files available to | | applications that play music, or browse movies and photos, so anything that you | | put in a folder returned by getExternalMediaDirs() will automatically appear in | | those apps. | 

Técnicamente, las carpetas externas proporcionadas anteriormente pueden no estar disponibles, ya que algunos dispositivos usan una tarjeta SD extraíble para almacenamiento externo. En la práctica, esto rara vez es un problema, ya que casi todos los dispositivos modernos tienen un almacenamiento interno no extraíble para su almacenamiento “externo”. Por lo tanto, no vale la pena ir a extremos extremos para dar cuenta de ello. Pero recomendamos incluir un código simple para protegerse de la posibilidad, lo que hará en un momento.

Permiso de almacenamiento externo

En general, necesita un permiso para escribir o leer en el almacenamiento externo. Los permisos son valores de cadena conocidos que coloca en su manifiesto mediante la etiqueta . Le dicen a Android que quieres hacer algo para lo cual Android quiere que pidas permiso.

Aquí, Android espera que solicites permiso porque quiere imponer cierta responsabilidad. Le dice a Android que necesita acceder al almacenamiento externo, y Android le dirá al usuario que esta es una de las cosas que hace su aplicación cuando intenta instalarla. De esta forma, nadie se sorprende cuando comienza a guardar cosas en su tarjeta SD.

En Android 4.4, KitKat, aflojaron esta restricción. Dado que Context.getExternalFilesDir(String) devuelve una carpeta que es específica para su aplicación, tiene sentido que desee poder leer y escribir archivos que vivan allí. Por lo tanto, en Android 4.4 (API 19) y superior, no necesita este permiso para esta carpeta. (Pero aún lo necesita para otros tipos de almacenamiento externo).

Agregue una línea a su manifiesto que solicite el permiso para leer el almacenamiento externo, pero solo hasta el listado API 16.5 Solicitud de permiso de almacenamiento externo ( AndroidManifest.xml )

   

El atributo maxSdkVersion lo hace para que su aplicación solo solicite este permiso en las versiones de Android anteriores a la API 19, KitKat de Android. Tenga en cuenta que solo solicita leer el almacenamiento externo. También hay un permiso WRITE_EXTERNAL_STORAGE , pero no lo necesita. No escribirás nada en el almacenamiento externo: la aplicación de la cámara lo hará por ti

El segundo recurso es este enlace, léalo todo, pero también puede acceder a la sección Uso del almacenamiento externo .

Referencia:

Más cosas de lectura:

Descargo de responsabilidad: Esta información fue tomada de la Progtwigción de Android: La Guía Big Nerd Ranch con el permiso de los autores. Para obtener más información sobre este libro o comprar una copia, visite bignerdranch.com.

Este tema es un poco antiguo, pero estaba buscando una solución y después de algunas investigaciones obtuve el siguiente código para recuperar una lista de puntos de assembly “externos” disponibles que, según mi conocimiento, funcionan en muchos dispositivos diferentes.

Básicamente, lee los puntos de assembly disponibles, filtra los no válidos, prueba el rest si son accesibles y los agrega si se cumplen todas las condiciones.

Por supuesto, los permisos requeridos deben otorgarse antes de invocar el código.

 // Notice: FileSystemDevice is just my own wrapper class. Feel free to replace it with your own. private List getDevices() { List devices = new ArrayList<>(); // Add default external storage if available. File sdCardFromSystem = null; switch(Environment.getExternalStorageState()) { case Environment.MEDIA_MOUNTED: case Environment.MEDIA_MOUNTED_READ_ONLY: case Environment.MEDIA_SHARED: sdCardFromSystem = Environment.getExternalStorageDirectory(); break; } if (sdCardFromSystem != null) { devices.add(new FileSystemDevice(sdCardFromSystem)); } // Read /proc/mounts and add all mount points that are available // and are not "special". Also, check if the default external storage // is not contained inside the mount point. try { FileInputStream fs = new FileInputStream("/proc/mounts"); String mounts = IOUtils.toString(fs, "UTF-8"); for(String line : mounts.split("\n")) { String[] parts = line.split(" "); // parts[0] - mount type // parts[1] - mount point if (parts.length > 1) { try { // Skip "special" mount points and mount points that can be accessed // directly by Android's functions. if (parts[0].equals("proc")) { continue; } if (parts[0].equals("rootfs")) { continue; } if (parts[0].equals("devpts")) { continue; } if (parts[0].equals("none")) { continue; } if (parts[0].equals("sysfs")) { continue; } if (parts[0].equals("selinuxfs")) { continue; } if (parts[0].equals("debugfs")) { continue; } if (parts[0].equals("tmpfs")) { continue; } if (parts[1].equals(Environment.getRootDirectory().getAbsolutePath())) { continue; } if (parts[1].equals(Environment.getDataDirectory().getAbsolutePath())) { continue; } if (parts[1].equals(Environment.getExternalStorageDirectory().getAbsolutePath())) { continue; } // Verify that the mount point is accessible by listing its content. File file = new File(parts[1]); if (file.listFiles() != null) { try { // Get canonical path for case it's just symlink to another mount point. String devPath = file.getCanonicalPath(); for(FileSystemDevice device : devices) { if (!devices.contains(devPath)) { devices.add(new FileSystemDevice(new File(devPath))); } } } catch (Exception e) { // Silently skip the exception as it can only occur if the mount point is not valid. e.printStackTrace(); } } } catch (Exception e) { // Silently skip the exception as it can only occur if the mount point is not valid. e.printStackTrace(); } } } fs.close(); } catch (FileNotFoundException e) { // Silently skip the exception as it can only occur if the /proc/mounts file is unavailable. // Possibly, another detection method can be called here. e.printStackTrace(); } catch (IOException e) { // Silently skip the exception as it can only occur if the /proc/mounts file is unavailable. // Possibly, another detection method can be called here. e.printStackTrace(); } return devices; } 

Aquí hay una forma de crear un nuevo archivo en el almacenamiento externo (tarjeta SD si está presente en el dispositivo o dispositivo de almacenamiento externo si no es así). Just replace “foldername” with the name of your desired destination folder and “filename” with the name of the file you are saving. Of course here you can see how to save a generic File, now you can search for how to save images maybe here or whatever in a file.

 try { File dir = new File(Environment.getExternalStorageDirectory() + "/foldername/"); if (!dir.exists()){ dir.mkdirs(); } File sdCardFile = new File(Environment.getExternalStorageDirectory() + "/foldername/" + fileName ); int num = 1; String fileNameAux = fileName; while (sdCardFile.exists()){ fileNameAux = fileName+"_"+num; sdCardFile = new File(Environment.getExternalStorageDirectory() + "/foldername/" + fileNameAux); num++; } 

This also controls that file exists and adds a number in the end of the name of the new file to save it.

¡Espero eso ayude!

EDIT: Sorry, i forgot you have to ask for in your manifest (or progtwigtically if you prefer from Marshmallow)

 
`Log.d(TAG, System.getenv("SECONDARY_STORAGE"));`

salida:

`D/MainActivity: /storage/extSdCard`

Working on Note 3 Android 5.0.

For versions below Marshmallow you can directly give the permissions in the manifest.

But for devices with Marshmallow and above you need to grant the permissions on run time.

Mediante el uso

 Environment.getExternalStorageDirectory(); 

you can directly access the External SD card (Mounted one) Hope this helps.