Forma universal de escribir en la tarjeta SD externa en Android


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

He leído en todas partes que Android no permite a los usuarios escribir en la tarjeta SD externa, por tarjeta SD Me refiero a la tarjeta SD externa y montable y no el almacenamiento externo, pero las aplicaciones de administrador 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 la tarjeta SD externa en diferentes niveles de API (Pre-KitKat, KitKat, Lollipop+)?

Actualización 1

Probé el Método 1 de la respuesta de Doomknight, sin éxito: Como puedes ver, estoy buscando permisos en tiempo de ejecución antes de intentar escribir en SD:

HashSet<String> 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 tengo 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
Author: albodelu, 2016-10-16

8 answers

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 Marco de acceso al almacenamiento .


Resumen.

No hay forma universal para escribir en una tarjeta SD externa en Android debido a continua cambios:

  • Pre-KitKat: la plataforma oficial de Android no admite tarjetas SD, excepto excepciones.

  • KitKat: introdujo API que permiten a las aplicaciones acceder a archivos en directorios específicos de aplicaciones en tarjetas SD.

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

  • Nougat: proporciona una API simplificada para acceder al almacenamiento externo común Directory.

Basado en Doomsknight's answer y mine , y Dave Smith y Mark Murphy blog posts: 1, 2, 3:


1. Permisos.

Puede conceder acceso de lectura/escritura a la tarjeta SD externa en los diferentes niveles de api (API23 + en tiempo de ejecución).

Desde KitKat, los permisos son no necesarios {[38] } si utiliza directorios específicos de la aplicación, requeridos de lo contrario:

Con Kitkat sus posibilidades de una "solución completa" sin enraizamiento son prácticamente cero: el proyecto Android definitivamente ha jodido aqui. No hay aplicaciones obtener 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 multimedia: no puedes retag / reorganizar su colección de medios por más tiempo, ya que esas aplicaciones no puedo escribirle.
  • aplicaciones de oficina: más o menos lo mismo

El único lugar 3 rd se permite que las aplicaciones de terceros escribe en tu tarjeta externa son "sus propios directorios" (es decir, /sdcard/Android/data/<package_name_of_the_app>).

Las únicas formas de realmente fijar que requieren ya sea el fabricante (algunos de ellos fijo es, por ejemplo, Huawei con su actualización Kitkat para el P6) - o raíz... (La explicación de Izzy continúa aquí)


2. Sobre la solución universal.

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

Este hecho es demostrado por estos ejemplos de configuraciones de almacenamiento externo para dispositivos.

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

A partir de Android 4.4, el propietario, el grupo y modos de archivos en los dispositivos de almacenamiento externo ahora se sintetizan en función del directorio estructura. Esto permite a las aplicaciones administrar su paquete específico directorios en almacenamiento externo sin necesidad de que tengan la amplia WRITE_EXTERNAL_STORAGE permiso. Por ejemplo, la aplicación con paquete name com.example.foo ahora puede acceder libremente Android/data/com.example.foo/ en dispositivos de almacenamiento externo con no permiso. Estos permisos sintetizados se logran mediante empaquetando dispositivos de almacenamiento raw en un demonio FUSE.

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

  • / mnt / runtime / default se muestra a las aplicaciones sin almacenamiento especial permiso...
  • /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.

Usarí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.

Mejorar la heurística para determinar lo que representa medios extraíbles basados en los diferentes niveles de api como android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT

Recuerde que Android 6.0 admite dispositivos de almacenamiento portátilesy las aplicaciones de terceros deben pasar por el Marco de acceso de almacenamiento . Sus dispositivos HTC10 y Shield K1 son probablemente API 23.

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

<permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
<group gid="sdcard_r" />
<group gid="sdcard_rw" />
<group gid="media_rw" /> // this line is added via root in the link to fix it.
</permission>

Nunca lo probé, así que no puedo compartir código pero evitaría forintentar escribir en todos los directorios devueltos y buscar el mejor directorio de almacenamiento disponible para escribir en base al espacio restante.

Quizás La alternativa de Gizm0 a su método getStorageDirectories() es un buen punto de partida.

ContextCompat.getExternalFilesDirs resuelve el problema si no necesitas acceso a otras carpetas.


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

Añade el siguiente código a tu AndroidManifest.xml y lee Obtener acceso al almacenamiento externo

Con el fin de ... escribir archivos en el almacenamiento externo, su aplicación debe adquirir ... sistema permisos:

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

Si necesitas ambos..., usted necesita solicitar solo el permiso WRITE_EXTERNAL_STORAGE.

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

Nota: Comenzando con Android 4.4, estos permisos no son necesarios si estás leyendo o escribiendo solo archivos que son privados para tu app. Para más información..., ver guardar archivos que son app-private .

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                     android:maxSdkVersion="18" />
</manifest>

Solicitar permisos en tiempo de ejecución si el nivel de API 23+ y leer Solicitando permisos en tiempo de ejecución

A partir de Android 6.0 (nivel de API 23), los usuarios otorgan permisos a aplicaciones mientras la aplicación se está ejecutando, no cuando se instala la aplicación ... o actualizar 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 trate de usar Doomsknight método 1, método 2 de lo contrario.

Lea La explicación de Mark Murphy y recomendado Dianne Hackborn y Dave Smith ones

  • Hasta Android 4.4, no había soporte oficial para medios extraíbles en Android, a partir de KitKat, el concepto de el almacenamiento externo" primario "y" secundario " emerge en la API de FMW.
  • Las aplicaciones anteriores solo se basan en la indexación de MediaStore, se envían con el hardware o examinan los puntos de montaje y aplican algunas heurísticas para determinar qué representa el medio extraíble.
  • Desde Android 4.2, ha habido una solicitud de Google para que los fabricantes de dispositivos bloqueen los medios extraíbles para la seguridad (soporte multiusuario) y se agregaron nuevas pruebas en 4.4.
  • Desde KiKat getExternalFilesDirs() y otros métodos fueron añadido para devolver una ruta utilizable en todos los volúmenes de almacenamiento disponibles (El primer item returned is the primary volume).
  • La siguiente tabla indica lo que un desarrollador podría intentar hacer y cómo responderá KitKat: introduzca la descripción de la imagen aquí

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

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

También lea La explicación de Paolo Rovelli y trate para usar La solución de Jeff Sharkey desde KitKat:

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

El nuevo Context.getExternalFilesDirs() y Context.getExternalCacheDirs() los métodos pueden devolver múltiples rutas, incluyendo dispositivos primarios y secundarios.

Luego 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 helper DocumentFile.

getStorageState Añadido en API 19, obsoleto en API 21, uso getExternalStorageState(File)

Aquí hay un gran tutorial para interactuar con el Acceso al almacenamiento Marco en KitKat.

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


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

Scoped Directory Access En Android 7.0, las apps pueden usar nuevas API para solicitar acceso a directorios de almacenamiento externo , incluidos directorios en medios extraíbles como tarjetas SD...

Para obtener más información, consulte el entrenamiento de Acceso a Directorios con alcance .


8. Android O cambio.

Comenzando en Android O , el El marco de acceso al almacenamiento permite documentos personalizados proveedores para crear descriptores de archivo buscables para archivos que residen en un remoto fuente de datos...

Permisos, antes de Android O, si una aplicación solicitaba un permiso en tiempo de ejecución y el permiso se concedía, el sistema también otorgaba incorrectamente la app el resto de permisos que pertenecían a la misma grupo de permiso, y que se registraron en el manifiesto.

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

Por ejemplo, READ_EXTERNAL_STORAGE y WRITE_EXTERNAL_STORAGE...


9. Preguntas relacionadas y recomendaciones respuesta.

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

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

Diff entre getExternalFilesDir y getExternalStorageDirectory()

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

Cómo usar la nueva API SD card ACCESS presentada para Android 5.0 (Lollipop)

Escribir a un externo Tarjeta SD en Android 5.0 y superior

Permiso de escritura de la tarjeta SD de Android usando SAF (Storage Access Framework)

SAFFAQ: Preguntas frecuentes sobre el Marco de Acceso al Almacenamiento


10. Errores y problemas relacionados.

Bug: En Android 6, al usar getExternalFilesDirs, no te permitirá crear nuevos archivos en sus resultados

Escribir al directorio devuelto por getExternalCacheDir () en Lollipop falla sin escribir permiso

 84
Author: albodelu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-09 20:19:48

Creo que hay dos métodos para lograr esto:

MÉTODO 1: (NO funciona en la versión 6.0 y superior, debido a cambios en los permisos)

He estado usando este método durante años en muchas versiones de dispositivos sin ningún problema. 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 SD Reales) en una lista de ubicaciones de directorios de cadenas. Con la lista puede preguntar al usuario dónde guardar, etc.

Puedes llamarlo con lo siguiente:

 HashSet<String> extDirs = getStorageDirectories();

Método:

/**
 * Returns all the possible SDCard directories
 */
public static HashSet<String> getStorageDirectories() {
    final HashSet<String> out = new HashSet<String>();
    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:

Utilice la biblioteca de soporte v4

import android.support.v4.content.ContextCompat;

Simplemente llame a lo siguiente para obtener una lista de File ubicaciones 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 dispositivos de almacenamiento externo donde la aplicación puede colocar archivos persistentes le pertenece. Estos los archivos son internos de la aplicación, y normalmente no visible para el usuario como medio.

Los dispositivos de almacenamiento externo devueltos aquí se consideran una parte permanente de el dispositivo, incluyendo tanto el almacenamiento externo emulado como los medios físicos ranuras, como tarjetas SD en un compartimiento de baterías. Las rutas devueltas hacen no incluya dispositivos transitorios, como unidades flash USB.

Una aplicación puede almacenar datos en cualquiera o todos los dispositivos devueltos. Para por ejemplo, una aplicación puede optar por almacenar archivos de gran tamaño en el espacio más disponible

Más información sobre ContextCompat

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

 9
Author: Doomsknight,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-10 15:57:52

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

Esto se publicó originalmente aquí (Hay una manera de obtener el tamaño de la tarjeta SD en Android?) por mí 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<File> en lugar de File y dejar el bucle continúa en lugar de llamar break después de que se encuentre el primero.

 2
Author: ᴛʜᴇᴘᴀᴛᴇʟ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:47

Además de todas las otras respuestas agradables, 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 2nd edition, capítulo 16, página 294.

El libro describe los métodos básicos y externos de archivo y directorio. Voy a tratar de hacer un currículum de lo que podría ser relevante para su pregunta.

La siguiente parte del libro:

Almacenamiento externo

Su foto necesita más que un lugar en la pantalla. Imágenes de tamaño completo son demasiado grandes para pegarse dentro de un Base de datos SQLite, mucho menos un Intent. Necesitarán un lugar para vivir en el sistema de archivos de su dispositivo. Normalmente, los pondrías en tu 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(), puedes hacer lo mismo con archivos regulares, también (que vivirán en un subcarpeta adyacente a la subcarpeta de bases de datos en la que vive su base de datos SQLite)

Métodos básicos de archivo y directorio 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 lo necesitas.

Por otro lado, si necesitas otra aplicación para escribir en esos archivos, no tienes suerte: mientras hay una bandera Context.MODE_WORLD_READABLE que puede pasar a openFileOutput(String, int), es ser obsoleto, y no completamente fiable en sus efectos en los dispositivos más nuevos. Si está almacenando archivos para compartir con otras aplicaciones o recibiendo archivos de otras aplicaciones( archivos como imágenes almacenadas), debe almacenarlos en almacenamiento externo en su lugar.

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

El contexto también proporciona bastantes métodos para acceder al almacenamiento externo. Estos métodos proporcionan fácil maneras de llegar a su almacenamiento externo principal, y un poco-sorta-maneras fáciles de llegar a todo lo demás. Todos estos métodos también almacenan archivos en lugares disponibles públicamente, así que tenga cuidado con ellos.

Archivo Externo y métodos de directorio 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 utilizan una tarjeta SD extraíble para el almacenamiento externo. En la práctica, esto rara vez es un problema, porque casi todos los dispositivos modernos tienen almacenamiento interno no extraíble para su almacenamiento "externo". Así que no vale la pena ir a extremos extremos para dar cuenta de ello. Pero recomendamos incluir código simple para evitar la posibilidad, lo que hará en un momento.

Permiso de almacenamiento externo

En general, necesita un permiso para escribir o leer desde un almacenamiento externo. Los permisos son valores de cadena bien conocidos que pones en tu manifiesto usando la etiqueta <uses-permission>. Dicen Android que quieres hacer algo que Android quiere que pedir permiso para.

Aquí, Android espera que le pidas permiso porque quiere hacer cumplir alguna responsabilidad. Le dices a Android que necesitas acceder al almacenamiento externo, y Android le dirá al usuario que esta es una de las cosas que hace tu aplicación cuando intentan instalarla. De esa manera, 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 viven allí. Así que en Android 4.4 (API 19) y arriba, usted no necesita este permiso para esta carpeta. (Pero todavía lo necesita para otros tipos de almacenamiento externo.)

Agregue una línea a su manifiesto que solicita el permiso para leer almacenamiento externo, pero solo hasta la lista de API 16.5 Que solicita permiso de almacenamiento externo(AndroidManifest.xml)

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.bignerdranch.android.criminalintent" >
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
         android:maxSdkVersion="18" />

El atributo maxSdkVersion hace que tu aplicación solo solicite este permiso en versiones de Android que sean anteriores a API 19, Android KitKat. Tenga en cuenta que solo está pidiendo leer el almacenamiento externo. También hay un permiso WRITE_EXTERNAL_STORAGE, pero no lo necesitas. 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 saltar a Usando la sección Almacenamiento externo.

Referencia:

Leer Más cosas:

Descargo de responsabilidad: Esta información fue tomada de Android Programming: The Big Nerd Ranch Guide con el permiso de los autores. Para obtener más información sobre este libro o para comprar una copia, visite bignerdranch.com.

 2
Author: maytham-ɯɐɥʇʎɐɯ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-09 10:00:02

Este tema es un poco viejo, pero estaba buscando una solución y después de algunas investigaciones, vine con el código a continuación para recuperar una lista de puntos de montaje "externos" disponibles que, según mi conocimiento, funciona en muchos dispositivos diferentes.

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

Por supuesto, los permisos requeridos deben ser otorgados antes de que el código sea invocar.

// Notice: FileSystemDevice is just my own wrapper class. Feel free to replace it with your own. 

private List<FileSystemDevice> getDevices() {

    List<FileSystemDevice> 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;
}
 0
Author: Václav Hodek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-30 23:20:08

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). Simplemente reemplace " foldername "con el nombre de la carpeta de destino deseada y" filename " con el nombre del archivo que está guardando. Por supuesto, aquí puede ver cómo guardar un archivo genérico, ahora puede buscar cómo guardar imágenes tal vez aquí o lo que sea en un archivo.

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

Esto también controla que el archivo existe y agrega un número al final de el nombre del nuevo archivo para guardarlo.

Espero que ayude!

EDITAR: Lo siento, olvidé que tienes que pedir <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> en tu manifiesto (o programáticamente si lo prefiere de Malvavisco)

 -1
Author: Hugo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:42
 
`Log.d(TAG, System.getenv("SECONDARY_STORAGE"));`

Salida:

`D/MainActivity: /storage/extSdCard`

Trabajando en Note 3 Android 5.0.

 -2
Author: user2773829,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-26 05:10:15

Para las versiones por debajo de Marshmallow puede dar directamente los permisos en el manifiesto.

Pero para los dispositivos con Marshmallow y superior necesita otorgar los permisos en tiempo de ejecución.

Usando

Environment.getExternalStorageDirectory();

Puede acceder directamente a la tarjeta SD externa (Montada una) Espero que esto ayude.

 -3
Author: Geet Choubey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-16 10:08:13