Saltar al contenido

Manera universal de escribir en una tarjeta SD externa en Android

Solución:

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 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 (API nivel 28) o versiones anteriores no ven ningún cambio, de forma predeterminada.

Las aplicaciones que se dirigen 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, salvo 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 en el ámbito de la aplicación y en el ámbito de los 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:

  • Idealmente, use Storage Access Framework y DocumentFile como señaló Jared Rummler. O:
  • Utilice la ruta específica de su aplicación/storage/extSdCard/Android/data/com.myapp.example/files.
  • Agregue permiso de lectura / escritura para manifestar para pre-KitKat, no se requiere permiso más adelante para esta ruta.
  • Intente usar la ruta de su aplicación y los métodos de Doomsknight considerando el estuche KitKat y Samsung.
  • Filtre y use getStorageDirectories, la ruta de su aplicación y los permisos de lectura / escritura antes de KitKat.
  • ContextCompat.getExternalFilesDirs desde KitKat. Teniendo en cuenta los dispositivos que regresan internos primero.

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 …

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

Mejore la heurística para determinar qué representa los 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

… 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 una excepción de permiso denegado para acceder /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 el código, pero evitaría el for intentando escribir en todos los directorios devueltos y buscar el mejor directorio de almacenamiento disponible para escribir en función del espacio restante.

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

ContextCompat.getExternalFilesDirs resuelve 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("https://foroayuda.es/"))
                        if (!part.toLowerCase(Locale.US).contains("vold"))
                            out.add(part);
                }
            }
        }
    }
    return out;
}

Agregue el siguiente código a su AndroidManifest.xml y 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 la WRITE_EXTERNAL_STORAGE permiso.

A partir de Android 4.1, el acceso de lectura está protegido con la READ_EXTERNAL_STORAGE permiso.

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 …, debe solicitar solo el WRITE_EXTERNAL_STORAGE permiso.

Lea la explicación de Mark Murphy y las publicaciones recomendadas de Dianne Hackborn y Dave Smith

  • 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 multiusuario) y se agregaron nuevas pruebas en 4.4.
  • Desde KitKat getExternalFilesDirs() y se agregaron 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 cómo 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.

El nuevo Context.getExternalFilesDirs() y
Context.getExternalCacheDirs() Los métodos pueden devolver varias rutas, incluidos los dispositivos primarios y secundarios.

Luego puede iterar sobre ellos y verificar 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.

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 mantengan la amplia
WRITE_EXTERNAL_STORAGE permiso. Por ejemplo, la aplicación con el nombre del paquete com.example.foo ahora 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 enraizamiento 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 de medios, ya que esas aplicaciones no pueden escribir en ella.
  • aplicaciones de oficina: prácticamente lo mismo

El único lugar 3rd Las aplicaciones de fiesta que pueden escribir en su tarjeta externa son “sus propios directorios” (es decir,
/sdcard/Android/data/<package_name_of_the_app>).

Las únicas formas de solucionarlo realmente requieren que sea el 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.

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

Aquí hay un gran 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 es necesario en tiempo de ejecución. Porque el nuevo modelo incluye el READ/WRITE_EXTERNAL_STORAGE permisos, la plataforma necesita otorgar acceso al almacenamiento de forma dinámica sin matar o 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 a las aplicaciones sin permisos especiales de almacenamiento …
  • / mnt / runtime / read se muestra en 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:

Nota que el acceso al directorio de ámbito agregado en 7.0 está obsoleto en Android Q.

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

Agregaron un 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 solicitó un permiso en tiempo de ejecución y se otorgó el permiso, el sistema también otorgó 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 aplicaciones orientadas 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, todas las solicitudes posteriores de permisos en ese grupo de permisos se otorgan automáticamente.

Por ejemplo, READ_EXTERNAL_STORAGE y WRITE_EXTERNAL_STORAGE

Actualizar: Una versión beta anterior de Android Q reemplazó temporalmente al READ_EXTERNAL_STORAGE y WRITE_EXTERNAL_STORAGE permisos con permisos más detallados y específicos de 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 de las colecciones de medios que se introdujeron en versiones beta anteriores:READ_MEDIA_IMAGES, READ_MEDIA_AUDIO, y READ_MEDIA_VIDEO—Ahora están obsoletos. Más información:

Q Beta 4 (API finales) revisión de Mark Murphy: La muerte del almacenamiento externo: El final 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

Creo que hay dos métodos para lograrlo:

MÉTODO 1: (lo hace NO trabajar en 6.0 y 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ó.

Volverá 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("https://foroayuda.es/"))
                        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 al 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 los dispositivos de almacenamiento externos donde la aplicación puede colocar archivos persistentes de su propiedad. 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 en todos los dispositivos devueltos. Por ejemplo, una aplicación puede optar por almacenar archivos grandes en el dispositivo con más espacio disponible.

Más información sobre ContextCompat

Son como archivos específicos de una 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 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 recibo la “primera” tarjeta SD externa, sin embargo, puede modificarla y devolverla. ArrayList<File> en lugar de File y deja que el ciclo continúe en lugar de llamar break después de encontrar el primero.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *