Accede a archivos específicos de la app

En muchos casos, la app crea archivos a los que otras apps no necesitan o no deberían acceder. El sistema proporciona las siguientes ubicaciones para almacenar los archivos específicos de la app:

  • Directorios de almacenamiento interno: estos directorios incluyen una ubicación dedicada a fin de almacenar archivos persistentes y otra ubicación para almacenar datos en caché. El sistema evita que otras apps accedan a las ubicaciones que, en Android 10 (API nivel 29) o versiones posteriores, están encriptadas. Estas características hacen que las ubicaciones sean un buen lugar para almacenar datos sensibles a los que solo tu app puede acceder.

  • Directorios de almacenamiento externo: estos directorios incluyen una ubicación dedicada a fin de almacenar archivos persistentes y otra ubicación para almacenar datos en caché. Aunque es posible que otra app acceda a los directorios si tiene los permisos adecuados, los archivos almacenados allí son para uso exclusivo de tu app. Si tienes la intención específica de crear archivos a los que otras apps puedan acceder, tu app debería almacenar esos archivos en la parte de almacenamiento compartido del almacenamiento externo.

Cuando el usuario desinstala tu app, se quitan los archivos guardados en el almacenamiento específico de la app. Por ese motivo, no debes usar este almacenamiento para guardar todo lo que el usuario espera que persista independientemente de tu app. Supongamos que tu app permite tomar fotos. El usuario esperará acceder a las imágenes incluso después de desinstalar la app. Por lo tanto, debes usar el almacenamiento compartido para guardar esos tipos de archivos en la colección de contenido multimedia correspondiente.

En las siguientes secciones, se describe cómo almacenar archivos dentro de directorios específicos de la app y acceder a ellos.

Cómo acceder desde el almacenamiento interno

El sistema proporciona directorios dentro del almacenamiento interno donde las apps pueden organizar sus archivos. Un directorio está diseñado para los archivos persistentes de la app y otro contiene los archivos almacenados en caché de la app. La app no requiere ningún permiso del sistema para leer y escribir en los archivos de estos directorios.

Las otras apps no pueden acceder a los archivos almacenados en el almacenamiento interno. Esto hace que el almacenamiento interno sea un buen lugar para los datos de apps a los que otras no deberían acceder.

Sin embargo, recuerda que esos directorios suelen ser pequeños. Antes de escribir archivos específicos de una app en el almacenamiento interno, la app debería consultar el espacio libre del dispositivo.

Cómo acceder a archivos persistentes

Los archivos persistentes comunes de la app están en un directorio al que puedes acceder con la propiedad filesDir de un objeto de contexto. El framework proporciona varios métodos para ayudarte a acceder y almacenar archivos en este directorio.

Cómo acceder a archivos y almacenarlos

Puedes usar la API de File para acceder a los archivos y almacenarlos.

Para ayudar a mantener el rendimiento de la app, no abras ni cierres el mismo archivo varias veces.

En el siguiente fragmento de código, se muestra cómo usar la API de File:

Kotlin

val file = File(context.filesDir, filename)

Java

File file = new File(context.getFilesDir(), filename);

Cómo almacenar un archivo con una transmisión

Como alternativa al uso de la API de File, puedes llamar a openFileOutput() para obtener una FileOutputStream que escriba en un archivo dentro del directorio filesDir.

En el siguiente fragmento de código, se muestra cómo escribir texto en un archivo:

Kotlin

val filename = "myfile"
val fileContents = "Hello world!"
context.openFileOutput(filename, Context.MODE_PRIVATE).use {
        it.write(fileContents.toByteArray())
}

Java

String filename = "myfile";
String fileContents = "Hello world!";
try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) {
    fos.write(fileContents.toByteArray());
}

Para permitir que otras apps accedan a los archivos almacenados en el directorio dentro del almacenamiento interno, usa un FileProvider con el atributo FLAG_GRANT_READ_URI_PERMISSION.

Cómo acceder a un archivo con una transmisión

Para leer un archivo como una transmisión, usa openFileInput():

Kotlin

context.openFileInput(filename).bufferedReader().useLines { lines ->
    lines.fold("") { some, text ->
        "$some\n$text"
    }
}

Java

FileInputStream fis = context.openFileInput(filename);
InputStreamReader inputStreamReader =
        new InputStreamReader(fis, StandardCharsets.UTF_8);
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(inputStreamReader)) {
    String line = reader.readLine();
    while (line != null) {
        stringBuilder.append(line).append('\n');
        line = reader.readLine();
    }
} catch (IOException e) {
    // Error occurred when opening raw file for reading.
} finally {
    String contents = stringBuilder.toString();
}

Cómo ver la lista de archivos

Puedes obtener un array con los nombres de todos los archivos del directorio filesDir llamando a fileList(), como se muestra en el siguiente fragmento de código:

Kotlin

var files: Array<String> = context.fileList()

Java

Array<String> files = context.fileList();

Cómo crear directorios anidados

También puedes crear directorios anidados o abrir un directorio interno llamando a getDir() en el código basado en Kotlin o pasando el directorio raíz y un nombre de directorio nuevo a un constructor File en el código basado en Java:

Kotlin

context.getDir(dirName, Context.MODE_PRIVATE)

Java

File directory = context.getFilesDir();
File file = new File(directory, filename);

Cómo crear archivos de caché

Si solo necesitas almacenar datos sensibles temporalmente, usa el directorio de caché designado de la app dentro del almacenamiento interno para guardar los datos. Al igual que con todo el almacenamiento específico de la app, los archivos almacenados en este directorio se quitan cuando el usuario desinstala la app, aunque es posible que los archivos de este directorio se quiten antes.

Para crear un archivo almacenado en caché, llama al método File.createTempFile():

Kotlin

File.createTempFile(filename, null, context.cacheDir)

Java

File.createTempFile(filename, null, context.getCacheDir());

La app accede a un archivo de este directorio mediante la propiedad cacheDir de un objeto de contexto y la API de File:

Kotlin

val cacheFile = File(context.cacheDir, filename)

Java

File cacheFile = new File(context.getCacheDir(), filename);

Cómo quitar archivos de caché

Aunque Android suele borrar los archivos de caché por su cuenta, no supongas que el sistema los borrará. Siempre debes mantener los archivos de caché de la app en el almacenamiento interno.

Para quitar un archivo del directorio de caché dentro del almacenamiento interno, usa uno de los siguientes métodos:

  • El método delete() en un objeto File que representa el archivo:

    Kotlin

    cacheFile.delete()
    

    Java

    cacheFile.delete();
    
  • El método deleteFile() del contexto de la app, que pasa el nombre del archivo:

    Kotlin

    context.deleteFile(cacheFileName)
    

    Java

    context.deleteFile(cacheFileName);
    

Cómo acceder desde el almacenamiento externo

Si el almacenamiento interno no tiene espacio suficiente para almacenar archivos específicos de la app, considera usar el almacenamiento externo. El sistema proporciona directorios dentro del almacenamiento externo en los que una app puede organizar archivos que aportan valor al usuario solo dentro de tu app. Un directorio está diseñado para los archivos persistentes de la app y el otro contiene los archivos almacenados en caché de la app.

En Android 4.4 (API nivel 19) o versiones posteriores, la app no necesita solicitar permisos relacionados con el almacenamiento para acceder a los directorios específicos de la app en el almacenamiento externo. Se quitan los archivos almacenados en esos directorios cuando se desinstala la app.

En los dispositivos que ejecutan Android 9 (nivel de API 28) o versiones anteriores, tu app puede acceder a los archivos específicos de la app que pertenecen a otras apps, siempre y cuando la app tenga los permisos de almacenamiento correspondientes. Para darles a los usuarios más control sobre sus archivos y acotar el desorden, las apps orientadas a Android 10 (nivel de API 29) y versiones posteriores reciben acceso con alcance específico al almacenamiento externo, o almacenamiento específico, de forma predeterminada. Cuando el almacenamiento específico está habilitado, las apps no pueden acceder a los directorios específicos de otras apps.

Cómo verificar que el almacenamiento esté disponible

Dado que el almacenamiento externo reside en un volumen físico que el usuario podría quitar, verifica que el volumen esté accesible antes de intentar leer o escribir datos específicos de la app en el almacenamiento externo.

Puedes llamar a Environment.getExternalStorageState() para consultar el estado del volumen. Si el estado que se muestra es MEDIA_MOUNTED, puedes leer y escribir archivos específicos de la app dentro del almacenamiento externo. Si es MEDIA_MOUNTED_READ_ONLY, solo puedes leer los archivos.

Por ejemplo, los siguientes métodos son útiles para determinar la disponibilidad del almacenamiento:

Kotlin

// Checks if a volume containing external storage is available
// for read and write.
fun isExternalStorageWritable(): Boolean {
    return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}

// Checks if a volume containing external storage is available to at least read.
fun isExternalStorageReadable(): Boolean {
     return Environment.getExternalStorageState() in
        setOf(Environment.MEDIA_MOUNTED, Environment.MEDIA_MOUNTED_READ_ONLY)
}

Java

// Checks if a volume containing external storage is available
// for read and write.
private boolean isExternalStorageWritable() {
    return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}

// Checks if a volume containing external storage is available to at least read.
private boolean isExternalStorageReadable() {
     return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ||
            Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
}

En dispositivos sin almacenamiento externo extraíble, usa el siguiente comando a fin de habilitar un volumen virtual para probar tu lógica de disponibilidad de almacenamiento externo:

adb shell sm set-virtual-disk true

Cómo seleccionar una ubicación de almacenamiento físico

En algunas ocasiones, un dispositivo que asigna una partición de la memoria interna como almacenamiento externo también proporciona una ranura para tarjeta SD. Eso significa que el dispositivo tiene varios volúmenes físicos que pueden contener almacenamiento externo. Por ese motivo, debes seleccionar cuál usar como almacenamiento específico de la app.

Para acceder a las diferentes ubicaciones, llama al método ContextCompat.getExternalFilesDirs(). Como se muestra en el fragmento de código, el primer elemento del arreglo que se muestra se considera el volumen de almacenamiento externo principal. Usa ese volumen, a menos que esté lleno o no esté disponible.

Kotlin

val externalStorageVolumes: Array<out File> =
        ContextCompat.getExternalFilesDirs(applicationContext, null)
val primaryExternalStorage = externalStorageVolumes[0]

Java

File[] externalStorageVolumes =
        ContextCompat.getExternalFilesDirs(getApplicationContext(), null);
File primaryExternalStorage = externalStorageVolumes[0];

Cómo acceder a archivos persistentes

Para acceder a archivos específicos de la app desde el almacenamiento externo, llama a getExternalFilesDir().

Para ayudar a mantener el rendimiento de la app, no abras ni cierres el mismo archivo varias veces.

En el siguiente fragmento de código, se muestra cómo llamar a getExternalFilesDir():

Kotlin

val appSpecificExternalDir = File(context.getExternalFilesDir(null), filename)

Java

File appSpecificExternalDir = new File(context.getExternalFilesDir(null), filename);

Cómo crear archivos de caché

Para agregar un archivo específico de la app en la caché dentro del almacenamiento externo, obtén una referencia al externalCacheDir:

Kotlin

val externalCacheFile = File(context.externalCacheDir, filename)

Java

File externalCacheFile = new File(context.getExternalCacheDir(), filename);

Cómo quitar archivos de caché

Para quitar un archivo del directorio de caché externo, usa el método delete() en un objeto File que represente el archivo:

Kotlin

externalCacheFile.delete()

Java

externalCacheFile.delete();

Contenido multimedia

Si la app funciona con archivos multimedia que aportan valor al usuario solo dentro de tu app, es mejor guardarlos en directorios específicos dentro del almacenamiento externo, como se muestra en el siguiente fragmento de código:

Kotlin

fun getAppSpecificAlbumStorageDir(context: Context, albumName: String): File? {
    // Get the pictures directory that's inside the app-specific directory on
    // external storage.
    val file = File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName)
    if (!file?.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created")
    }
    return file
}

Java

@Nullable
File getAppSpecificAlbumStorageDir(Context context, String albumName) {
    // Get the pictures directory that's inside the app-specific directory on
    // external storage.
    File file = new File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName);
    if (file == null || !file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}

Es importante que uses los nombres de directorio proporcionados por constantes de API como DIRECTORY_PICTURES. Esos nombres de directorio garantizan que el sistema trate de forma adecuada a los archivos. Si ninguno de los nombres de subdirectorio predefinidos se adapta a los archivos, puedes pasar null a getExternalFilesDir(). Esa acción mostrará el directorio raíz específico de la app dentro del almacenamiento externo.

Cómo consultar el espacio libre

Muchos usuarios tienen poco espacio de almacenamiento disponible en sus dispositivos, por lo que es necesario prestar atención al espacio que ocupa la app.

Si conoces de antemano la cantidad de datos que almacenas, puedes llamar a getAllocatableBytes() para determinar cuánto espacio puede proporcionar el dispositivo a tu app. El valor que muestra getAllocatableBytes() podría ser mayor que la cantidad actual de espacio libre en el dispositivo. Esto se debe a que el sistema identificó archivos que puede quitar de los directorios de caché de otras apps.

Si hay suficiente espacio para guardar los datos de la app, llama a allocateBytes(). De lo contrario, la app puede solicitar al usuario que quite algunos archivos del dispositivo o quite todos los archivos de caché del dispositivo.

En el siguiente fragmento de código, se muestra un ejemplo de cómo la app puede consultar el espacio libre en el dispositivo:

Kotlin

// App needs 10 MB within internal storage.
const val NUM_BYTES_NEEDED_FOR_MY_APP = 1024 * 1024 * 10L;

val storageManager = applicationContext.getSystemService<StorageManager>()!!
val appSpecificInternalDirUuid: UUID = storageManager.getUuidForPath(filesDir)
val availableBytes: Long =
        storageManager.getAllocatableBytes(appSpecificInternalDirUuid)
if (availableBytes >= NUM_BYTES_NEEDED_FOR_MY_APP) {
    storageManager.allocateBytes(
        appSpecificInternalDirUuid, NUM_BYTES_NEEDED_FOR_MY_APP)
} else {
    val storageIntent = Intent().apply {
        // To request that the user remove all app cache files instead, set
        // "action" to ACTION_CLEAR_APP_CACHE.
        action = ACTION_MANAGE_STORAGE
    }
}

Java

// App needs 10 MB within internal storage.
private static final long NUM_BYTES_NEEDED_FOR_MY_APP = 1024 * 1024 * 10L;

StorageManager storageManager =
        getApplicationContext().getSystemService(StorageManager.class);
UUID appSpecificInternalDirUuid = storageManager.getUuidForPath(getFilesDir());
long availableBytes =
        storageManager.getAllocatableBytes(appSpecificInternalDirUuid);
if (availableBytes >= NUM_BYTES_NEEDED_FOR_MY_APP) {
    storageManager.allocateBytes(
            appSpecificInternalDirUuid, NUM_BYTES_NEEDED_FOR_MY_APP);
} else {
    // To request that the user remove all app cache files instead, set
    // "action" to ACTION_CLEAR_APP_CACHE.
    Intent storageIntent = new Intent();
    storageIntent.setAction(ACTION_MANAGE_STORAGE);
}

Cómo crear una actividad de administración de almacenamiento

Una app puede declarar y crear una actividad personalizada que, cuando se inicie, permita que el usuario administre los datos que la app almacenó en el dispositivo del usuario. Las apps declaran esta actividad personalizada para "administrar el espacio" mediante el atributo android:manageSpaceActivity en el archivo de manifiesto. Las apps de administrador de archivos pueden invocar esta actividad incluso cuando tu app no exporta la actividad. Es decir, cuando tu actividad establece android:exported en false.

Cómo pedirle al usuario que quite algunos archivos del dispositivo

Para solicitar que el usuario elija los archivos del dispositivo que se desea quitar, invoca un intent que incluya la acción ACTION_MANAGE_STORAGE. Este intent muestra un mensaje al usuario. Si lo deseas, el mensaje puede mostrar la cantidad de espacio libre disponible en el dispositivo. Para mostrar esta información útil, usa el resultado del siguiente cálculo:

StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()

Cómo solicitarle al usuario que quite todos los archivos de caché

Como alternativa, puedes solicitar que el usuario borre los archivos de caché de todas las apps del dispositivo. Para ello, invoca un intent que contenga la acción de intent ACTION_CLEAR_APP_CACHE.

Recursos adicionales

Para obtener más información sobre cómo guardar archivos en el almacenamiento del dispositivo, consulta los siguientes recursos.

Videos