Scoped Storage — is een toegangsmodel tot het bestandssysteem dat is geïntroduceerd in Android 10 (API 29) en de willekeurige toegang van apps tot de gedeelde opslag van het apparaat beperkt. Volgens Google Android Developer Documentation (2024) vervangt Scoped Storage het oude model van READ_EXTERNAL_STORAGE en WRITE_EXTERNAL_STORAGE machtigingen, waarbij apps alleen toegang krijgen tot hun eigen bestanden in een geïsoleerde sandbox en tot gedeelde collecties via MediaStore. Dit model verhoogt de privacy van gebruikersgegevens en voorkomt ongeautoriseerde verzameling van informatie door apps.
Belangrijkste punten
Scoped Storage — is een architectonische verandering in Android 10 (API 29) die de manier waarop apps toegang krijgen tot het bestandssysteem van het apparaat fundamenteel verandert. In plaats van volledige toegang tot de gedeelde opslag, krijgt de app alleen toegang tot zijn eigen sandbox en tot bepaalde collecties mediabestanden via MediaStore.
Vóór Scoped Storage kon elke app met de machtiging READ_EXTERNAL_STORAGE alle bestanden in de gedeelde opslag lezen — foto’s, documenten, downloads, gegevens van andere apps. Dit creëerde privacyrisico’s: oneerlijke apps konden informatie over de gebruiker verzamelen zonder diens medeweten.
Volgens Google I/O 2019 beschouwt meer dan 65% van de Android-gebruikers gegevensprivacy als een kritieke factor bij het kiezen van een app. Scoped Storage is het directe antwoord op deze vraag: elke app werkt in een geïsoleerde omgeving en krijgt alleen toegang tot gegevens van anderen met expliciete toestemming van de gebruiker.
Belangrijk: Scoped Storage was in Android 10 optioneel (de app kon het uitschakelen via requestLegacyExternalStorage). Vanaf Android 11 (API 30) is Scoped Storage verplicht geworden voor alle apps, ongeacht de target SDK.
Google heeft Scoped Storage geïntroduceerd om drie fundamentele problemen van het oude bestandstoegangsmodel op te lossen: privacy, machtigingsbeheer en het opruimen van achtergebleven bestanden.
In het oude model kon een app met READ_EXTERNAL_STORAGE de hele gedeelde opslag scannen en metadata over de bestanden van de gebruiker verzamelen — geografische coördinaten van foto’s, documentnamen, mapstructuur. Scoped Storage elimineert deze mogelijkheid: zelfs met de machtiging ziet de app alleen bestanden die hij zelf heeft gemaakt en bestanden die de gebruiker expliciet heeft geselecteerd via SAF.
Vóór Scoped Storage kon de app bestanden achterlaten in de gedeelde opslag na verwijdering. Na verloop van tijd stapelden rommel-mappen zich op. Scoped Storage lost dit op: alle bestanden in de sandbox van de app worden samen met de app verwijderd, en bestanden via MediaStore hebben een eigenaar en kunnen door het systeem worden opgeruimd.
| Aspect | Oud model (Legacy) | Scoped Storage |
|---|---|---|
| Toegang tot gedeelde opslag | Volledig (met machtiging) | Alleen eigen sandbox |
| Mediabestanden | Direct bestandspad | Via MediaStore URI |
| Andermans bestanden | Altijd beschikbaar | Alleen via SAF |
| Opruimen bij verwijdering | Bestanden blijven | Sandbox wordt verwijderd |
| Controle gebruiker | Minimaal | Expliciete toestemming per bestand |
Scoped Storage maakt deel uit van de algemene strategie van Google om de privacy in Android te versterken, die ook machtigingen “alleen tijdens gebruik”, indicatoren voor toegang tot camera en microfoon en Privacy Dashboard omvat.
Elke app in Android krijgt een eigen privémap, toegankelijk via Context.getFilesDir() en context.getCacheDir(). Voor toegang tot de externe (gedeelde) privémap wordt Context.getExternalFilesDir() gebruikt.
In Scoped Storage heeft de app volledige toegang tot zijn eigen externe privémap zonder enige machtigingen. Dit is de primaire locatie voor het opslaan van bestanden die niet toegankelijk mogen zijn voor andere apps of die de app voor eigen gebruik aanmaakt.
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()
}
}
Pad naar de externe privémap: /storage/emulated/0/Android/data/{packageName}/files/. Vanaf Android 11 is het directe bestandspad niet beschikbaar — alleen via API. Dit is een verdere versterking van de beveiliging van Scoped Storage.
MediaStore — is de primaire API voor toegang tot gedeelde mediabestanden (afbeeldingen, video, audio) in Scoped Storage. De app werkt niet met bestandspaden, maar met content URIs die door de MediaStore-provider worden geleverd.
MediaStore is verdeeld in drie hoofdcollecties: Images, Video en Audio. Elke collectie ondersteunt CRUD-bewerkingen via ContentResolver. Voor het invoegen van een nieuw bestand in de Images-collectie wordt MediaStore.Images.Media gebruikt, voor het opvragen van bestaande — query met de betreffende URI.
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
)
}
}
Belangrijk: MediaStore ondersteunt geen willekeurige bestandspaden — alleen content URIs. Een poging om het bestandspad uit een URI te verkrijgen via MediaStore.Files.getContentUri() geeft geen direct pad naar het bestand op Android 11+. Gebruik in plaats daarvan ContentResolver.openInputStream() en openOutputStream() om met de inhoud te werken.
Storage Access Framework (SAF) — is de API voor toegang tot willekeurige bestanden en mappen buiten de sandbox van de app. SAF biedt de gebruiker een interface om een bestand of map te selecteren, waarna de app een content URI met tijdelijke toegang ontvangt.
SAF wordt gebruikt voor scenario’s die niet door MediaStore worden gedekt: werken met willekeurige documenten (PDF, ZIP, APK), toegang tot mappen op de SD-kaart, importeren en exporteren van bestanden uit andere apps. De gebruiker selecteert het bestand expliciet via de systeembrede bestandsselectie — dit garandeert zijn toestemming voor toegang.
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
// Verkrijg permanente URI-toestemming
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
contentResolver.takePersistableUriPermission(treeUri, flags)
}
}
SAF geeft de app toegang tot de door de gebruiker geselecteerde map tot het apparaat opnieuw wordt opgestart (bij gebruik van takePersistableUriPermission). Dit is de enige manier om toegang te krijgen tot willekeurige bestanden in de gedeelde opslag op Android 11+.
Migratie van een bestaande Android-app naar Scoped Storage vereist wijzigingen in verschillende belangrijke gebieden. Google beveelt een gefaseerde aanpak aan met testen op Android 11+.
Vervang alle directe File-bewerkingen in de gedeelde opslag door ContentResolver.openInputStream / openOutputStream. Voor bestanden in de sandbox van de app (getExternalFilesDir) blijven bestandspaden werken.
Verwijder READ_EXTERNAL_STORAGE en WRITE_EXTERNAL_STORAGE als ze niet nodig zijn voor specifieke scenario’s (bijvoorbeeld bestandsbeheer — daarvoor is er de speciale machtiging MANAGE_EXTERNAL_STORAGE).
| Scenario | Oude aanpak | Scoped Storage aanpak |
|---|---|---|
| Opslaan foto | File(path).writeBytes() | MediaStore + ContentResolver |
| Lezen PDF | File(path).inputStream() | SAF ACTION_OPEN_DOCUMENT |
| Eigen bestanden | Environment.getExternalStorageDirectory() | context.getExternalFilesDir() |
| Cache | File(cacheDir).writeBytes() | context.cacheDir (ongewijzigd) |
Test de app na migratie op Android 11+ (API 30) met targetSdk = 30 of hoger in build.gradle. Zorg ervoor dat alle bestandsbewerkingen in de gedeelde opslag via MediaStore of SAF werken, niet via directe bestandspaden.
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()
}
}
Volgens Google Play Console (2024) draait meer dan 78% van de actieve Android-apparaten op Android 10+ met Scoped Storage. Migratie is een verplichte stap voor het publiceren van updates in Google Play: nieuwe apps moeten API 31+ targeten, updates — API 30+.
Veelgestelde vragen
Nee, vanaf Android 11 (API 30) is Scoped Storage verplicht voor alle apps. De vlag requestLegacyExternalStorage, beschikbaar in Android 10, werkt niet op API 30+. De enige manier om brede toegang tot het bestandssysteem te krijgen is de machtiging MANAGE_EXTERNAL_STORAGE, maar deze is alleen bedoeld voor bestandsbeheerders en antivirusprogramma’s.
MANAGE_EXTERNAL_STORAGE — een speciale machtiging voor apps die volledige toegang tot het bestandssysteem nodig hebben (bestandsbeheerders, back-up tools). Het aanvragen van de machtiging opent een systeemscherm met een waarschuwing voor de gebruiker. Bij publicatie in Google Play moet het Declaration-formulier over de noodzaak van deze machtiging worden ingevuld.
In Scoped Storage op Android 11+ kan geen direct bestandspad uit een content URI worden verkregen. Gebruik in plaats daarvan ContentResolver.openInputStream() voor lezen en openOutputStream() voor schrijven. Als je een bestandspad nodig hebt voor compatibiliteit met een externe bibliotheek — maak een kopie van het bestand in getCacheDir() en werk met de kopie.
FileProvider blijft ongewijzigd werken — het wordt gebruikt om toegang te bieden tot bestanden uit de sandbox van de app aan andere apps via content URIs. Scoped Storage heeft geen invloed op FileProvider, omdat FileProvider werkt op het niveau van content URIs, niet van directe bestandspaden.
Controleer Build.VERSION.SDK_INT: als >= Build.VERSION_CODES.Q (29) is, ondersteunt het apparaat Scoped Storage. Op Android 10 kan Scoped Storage echter worden uitgeschakeld via requestLegacyExternalStorage. Op Android 11+ controleer of de vlag requestLegacyExternalStorage in het manifest is ingesteld — zo niet, dan is Scoped Storage actief.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook