Manera universal de escribir en una tarjeta SD externa en Android


76

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

Leí en todas partes que Android no permite a los usuarios escribir en una tarjeta SD externa, por tarjeta SD me refiero a la tarjeta SD externa y montable y no al almacenamiento externo , pero las aplicaciones de administración 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 resultado: como puede ver, estoy verificando los 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 obtengo un error de acceso, probé 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

las aplicaciones del sistema pueden acceder al almacenamiento externo de la tarjeta SD por completo, pero otras aplicaciones no pueden a menos que el sistema operativo y la aplicación tengan acceso de root
Pavneet_Singh

4
@PavneetSingh esto no es cierto, todas las aplicaciones de exploración de archivos tienen acceso a la tarjeta SD externa, incluso sin root.
Vektor88

¿De qué explorador de archivos estás hablando? porque algún famoso, utiliza las secuencias de comandos de enraizamiento a la tarjeta SD de acceso
Pavneet_Singh

1
usan el método que le dije, para la prueba solo tenga un sistema operativo kitkat (no rooteado) e instale ES e intente eliminar un archivo de él, recibirá una advertencia (esto puede hacer que su teléfono sea un ladrillo) pidiéndole que aplique root proceso bajo su propio riesgo
Pavneet_Singh

1
línea del enlace , le recomendaría encarecidamente que NUNCA confíe en este código , como dije, su aplicación por sí sola no puede hacerlo, pero el proveedor de medios es una aplicación del sistema, por lo que puede aprovechar su función para hacer lo que pueda
Pavneet_Singh

Respuestas:


121

Resumen

Puede otorgar 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 no son necesarios si usa directorios específicos de la aplicación; de lo contrario , se requieren .

Manera universal:

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

Este hecho lo demuestran estos ejemplos de configuraciones de almacenamiento externo para dispositivos .

Manera basada en API:

Antes de KitKat, intente utilizar el método 1 de Doomsknight, de lo contrario el método 2.

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

Forma recomendada:

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

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

Manera consciente de la privacidad:

A partir de Android Q Beta 4 , las aplicaciones destinadas a Android 9 (nivel de API 28) o versiones anteriores no ven ningún cambio, de forma predeterminada.

Las aplicaciones destinadas a Android Q de forma predeterminada (o que lo habilitan) reciben una vista filtrada en el almacenamiento externo.


1. Respuesta inicial.

Manera universal de escribir en una tarjeta SD externa en Android

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

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

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

  • Lollipop: API agregadas para permitir que las aplicaciones soliciten acceso a carpetas que pertenecen a otros proveedores.

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

  • ... Cambio de privacidad de Android Q: almacenamiento de ámbito de aplicación y de ámbito de medios

¿Cuál es la mejor manera de otorgar acceso de lectura / escritura a la tarjeta SD externa en diferentes niveles de API?

Basado en la respuesta de Doomsknight y la mía , y en las publicaciones del blog de Dave Smith y Mark Murphy: 1 , 2 , 3 :


2. Respuesta actualizada.

Actualización 1 . Probé el Método 1 de la respuesta de Doomknight, sin resultado:

Como puede ver, estoy verificando los permisos en tiempo de ejecución antes de intentar escribir en SD ...

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

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

... Pero me sale un error de acceso, probé en dos dispositivos diferentes: HTC10 y Shield K1.

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

Su registro muestra un permiso denegado de acceso de excepción/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ía el mejor directorio de almacenamiento disponible para escribir en función del espacio restante .

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

ContextCompat.getExternalFilesDirsresuelve el problema si no necesita acceder a otras carpetas .


3. Android 1.0 .. Pre-KitKat.

Antes de KitKat, intente usar el método 1 de Doomsknight o lea esta respuesta de Gnathonic.

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

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

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 WRITE_EXTERNAL_STORAGEpermiso .

A partir de Android 4.1, el acceso de lectura está protegido con el READ_EXTERNAL_STORAGEpermiso.

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

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

Si necesita ambos ..., solo debe solicitar el WRITE_EXTERNAL_STORAGEpermiso.

Leer la explicación de la marca Murphy y recomendados Dianne Hackborn y Dave Smith mensajes

  • Hasta Android 4.4, no había soporte oficial para medios extraíbles en Android. A partir de KitKat, el concepto de almacenamiento externo "primario" y "secundario" surge 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 los medios extraíbles.

4. Android 4.4 KitKat presenta Storage Access Framework (SAF) .

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

  • Desde Android 4.2, ha habido una solicitud de Google para que los fabricantes de dispositivos bloqueen los medios extraíbles por seguridad (soporte para múltiples usuarios) y se agregaron nuevas pruebas en 4.4.
  • Dado getExternalFilesDirs()que se agregaron KitKat y otros métodos 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 hacer y cómo responderá KitKat: ingrese la descripción de la imagen aquí

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 privados de la aplicación .

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

Lea también la explicación de Paolo Rovelli e intente 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 métodos nuevos Context.getExternalFilesDirs()y Context.getExternalCacheDirs()pueden devolver varias rutas, incluidos los dispositivos primarios y secundarios.

Luego, puede iterar sobre ellos y verificar Environment.getStorageState()y File.getFreeSpace()determinar el mejor lugar para almacenar sus archivos.

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

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

Con KitKat, sus posibilidades de obtener una "solución completa" sin rootear son prácticamente nulas:

El proyecto de Android definitivamente se ha equivocado aquí. Ninguna aplicación tiene acceso completo a tarjetas SD externas:

  • 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: ya no puede volver a etiquetar / reorganizar su colección multimedia, ya que esas aplicaciones no pueden escribir en ella.
  • aplicaciones de oficina: prácticamente lo mismo

El único sitio 3 ª Parte aplicaciones pueden escribir en la tarjeta externa son "sus propios directorios" (es decir /sdcard/Android/data/<package_name_of_the_app>).

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


5. Android 5.0 introdujo cambios y la clase auxiliar DocumentFile .

getStorageStateAgregado en API 19, obsoleto en API 21, usegetExternalStorageState(File)

Aquí hay un excelente tutorial para interactuar con Storage Access Framework en KitKat.

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


6. Android 6.0 Marshmallow presenta un nuevo modelo de permisos en tiempo de ejecución .

Solicite permisos en tiempo de ejecución si API nivel 23+ y lea Solicitud de permisos en tiempo de ejecución

A partir de Android 6.0 (nivel de API 23), los usuarios otorgan permisos a las aplicaciones mientras la aplicación se está ejecutando, 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);

Android 6.0 presenta un nuevo modelo de permisos en tiempo de ejecución donde las aplicaciones solicitan capacidades cuando las necesitan en tiempo de ejecución. Debido a que el nuevo modelo incluye los READ/WRITE_EXTERNAL_STORAGEpermisos, la plataforma debe otorgar acceso al almacenamiento de forma dinámica sin matar ni reiniciar las aplicaciones que ya se están ejecutando. Lo hace manteniendo tres vistas distintas de todos los dispositivos de almacenamiento montados:

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

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

Acceso a directorios con alcance 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 de Acceso a directorios con ámbito .

Lea las publicaciones de Mark Murphy: Tenga cuidado con el acceso al directorio con alcance . Quedó obsoleto en Android Q :

Tenga en cuenta que el acceso al directorio con ámbito agregado en 7.0 está obsoleto en Android Q.

Específicamente, el createAccessIntent()método en StorageVolume está en desuso.

Agregaron una createOpenDocumentTreeIntent()que se puede utilizar como alternativa.


8. Android 8.0 Oreo .. Cambios en Android Q Beta.

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 solicitaba un permiso en tiempo de ejecución y se concedía el permiso, el sistema también concedía incorrectamente a la aplicación el resto de los permisos que pertenecían al mismo grupo de permisos y que estaban registrados en el manifiesto.

Para las aplicaciones destinadas a Android O , este comportamiento se ha corregido. La aplicación solo recibe los permisos que solicitó 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_STORAGEy WRITE_EXTERNAL_STORAGE...

Actualización: una versión beta anterior de Android Q reemplazó temporalmente los permisos READ_EXTERNAL_STORAGEy WRITE_EXTERNAL_STORAGEcon permisos más detallados y específicos para los medios.

Nota: Google introdujo roles en Beta 1 y los eliminó de la documentación antes de Beta 2 ...

Nota: los permisos específicos a las colecciones de los medios que se introdujeron en releases- anterior beta READ_MEDIA_IMAGES, READ_MEDIA_AUDIOy READ_MEDIA_VIDEO- se han quedado obsoletas . Más información:

Revisión de Q Beta 4 (API finales) de Mark Murphy: La muerte del almacenamiento externo: El fin de la saga (?)

"La muerte es más universal que la vida. Todos mueren, pero no todos viven". - Andrew Sachs


9. Preguntas relacionadas y respuestas recomendadas.

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

mkdir () funciona 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 utilizar 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

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.

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


2
Un análisis muy en profundidad. +1. Aunque debo señalar, no escribí ese método. Es de otra fuente hace un par de años :) Espero que le ayude.
IAmGroot

Gracias por la respuesta completa. Me parece que el problema aquí es que debería usar SAF para escribir en la raíz de mi tarjeta SD en lugar de usar el método 1 de @Doomsknight, porque no funcionará en niveles de API más altos, mientras que debajo de KitKat puedo usarlo.
Vektor88

Np, si necesita escribir fuera del directorio de su aplicación, parece la forma correcta de usar la API oficial . Parece que aquí se resolvió un problema similar, lo comprobaré otro día.
albodelu

¿Cómo ES File Explorer en teléfonos 4.4.2 creando carpetas? @albodelu ¿Puede usted explicar por favor
karanatwal.github.io

En el punto 3, hay un enlace donde se explica cómo hacerlo para dispositivos rooteados. Esta aplicación tiene una opción para deshabilitar la limitación agregada en kitKat: Tool> Root Explorer> Mount R / W
albodelu

10

Creo que hay dos métodos para lograrlo:

MÉTODO 1: ( NO funciona en 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 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 SD reales) en una lista de ubicaciones del directorio de cadenas. Con la lista, puede preguntarle al usuario dónde guardar, etc.

Puede 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 los siguientes para obtener una lista de Fileubicaciones 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 externos donde la aplicación puede colocar los archivos persistentes que posee. Estos archivos son internos de la aplicación y, por lo general, el usuario no puede verlos como medios.

Los dispositivos de almacenamiento externo devueltos aquí se consideran una parte permanente del dispositivo, incluido el almacenamiento externo emulado y las ranuras para medios físicos, como las tarjetas SD en el compartimento 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 optar por almacenar archivos grandes en el dispositivo con el mayor espacio disponible

Más información sobre ContextCompat

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


1
Comparta un enlace donde obtuvo el código en el método 1. Crédito donde se debe. Gracias.
Eugen Pechanec

2
@EugenPechanec Estoy completamente de acuerdo. sin embargo, hace años obtuve este código de una fuente fantástica. Lo buscaré
IAmGroot

1
No tengo claro qué debo hacer con estas rutas: si el dispositivo no está rooteado, no obtendré acceso de escritura a la tarjeta SD especificando su ruta absoluta. ¿Me equivoco? Tal vez pueda almacenar algunos datos en la carpeta de la aplicación dedicada en /external_sd/data/data/com.myapp ..., pero no podré escribir en / external_sd / MyApp / Images
Vektor88

1
@ vektor88 Puede acceder a él bien sin rootear. Ni mis clientes ni yo usamos dispositivos rooteados. ¿Qué error estás recibiendo? ¿Agregaste el permiso? Y como se indica a continuación, está apuntando a lo último y solicita permiso en tiempo de ejecución. Cambie el objetivo a 20 y vea si funciona, antes de resolver la solicitud de permiso de tiempo de ejecución. De lo contrario, proporcione un error.
IAmGroot

1
@ Vektor88 Ese concepto es puramente como una prueba, para ver si ahora tiene problemas de permisos. Si está apuntando a api 23, entonces responde la pregunta, ¿solicitó permisos en tiempo de ejecución? Aunque este es un tema separado del deseo de obtener la ubicación de la tarjeta SD para guardar (como se cuestionó originalmente). developer.android.com/training/permissions/requesting.html
IAmGroot

3

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 versiones anteriores.

Esto se publicó originalmente aquí ( ¿Hay alguna 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 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 regresar en ArrayList<File>lugar de Filedejar que el ciclo continúe en lugar de llamar breakdespués de encontrar la primera.


Gracias @ th3pat3l isExternalStorageRemovablereferencia developer.android.com/reference/android/os/…
albodelu

1
Esto solo funciona para acceder al subdirectorio de la tarjeta SD externa según su aplicación: por ejemplo, en mi teléfono LG me da: /storage/0403-0201/Android/data/com.your.application/files - si desea agregar un archivo en el directorio DCIM, incluso si tiene permisos en su manifiesto y los solicita en tiempo de ejecución, y accede a su aplicación-> permisos en la configuración y activa Almacenamiento. AÚN CONSIGUE ENOENT (No existe tal archivo o directorio ) error si intenta crear y abrir un archivo allí. Al menos eso es lo que está sucediendo en mi LG Aristo SDK 23. Lo único que veo que funciona es SAF.
MarkJoel60

isExternalStorageRemovablees solo para> = 21, ¿es posible determinar si el almacenamiento es extraíble (tarjeta microsd) en <= 19 dispositivos API, implementando un método similar de alguna manera?
user924

3

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

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

El libro describe los métodos básicos y externos de archivos y directorios. Intentaré 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 quedarse dentro de una base de datos SQLite, y mucho menos un archivo 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 archivos normales (que vivirán en una subcarpeta adyacente a la subcarpeta de bases de datos en la que reside su base de datos SQLite)

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

| Method                                                                                |
|---------------------------------------------------------------------------------------|
|File getFilesDir()                                                                      |
| - 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: si bien hay una Context.MODE_WORLD_READABLEbandera a la que puede pasar openFileOutput(String, int), está obsoleta y no es completamente confiable en sus efectos en los dispositivos más nuevos. Si almacena archivos para compartir con otras aplicaciones o recibe archivos de otras aplicaciones (archivos como imágenes almacenadas), debe almacenarlos en un 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(). Esta puede ser una tarjeta SD, pero hoy en día se integra más comúnmente en el dispositivo. Algunos dispositivos pueden tener almacenamiento externo adicional. Eso se incluiría en "todo lo demás".

El contexto también proporciona bastantes métodos para acceder al almacenamiento externo. Estos métodos proporcionan formas fáciles de acceder a su almacenamiento externo principal y formas bastante sencillas de acceder a todo lo demás. Todos estos métodos también almacenan archivos en lugares disponibles públicamente, 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, porque casi todos los dispositivos modernos tienen almacenamiento interno no extraíble para su almacenamiento "externo". Por lo tanto, no vale la pena hacer todo lo posible para explicarlo. Pero recomendamos incluir un código simple para protegerse contra 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 <uses-permission>etiqueta. Le dicen a Android que quieres hacer algo para lo que Android quiere que pidas permiso.

En este caso, Android espera que pidas 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 esa forma, nadie se sorprende cuando comienzas 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 de su aplicación, tiene sentido que desee poder leer y escribir archivos que viven allí. Entonces, en Android 4.4 (API 19) y versiones posteriores, 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 de API 16.5 Solicitar 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 su aplicación solo solicite este permiso en versiones de Android anteriores a API 19, Android KitKat. Tenga en cuenta que solo solicita leer el almacenamiento externo. También hay un WRITE_EXTERNAL_STORAGEpermiso, 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 para leerlo todo, pero también puede saltar a la sección Uso de almacenamiento externo .

Referencia:

Más material de lectura:

Descargo de responsabilidad: esta información se tomó de Programación de Android: 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.


0

Este tema es un poco antiguo pero estaba buscando una solución y, después de investigar un poco, vine con el siguiente código para recuperar una lista de puntos de montaje "externos" disponibles que, según mi conocimiento, funcionan 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, se deben conceder los permisos necesarios antes de invocar el código.

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

-1

Esta es una forma de crear un nuevo archivo en el almacenamiento externo (tarjeta SD si está presente en el dispositivo o en el almacenamiento externo del dispositivo si no lo está). Simplemente reemplace "nombre de carpeta" con el nombre de la carpeta de destino deseada y "nombre de archivo" 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 del nombre del nuevo archivo para guardarlo.

¡Espero eso ayude!

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


-3

Para versiones por debajo de Marshmallow, puede otorgar los permisos directamente en el manifiesto.

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

Mediante el uso

Environment.getExternalStorageDirectory();

puede acceder directamente a la tarjeta SD externa (montada) Espero que esto ayude.


Environment.getExternalStorageDirectory();le dará la ruta a la tarjeta SD interna del dispositivo.
Vektor88

Ahora, desde Android Lollipop, tiene dos opciones, para usar la tarjeta SD externa como almacenamiento interno o como una tarjeta SD típica, también depende de esa selección.
Geet Choubey


2
Ese fragmento de código me da la ruta donde está montada la tarjeta SD externa. ¿Entonces que? Todavía no puedo escribir en él.
Vektor88

¿Qué error obtiene cuando intenta escribir en una tarjeta SD externa?
Geet Choubey
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.