Scoped Storage est un modèle d'accès au système de fichiers introduit dans Android 10 (API 29) qui restreint l'accès arbitraire des applications au stockage partagé de l'appareil. Selon la Documentation Développeur Android de Google (2024), Scoped Storage remplace l'ancien modèle d'autorisations READ_EXTERNAL_STORAGE et WRITE_EXTERNAL_STORAGE, accordant aux applications uniquement l'accès à leurs propres fichiers dans un sandbox isolé et aux collections partagées via MediaStore. Ce modèle renforce la confidentialité des données utilisateur et empêche la collecte non autorisée d'informations par les applications.
Points clés
Scoped Storage est un changement architectural dans Android 10 (API 29) qui transforme fondamentalement la façon dont les applications accèdent au système de fichiers de l'appareil. Au lieu d'un accès complet au stockage partagé, l'application obtient uniquement l'accès à son propre sandbox et à des collections multimédias spécifiques via MediaStore.
Avant Scoped Storage, toute application disposant de l'autorisation READ_EXTERNAL_STORAGE pouvait lire tous les fichiers du stockage partagé — photos, documents, téléchargements, données d'autres applications. Cela créait des risques de confidentialité : des applications non fiables pouvaient collecter des informations sur l'utilisateur à son insu.
Selon Google I/O 2019, plus de 65 % des utilisateurs d'Android considèrent la confidentialité des données comme un facteur critique lors du choix d'une application. Scoped Storage est une réponse directe à cette demande : chaque application fonctionne dans un environnement isolé et n'accède aux données d'autres applications qu'avec le consentement explicite de l'utilisateur.
Important : Scoped Storage dans Android 10 était optionnel (l'application pouvait le désactiver via requestLegacyExternalStorage). À partir d'Android 11 (API 30), Scoped Storage est devenu obligatoire pour toutes les applications, quel que soit le target SDK.
Google a introduit Scoped Storage pour résoudre trois problèmes fondamentaux de l'ancien modèle d'accès aux fichiers : la confidentialité, la gestion des autorisations et le nettoyage des fichiers résiduels.
Dans l'ancien modèle, une application avec READ_EXTERNAL_STORAGE pouvait scanner tout le stockage partagé et collecter les métadonnées des fichiers de l'utilisateur — coordonnées géographiques des photos, noms de documents, structure des répertoires. Scoped Storage élimine cette possibilité : même avec l'autorisation, l'application ne voit que les fichiers qu'elle a créés elle-même et les fichiers sélectionnés explicitement par l'utilisateur via SAF.
Avant Scoped Storage, les applications pouvaient laisser des fichiers dans le stockage partagé après la désinstallation. Avec le temps, des répertoires indésirables s'accumulaient. Scoped Storage résout ce problème : tous les fichiers dans le sandbox de l'application sont supprimés avec l'application, et les fichiers via MediaStore ont un propriétaire et peuvent être nettoyés par le système.
| Aspect | Ancien modèle (Legacy) | Scoped Storage |
|---|---|---|
| Accès au stockage partagé | Complet (avec autorisation) | Seulement son sandbox |
| Fichiers multimédia | Chemin de fichier direct | Via URI MediaStore |
| Fichiers des autres applications | Toujours accessibles | Uniquement via SAF |
| Nettoyage à la désinstallation | Les fichiers restent | Le sandbox est supprimé |
| Contrôle de l'utilisateur | Minimal | Consentement explicite par fichier |
Scoped Storage fait partie de la stratégie globale de confidentialité de Google dans Android, qui inclut également les autorisations « uniquement pendant l'utilisation », les indicateurs d'accès à la caméra et au microphone, et le Tableau de bord de la confidentialité.
Chaque application Android reçoit son propre répertoire privé, accessible via Context.getFilesDir() et context.getCacheDir(). Pour accéder au répertoire privé externe (partagé), on utilise Context.getExternalFilesDir().
Dans Scoped Storage, une application a un accès complet à son répertoire privé externe sans aucune autorisation. C'est l'emplacement principal pour stocker les fichiers qui ne doivent pas être accessibles aux autres applications ou que l'application crée pour son propre usage.
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()
}
}
Chemin vers le répertoire privé externe : /storage/emulated/0/Android/data/{packageName}/files/. À partir d'Android 11, le chemin de fichier direct n'est pas disponible — seulement via l'API. C'est une autre amélioration de sécurité de Scoped Storage.
MediaStore est l'API principale pour accéder aux fichiers multimédias partagés (images, vidéos, audio) dans Scoped Storage. L'application ne travaille pas avec des chemins de fichier, mais avec des content URIs fournis par le fournisseur MediaStore.
MediaStore est divisé en trois collections principales : Images, Video et Audio. Chaque collection prend en charge les opérations CRUD via ContentResolver. Pour insérer un nouveau fichier dans la collection Images, on utilise MediaStore.Images.Media ; pour interroger les fichiers existants — query avec l'URI correspondante.
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
)
}
}
Important : MediaStore ne prend pas en charge les chemins de fichier arbitraires — seulement les content URIs. Essayer d'obtenir un chemin de fichier à partir d'une URI via MediaStore.Files.getContentUri() ne donnera pas un chemin direct vers le fichier sur Android 11+. utilisez plutôt ContentResolver.openInputStream() et openOutputStream() pour travailler avec le contenu.
Storage Access Framework (SAF) est une API pour accéder aux fichiers et répertoires arbitraires en dehors du sandbox de l'application. SAF fournit à l'utilisateur une interface de sélection de fichier ou de répertoire, après quoi l'application reçoit une content URI avec un accès temporaire.
SAF est utilisé pour les scénarios non couverts par MediaStore : travailler avec des documents arbitraires (PDF, ZIP, APK), accéder aux répertoires sur la carte SD, importer et exporter des fichiers depuis d'autres applications. L'utilisateur sélectionne explicitement un fichier via le sélecteur de fichiers système — cela garantit son consentement à l'accès.
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 accorde à l'application l'accès au répertoire sélectionné par l'utilisateur jusqu'au redémarrage de l'appareil (en utilisant takePersistableUriPermission). C'est le seul moyen d'obtenir un accès à des fichiers arbitraires dans le stockage partagé sur Android 11+.
La migration d'une application Android existante vers Scoped Storage nécessite des changements dans plusieurs domaines clés. Google recommande une approche progressive avec des tests sur Android 11+.
Remplacez toutes les opérations directes avec File dans le stockage partagé par ContentResolver.openInputStream / openOutputStream. Pour les fichiers dans le sandbox de l'application (getExternalFilesDir), les chemins de fichier continuent de fonctionner.
Supprimez READ_EXTERNAL_STORAGE et WRITE_EXTERNAL_STORAGE s'ils ne sont pas nécessaires pour des scénarios spécifiques (par exemple, la gestion de fichiers — pour cela il existe l'autorisation spéciale MANAGE_EXTERNAL_STORAGE).
| Scénario | Ancienne approche | Approche Scoped Storage |
|---|---|---|
| Enregistrer une photo | File(path).writeBytes() | MediaStore + ContentResolver |
| Lire un PDF | File(path).inputStream() | SAF ACTION_OPEN_DOCUMENT |
| Fichiers personnels | Environment.getExternalStorageDirectory() | context.getExternalFilesDir() |
| Cache | File(cacheDir).writeBytes() | context.cacheDir (inchangé) |
Après la migration, testez l'application sur Android 11+ (API 30), en définissant targetSdk = 30 ou supérieur dans build.gradle. Assurez-vous que toutes les opérations sur les fichiers dans le stockage partagé fonctionnent via MediaStore ou SAF, et non via des chemins de fichier directs.
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()
}
}
Selon la Google Play Console (2024), plus de 78 % des appareils Android actifs fonctionnent sous Android 10+ avec Scoped Storage. La migration est une étape obligatoire pour publier des mises à jour sur Google Play : les nouvelles applications doivent cibler API 31+, les mises à jour — API 30+.
Questions fréquentes
Non, à partir d'Android 11 (API 30), Scoped Storage est obligatoire pour toutes les applications. Le flag requestLegacyExternalStorage, disponible dans Android 10, ne fonctionne pas sur API 30+. La seule façon d'obtenir un accès large au système de fichiers est l'autorisation MANAGE_EXTERNAL_STORAGE, mais elle est destinée uniquement aux gestionnaires de fichiers et aux antivirus.
MANAGE_EXTERNAL_STORAGE est une autorisation spéciale pour les applications qui ont besoin d'un accès complet au système de fichiers (gestionnaires de fichiers, outils de sauvegarde). La demande d'autorisation ouvre un écran système avec un avertissement pour l'utilisateur. Lors de la publication sur Google Play, il est nécessaire de remplir un formulaire de Déclaration sur la nécessité de cette autorisation.
Dans Scoped Storage sur Android 11+, on ne peut pas obtenir un chemin de fichier direct à partir d'une content URI. Utilisez plutôt ContentResolver.openInputStream() pour la lecture et openOutputStream() pour l'écriture. Si vous avez besoin d'un chemin de fichier pour la compatibilité avec une bibliothèque tierce, créez une copie du fichier dans getCacheDir() et travaillez avec la copie.
FileProvider continue de fonctionner sans changement — il est utilisé pour fournir un accès aux fichiers du sandbox de l'application à d'autres applications via des content URIs. Scoped Storage n'affecte pas FileProvider, car FileProvider fonctionne au niveau des content URIs, et non des chemins de fichier directs.
Vérifiez Build.VERSION.SDK_INT : s'il est >= Build.VERSION_CODES.Q (29), alors l'appareil prend en charge Scoped Storage. Cependant, sur Android 10, Scoped Storage peut être désactivé via requestLegacyExternalStorage. Sur Android 11+, vérifiez si le flag de manifeste requestLegacyExternalStorage est défini — sinon, Scoped Storage est actif.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi