Scoped Storage es un modelo de acceso al sistema de archivos introducido en Android 10 (API 29) que restringe el acceso arbitrario de las aplicaciones al almacenamiento compartido del dispositivo. Según la Documentación para Desarrolladores de Google Android (2024), Scoped Storage reemplaza el antiguo modelo de permisos READ_EXTERNAL_STORAGE y WRITE_EXTERNAL_STORAGE, otorgando a las aplicaciones acceso solo a sus propios archivos en un sandbox aislado y a colecciones compartidas a través de MediaStore. Este modelo mejora la privacidad de los datos del usuario y evita la recopilación no autorizada de información por parte de las aplicaciones.
Puntos clave
Scoped Storage es un cambio arquitectónico en Android 10 (API 29) que transforma fundamentalmente la forma en que las aplicaciones acceden al sistema de archivos del dispositivo. En lugar de acceso completo al almacenamiento compartido, la aplicación obtiene acceso solo a su propio sandbox y a colecciones de medios específicas a través de MediaStore.
Antes de Scoped Storage, cualquier aplicación con permiso READ_EXTERNAL_STORAGE podía leer todos los archivos en el almacenamiento compartido — fotos, documentos, descargas, datos de otras aplicaciones. Esto creaba riesgos de privacidad: aplicaciones malintencionadas podían recopilar información del usuario sin su conocimiento.
Según Google I/O 2019, más del 65% de los usuarios de Android consideran la privacidad de los datos un factor crítico al elegir una aplicación. Scoped Storage es una respuesta directa a esta demanda: cada aplicación opera en un entorno aislado y obtiene acceso a los datos de otras aplicaciones solo con el consentimiento explícito del usuario.
Importante: Scoped Storage en Android 10 era opcional (la aplicación podía desactivarlo mediante requestLegacyExternalStorage). A partir de Android 11 (API 30), Scoped Storage se volvió obligatorio para todas las aplicaciones, independientemente del target SDK.
Google introdujo Scoped Storage para resolver tres problemas fundamentales del antiguo modelo de acceso a archivos: privacidad, gestión de permisos y limpieza de archivos residuales.
En el modelo antiguo, una aplicación con READ_EXTERNAL_STORAGE podía escanear todo el almacenamiento compartido y recopilar metadatos de los archivos del usuario — coordenadas geográficas de fotos, nombres de documentos, estructura de directorios. Scoped Storage elimina esta posibilidad: incluso con permiso, la aplicación solo ve los archivos que ella misma creó y los que el usuario seleccionó explícitamente a través de SAF.
Antes de Scoped Storage, las aplicaciones podían dejar archivos en el almacenamiento compartido después de la desinstalación. Con el tiempo se acumulaban directorios basura. Scoped Storage resuelve esto: todos los archivos en el sandbox de la aplicación se eliminan junto con la aplicación, y los archivos a través de MediaStore tienen un propietario y pueden ser limpiados por el sistema.
| Aspecto | Modelo antiguo (Legacy) | Scoped Storage |
|---|---|---|
| Acceso al almacenamiento compartido | Completo (con permiso) | Solo su propio sandbox |
| Archivos multimedia | Ruta de archivo directa | A través de URI de MediaStore |
| Archivos de otras aplicaciones | Siempre accesibles | Solo a través de SAF |
| Limpieza al desinstalar | Los archivos permanecen | El sandbox se elimina |
| Control del usuario | Mínimo | Consentimiento explícito por archivo |
Scoped Storage es parte de la estrategia general de privacidad de Google en Android, que también incluye permisos "solo mientras se usa", indicadores de acceso a cámara y micrófono, y el Panel de privacidad.
Cada aplicación en Android recibe su propio directorio privado, accesible a través de Context.getFilesDir() y context.getCacheDir(). Para acceder al directorio privado externo (compartido) se usa Context.getExternalFilesDir().
En Scoped Storage, una aplicación tiene acceso completo a su directorio privado externo sin ningún permiso. Este es el lugar principal para almacenar archivos que no deben ser accesibles para otras aplicaciones o que la aplicación crea para su propio uso.
import android.os.Environment
import java.io.File
import java.io.IOException
class FileManager {
fun saveToAppStorage(context: Context, fileName: String, data: ByteArray) {
val appDir = context.getExternalFilesDir(null)
?: return
val file = File(appDir, fileName)
file.writeBytes(data)
}
fun readFromAppStorage(context: Context, fileName: String): ByteArray? {
val appDir = context.getExternalFilesDir(null)
?: return null
val file = File(appDir, fileName)
return file.takeIf { it.exists() }?.readBytes()
}
}
Ruta al directorio privado externo: /storage/emulated/0/Android/data/{packageName}/files/. A partir de Android 11, la ruta de archivo directa no está disponible — solo a través de la API. Esta es otra mejora de seguridad de Scoped Storage.
MediaStore es la API principal para acceder a archivos multimedia compartidos (imágenes, videos, audio) en Scoped Storage. La aplicación no trabaja con rutas de archivo, sino con content URIs proporcionados por el proveedor de MediaStore.
MediaStore se divide en tres colecciones principales: Images, Video y Audio. Cada colección admite operaciones CRUD a través de ContentResolver. Para insertar un nuevo archivo en la colección Images se usa MediaStore.Images.Media; para consultar los existentes — query con la URI correspondiente.
import android.content.ContentValues
import android.provider.MediaStore
import android.os.Environment
import java.io.OutputStream
fun saveImageToGallery(context: Context, bitmap: Bitmap, title: String) {
val values = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, "$title.jpg")
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
put(
MediaStore.Images.Media.RELATIVE_PATH,
"${Environment.DIRECTORY_PICTURES}/MyApp"
)
}
val uri = context.contentResolver
.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
?: return
context.contentResolver.openOutputStream(uri)?.use { output: OutputStream ->
bitmap.compress(
Bitmap.CompressFormat.JPEG,
95,
output
)
}
}
Importante: MediaStore no admite rutas de archivo arbitrarias — solo content URIs. Intentar obtener una ruta de archivo desde una URI a través de MediaStore.Files.getContentUri() no dará una ruta directa al archivo en Android 11+. En su lugar, usa ContentResolver.openInputStream() y openOutputStream() para trabajar con el contenido.
Storage Access Framework (SAF) es una API para acceder a archivos y directorios arbitrarios fuera del sandbox de la aplicación. SAF proporciona al usuario una interfaz de selección de archivos o directorios, tras lo cual la aplicación recibe un content URI con acceso temporal.
SAF se utiliza para escenarios que no cubre MediaStore: trabajar con documentos arbitrarios (PDF, ZIP, APK), acceder a directorios en la tarjeta SD, importar y exportar archivos desde otras aplicaciones. El usuario selecciona explícitamente un archivo a través del selector de archivos del sistema — esto garantiza su consentimiento para el acceso.
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.provider.DocumentsContract
const val REQUEST_CODE_PICK_DIR = 1001
fun pickDirectory(activity: Activity) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
activity.startActivityForResult(intent, REQUEST_CODE_PICK_DIR)
}
fun handlePickResult(requestCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_PICK_DIR && data != null) {
val treeUri: Uri = data.data ?: return
// Take persistent URI permission
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
contentResolver.takePersistableUriPermission(treeUri, flags)
}
}
SAF otorga a la aplicación acceso al directorio seleccionado por el usuario hasta que se reinicie el dispositivo (al usar takePersistableUriPermission). Esta es la única forma de obtener acceso a archivos arbitrarios en el almacenamiento compartido en Android 11+.
Migrar una aplicación Android existente a Scoped Storage requiere cambios en varias áreas clave. Google recomienda un enfoque por fases con pruebas en Android 11+.
Reemplaza todas las operaciones directas con File en el almacenamiento compartido por ContentResolver.openInputStream / openOutputStream. Para archivos en el sandbox de la aplicación (getExternalFilesDir), las rutas de archivo siguen funcionando.
Elimina READ_EXTERNAL_STORAGE y WRITE_EXTERNAL_STORAGE si no son necesarios para escenarios específicos (por ejemplo, gestión de archivos — para esto existe el permiso especial MANAGE_EXTERNAL_STORAGE).
| Escenario | Enfoque antiguo | Enfoque Scoped Storage |
|---|---|---|
| Guardar una foto | File(path).writeBytes() | MediaStore + ContentResolver |
| Leer PDF | File(path).inputStream() | SAF ACTION_OPEN_DOCUMENT |
| Archivos propios | Environment.getExternalStorageDirectory() | context.getExternalFilesDir() |
| Caché | File(cacheDir).writeBytes() | context.cacheDir (sin cambios) |
Después de la migración, prueba la aplicación en Android 11+ (API 30), configurando targetSdk = 30 o superior en build.gradle. Asegúrate de que todas las operaciones con archivos en el almacenamiento compartido funcionen a través de MediaStore o SAF, no mediante rutas de archivo directas.
import android.os.Build
fun isScopedStorage(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
}
fun compatReadFile(context: Context, uri: Uri): ByteArray? {
return if (isScopedStorage()) {
context.contentResolver
.openInputStream(uri)?.readBytes()
} else {
File(uri.path ?: return null)
.takeIf { it.exists() }
?.readBytes()
}
}
Según Google Play Console (2024), más del 78% de los dispositivos Android activos funcionan con Android 10+ y Scoped Storage. La migración es un paso obligatorio para publicar actualizaciones en Google Play: las nuevas aplicaciones deben tener target API 31+, las actualizaciones — API 30+.
Preguntas frecuentes
No, a partir de Android 11 (API 30) Scoped Storage es obligatorio para todas las aplicaciones. El flag requestLegacyExternalStorage, disponible en Android 10, no funciona en API 30+. La única forma de obtener acceso amplio al sistema de archivos es el permiso MANAGE_EXTERNAL_STORAGE, pero está destinado solo para gestores de archivos y antivirus.
MANAGE_EXTERNAL_STORAGE es un permiso especial para aplicaciones que necesitan acceso completo al sistema de archivos (gestores de archivos, herramientas de backup). La solicitud de permiso abre una pantalla del sistema con una advertencia para el usuario. Al publicar en Google Play, es necesario completar un formulario de Declaración sobre la necesidad de este permiso.
En Scoped Storage en Android 11+ no se puede obtener una ruta de archivo directa desde un content URI. En su lugar, usa ContentResolver.openInputStream() para leer y openOutputStream() para escribir. Si necesitas una ruta de archivo para compatibilidad con una biblioteca de terceros, crea una copia del archivo en getCacheDir() y trabaja con la copia.
FileProvider sigue funcionando sin cambios — se usa para proporcionar acceso a archivos del sandbox de la aplicación a otras aplicaciones a través de content URIs. Scoped Storage no afecta a FileProvider, ya que FileProvider funciona a nivel de content URIs, no de rutas de archivo directas.
Comprueba Build.VERSION.SDK_INT: si es >= Build.VERSION_CODES.Q (29), entonces el dispositivo es compatible con Scoped Storage. Sin embargo, en Android 10, Scoped Storage puede estar desactivado mediante requestLegacyExternalStorage. En Android 11+, verifica si el flag de manifiesto requestLegacyExternalStorage está establecido — si no, Scoped Storage está activo.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también