Scoped Storage — je model přístupu k souborovému systému zavedený v Android 10 (API 29), který omezuje svévolný přístup aplikací ke sdílenému úložišti zařízení. Podle Google Android Developer Documentation (2024) Scoped Storage nahrazuje starý model oprávnění READ_EXTERNAL_STORAGE a WRITE_EXTERNAL_STORAGE a poskytuje aplikacím přístup pouze k jejich vlastním souborům v izolovaném sandboxu a ke sdíleným kolekcím prostřednictvím MediaStore. Tento model zvyšuje soukromí uživatelských dat a zabraňuje neoprávněnému shromažďování informací aplikacemi.
Hlavní body
Scoped Storage — je architektonická změna v Android 10 (API 29), která zásadně mění způsob přístupu aplikací k souborovému systému zařízení. Místo plného přístupu ke sdílenému úložišti aplikace získává přístup pouze ke svému sandboxu a k určitým kolekcím mediálních souborů prostřednictvím MediaStore.
Před Scoped Storage mohla jakákoli aplikace s oprávněním READ_EXTERNAL_STORAGE číst všechny soubory ve sdíleném úložišti — fotografie, dokumenty, stažené soubory, data jiných aplikací. To vytvářelo rizika pro soukromí: nepoctivé aplikace mohly shromažďovat informace o uživateli bez jeho vědomí.
Podle Google I/O 2019 považuje více než 65% uživatelů Androidu soukromí dat za kritický faktor při výběru aplikace. Scoped Storage je přímou odpovědí na tento požadavek: každá aplikace pracuje v izolovaném prostředí a získává přístup k datům ostatních pouze s výslovným souhlasem uživatele.
Důležité: Scoped Storage v Android 10 byl volitelný (aplikace jej mohla deaktivovat pomocí requestLegacyExternalStorage). Počínaje Android 11 (API 30) se Scoped Storage stal povinným pro všechny aplikace bez ohledu na target SDK.
Google zavedl Scoped Storage k vyřešení tří zásadních problémů starého modelu přístupu k souborům: soukromí, správa oprávnění a čištění zbytkových souborů.
Ve starém modelu mohla aplikace s READ_EXTERNAL_STORAGE skenovat celé sdílené úložiště a shromažďovat metadata o souborech uživatele — zeměpisné souřadnice fotografií, názvy dokumentů, strukturu adresářů. Scoped Storage tuto možnost odstraňuje: i s oprávněním aplikace vidí pouze soubory, které sama vytvořila, a soubory výslovně vybrané uživatelem prostřednictvím SAF.
Před Scoped Storage mohla aplikace po odinstalaci zanechat soubory ve sdíleném úložišti. Postupem času se hromadily odpadkové adresáře. Scoped Storage to řeší: všechny soubory v sandboxu aplikace jsou smazány spolu s aplikací a soubory prostřednictvím MediaStore mají vlastníka a mohou být systémem vyčištěny.
| Aspekt | Starý model (Legacy) | Scoped Storage |
|---|---|---|
| Přístup ke sdílenému úložišti | Plný (s oprávněním) | Pouze vlastní sandbox |
| Mediální soubory | Přímá cesta k souboru | Přes URI MediaStore |
| Cizí soubory | Vždy dostupné | Pouze přes SAF |
| Čištění při odinstalaci | Soubory zůstávají | Sandbox je smazán |
| Kontrola uživatele | Minimální | Výslovný souhlas pro každý soubor |
Scoped Storage je součástí celkové strategie Google pro posílení soukromí v Androidu, která také zahrnuje oprávnění “pouze během používání”, indikátory přístupu ke kameře a mikrofonu a Privacy Dashboard.
Každá aplikace v Androidu získává vlastní soukromý adresář přístupný prostřednictvím Context.getFilesDir() a context.getCacheDir(). Pro přístup k externímu (sdílenému) soukromému adresáři se používá Context.getExternalFilesDir().
V Scoped Storage má aplikace plný přístup ke svému externímu soukromému adresáři bez jakýchkoli oprávnění. Toto je hlavní místo pro ukládání souborů, které by neměly být přístupné jiným aplikacím nebo které aplikace vytváří pro vlastní potřebu.
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()
}
}
Cesta k externímu soukromému adresáři: /storage/emulated/0/Android/data/{packageName}/files/. Počínaje Android 11 není přímá cesta k souboru dostupná — pouze přes API. Toto je další posílení bezpečnosti Scoped Storage.
MediaStore — je hlavní API pro přístup ke sdíleným mediálním souborům (obrázky, video, audio) v Scoped Storage. Aplikace nepracuje s cestami k souborům, ale s content URIs poskytovanými poskytovatelem MediaStore.
MediaStore je rozdělen do tří hlavních kolekcí: Images, Video a Audio. Každá kolekce podporuje CRUD operace prostřednictvím ContentResolver. Pro vložení nového souboru do kolekce Images se používá MediaStore.Images.Media, pro dotazování existujících — query s příslušným 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
)
}
}
Důležité: MediaStore nepodporuje libovolné cesty k souborům — pouze content URIs. Pokus o získání cesty k souboru z URI prostřednictvím MediaStore.Files.getContentUri() neposkytne přímou cestu k souboru na Android 11+. Místo toho použijte ContentResolver.openInputStream() a openOutputStream() pro práci s obsahem.
Storage Access Framework (SAF) — je API pro přístup k libovolným souborům a adresářům mimo sandbox aplikace. SAF poskytuje uživateli rozhraní pro výběr souboru nebo adresáře, po kterém aplikace obdrží content URI s dočasným přístupem.
SAF se používá pro scénáře, které nejsou pokryty MediaStore: práce s libovolnými dokumenty (PDF, ZIP, APK), přístup k adresářům na SD kartě, import a export souborů z jiných aplikací. Uživatel explicitně vybírá soubor prostřednictvím systémového výběru souborů — to zaručuje jeho souhlas s přístupem.
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
// Získat trvalé oprávnění URI
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
contentResolver.takePersistableUriPermission(treeUri, flags)
}
}
SAF poskytuje aplikaci přístup k adresáři vybranému uživatelem až do restartu zařízení (při použití takePersistableUriPermission). Toto je jediný způsob, jak získat přístup k libovolným souborům ve sdíleném úložišti na Android 11+.
Migrace existující Android aplikace na Scoped Storage vyžaduje změny v několika klíčových oblastech. Google doporučuje fázový přístup s testováním na Android 11+.
Nahraďte všechny přímé operace File ve sdíleném úložišti pomocí ContentResolver.openInputStream / openOutputStream. Pro soubory v sandboxu aplikace (getExternalFilesDir) cesty k souborům nadále fungují.
Odstraňte READ_EXTERNAL_STORAGE a WRITE_EXTERNAL_STORAGE, pokud nejsou potřebné pro specifické scénáře (např. správa souborů — k tomu existuje zvláštní oprávnění MANAGE_EXTERNAL_STORAGE).
| Scénář | Starý přístup | Scoped Storage přístup |
|---|---|---|
| Uložení fotografie | File(path).writeBytes() | MediaStore + ContentResolver |
| Čtení PDF | File(path).inputStream() | SAF ACTION_OPEN_DOCUMENT |
| Vlastní soubory | Environment.getExternalStorageDirectory() | context.getExternalFilesDir() |
| Mezipaměť | File(cacheDir).writeBytes() | context.cacheDir (beze změny) |
Po migraci otestujte aplikaci na Android 11+ (API 30) s targetSdk = 30 nebo vyšším v build.gradle. Ujistěte se, že všechny operace se soubory ve sdíleném úložišti fungují přes MediaStore nebo SAF, ne přes přímé cesty k souborům.
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()
}
}
Podle Google Play Console (2024) více než 78% aktivních Android zařízení běží na Android 10+ s Scoped Storage. Migrace je povinným krokem pro publikování aktualizací v Google Play: nové aplikace musí cílit API 31+, aktualizace — API 30+.
Často kladené otázky
Ne, počínaje Android 11 (API 30) je Scoped Storage povinný pro všechny aplikace. Příznak requestLegacyExternalStorage, dostupný v Android 10, nefunguje na API 30+. Jediný způsob, jak získat široký přístup k souborovému systému, je oprávnění MANAGE_EXTERNAL_STORAGE, ale je určeno pouze pro správce souborů a antiviry.
MANAGE_EXTERNAL_STORAGE — zvláštní oprávnění pro aplikace, které potřebují plný přístup k souborovému systému (správci souborů, nástroje pro zálohování). Žádost o oprávnění otevře systémovou obrazovku s varováním pro uživatele. Při publikování v Google Play je nutné vyplnit formulář Declaration o nezbytnosti tohoto oprávnění.
V Scoped Storage na Android 11+ nelze získat přímou cestu k souboru z content URI. Místo toho použijte ContentResolver.openInputStream() pro čtení a openOutputStream() pro zápis. Pokud potřebujete cestu k souboru pro kompatibilitu s knihovnou třetí strany — vytvořte kopii souboru v getCacheDir() a pracujte s kopií.
FileProvider nadále funguje beze změn — používá se k poskytování přístupu k souborům z sandboxu aplikace jiným aplikacím prostřednictvím content URIs. Scoped Storage neovlivňuje FileProvider, protože FileProvider pracuje na úrovni content URIs, nikoli přímých cest k souborům.
Zkontrolujte Build.VERSION.SDK_INT: pokud >= Build.VERSION_CODES.Q (29), zařízení podporuje Scoped Storage. Na Android 10 však může být Scoped Storage deaktivován pomocí requestLegacyExternalStorage. Na Android 11+ zkontrolujte, zda je v manifestu nastaven příznak requestLegacyExternalStorage — pokud ne, Scoped Storage je aktivní.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také