Scoped Storage — egy fájlrendszer-hozzáférési modell, amelyet az Android 10 (API 29) vezetett be, és korlátozza az alkalmazások önkényes hozzáférését az eszköz megosztott tárhelyéhez. A Google Android Developer Documentation (2024) szerint a Scoped Storage felváltja a READ_EXTERNAL_STORAGE és WRITE_EXTERNAL_STORAGE engedélyek régi modelljét, és az alkalmazásoknak csak a saját fájljaikhoz biztosít hozzáférést egy elkülönített homokozóban, valamint a megosztott gyűjteményekhez a MediaStore-on keresztül. Ez a modell növeli a felhasználói adatok védelmét és megakadályozza az információk jogosulatlan gyűjtését az alkalmazások által.
Főbb pontok
Scoped Storage — egy architekturális változás az Android 10-ben (API 29), amely alapvetően megváltoztatja az alkalmazások hozzáférését az eszköz fájlrendszeréhez. A megosztott tárhelyhez való teljes hozzáférés helyett az alkalmazás csak a saját homokozójához és bizonyos médiafájl-gyűjteményekhez fér hozzá a MediaStore-on keresztül.
A Scoped Storage előtt bármely READ_EXTERNAL_STORAGE engedéllyel rendelkező alkalmazás olvashatta az összes fájlt a megosztott tárhelyen — fényképeket, dokumentumokat, letöltéseket, más alkalmazások adatait. Ez adatvédelmi kockázatokat teremtett: a tisztességtelen alkalmazások a felhasználó tudta nélkül gyűjthettek információkat róla.
A Google I/O 2019 szerint az Android-felhasználók több mint 65%-a tartja az adatvédelmet kritikus tényezőnek az alkalmazásválasztás során. A Scoped Storage közvetlen válasz erre az igényre: minden alkalmazás elkülönített környezetben működik, és csak a felhasználó kifejezett hozzájárulásával fér hozzá mások adataihoz.
Fontos: A Scoped Storage Android 10-ben opcionális volt (az alkalmazás kikapcsolhatta a requestLegacyExternalStorage segítségével). Android 11-től (API 30) kezdve a Scoped Storage minden alkalmazás számára kötelezővé vált, függetlenül a target SDK-tól.
A Google azért vezette be a Scoped Storage-t, hogy megoldja a régi fájlhozzáférési modell három alapvető problémáját: az adatvédelmet, az engedélykezelést és a maradék fájlok tisztítását.
A régi modellben egy READ_EXTERNAL_STORAGE engedéllyel rendelkező alkalmazás képes volt beolvasni a teljes megosztott tárhelyet és metaadatokat gyűjteni a felhasználó fájljairól — a fényképek földrajzi koordinátáit, a dokumentumok nevét, a könyvtárszerkezetet. A Scoped Storage megszünteti ezt a lehetőséget: még az engedéllyel rendelkező alkalmazás is csak a saját maga által létrehozott fájlokat és a felhasználó által a SAF-on keresztül kifejezetten kiválasztott fájlokat látja.
A Scoped Storage előtt az alkalmazás fájlokat hagyhatott a megosztott tárhelyen az eltávolítás után. Idővel szemét könyvtárak halmozódtak fel. A Scoped Storage megoldja ezt: az alkalmazás homokozójában lévő összes fájl törlődik az alkalmazással együtt, a MediaStore-on keresztüli fájlok pedig rendelkeznek tulajdonossal, és a rendszer képes azokat kitakarítani.
| Szempont | Régi modell (Legacy) | Scoped Storage |
|---|---|---|
| Hozzáférés a megosztott tárhelyhez | Teljes (engedéllyel) | Csak saját homokozó |
| Médiafájlok | Közvetlen fájlútvonal | MediaStore URI-n keresztül |
| Mások fájljai | Mindig elérhető | Csak SAF-on keresztül |
| Tisztítás eltávolításkor | A fájlok megmaradnak | A homokozó törlődik |
| Felhasználói kontroll | Minimális | Kifejezett hozzájárulás fájlonként |
A Scoped Storage a Google általános adatvédelem erősítési stratégiájának része Androidon, amely magában foglalja a „csak használat közben” engedélyeket, a kamera- és mikrofon-hozzáférés jelzőit, valamint a Privacy Dashboard-ot.
Minden Android-alkalmazás saját privát könyvtárat kap, amely a Context.getFilesDir() és a context.getCacheDir() segítségével érhető el. A külső (megosztott) privát könyvtárhoz való hozzáféréshez a Context.getExternalFilesDir() használatos.
A Scoped Storage-ben az alkalmazás teljes hozzáféréssel rendelkezik a saját külső privát könyvtárához, minden engedély nélkül. Ez a fő hely az olyan fájlok tárolására, amelyeknek nem szabad elérhetőnek lenniük más alkalmazások számára, vagy amelyeket az alkalmazás saját használatra hoz létre.
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()
}
}
A külső privát könyvtár elérési útja: /storage/emulated/0/Android/data/{packageName}/files/. Android 11-től kezdve a közvetlen fájlútvonal nem érhető el — csak API-n keresztül. Ez a Scoped Storage biztonságának további erősítése.
MediaStore — a megosztott médiafájlokhoz (képek, videók, hangok) való hozzáférés elsődleges API-ja a Scoped Storage-ben. Az alkalmazás nem fájlútvonalakkal, hanem a MediaStore szolgáltató által biztosított content URI-kkal dolgozik.
A MediaStore három fő gyűjteményre oszlik: Images, Video és Audio. Minden gyűjtemény támogatja a CRUD-műveleteket a ContentResolveren keresztül. Új fájl beszúrásához az Images gyűjteménybe a MediaStore.Images.Media, a meglévők lekérdezéséhez a megfelelő URI-val történő query használatos.
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
)
}
}
Fontos: A MediaStore nem támogat tetszőleges fájlútvonalakat — csak content URI-kat. Az URI-ból fájlútvonal megszerzésére tett kísérlet a MediaStore.Files.getContentUri() segítségével nem ad közvetlen útvonalat a fájlhoz Android 11+-on. Ehelyett használja a ContentResolver.openInputStream() és openOutputStream() függvényeket a tartalommal való munkához.
Storage Access Framework (SAF) — API a tetszőleges fájlokhoz és könyvtárakhoz való hozzáféréshez az alkalmazás homokozóján kívül. A SAF egy felületet biztosít a felhasználónak a fájl vagy könyvtár kiválasztásához, majd az alkalmazás egy content URI-t kap ideiglenes hozzáféréssel.
A SAF olyan forgatókönyvekhez használatos, amelyeket a MediaStore nem fed le: tetszőleges dokumentumokkal (PDF, ZIP, APK) való munka, hozzáférés az SD-kártyán lévő könyvtárakhoz, fájlok importálása és exportálása más alkalmazásokból. A felhasználó kifejezetten kiválasztja a fájlt a rendszer fájlválasztóján keresztül — ez garantálja a hozzájárulását a hozzáféréshez.
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
// Tartós URI-engedély megszerzése
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
contentResolver.takePersistableUriPermission(treeUri, flags)
}
}
A SAF hozzáférést biztosít az alkalmazásnak a felhasználó által kiválasztott könyvtárhoz az eszköz újraindításáig (a takePersistableUriPermission használatakor). Ez az egyetlen módja annak, hogy hozzáférést kapjunk tetszőleges fájlokhoz a megosztott tárhelyen Android 11+-on.
A meglévő Android-alkalmazás Scoped Storage-re történő migrálása több kulcsfontosságú területen igényel változtatásokat. A Google szakaszos megközelítést javasol Android 11+-on történő teszteléssel.
Cserélje ki az összes közvetlen File műveletet a megosztott tárhelyen ContentResolver.openInputStream / openOutputStream függvényekre. Az alkalmazás homokozójában lévő fájlok (getExternalFilesDir) esetében a fájlútvonalak továbbra is működnek.
Távolítsa el a READ_EXTERNAL_STORAGE és WRITE_EXTERNAL_STORAGE engedélyeket, ha nem szükségesek specifikus forgatókönyvekhez (pl. fájlkezelés — ehhez külön MANAGE_EXTERNAL_STORAGE engedély létezik).
| Forgatókönyv | Régi megközelítés | Scoped Storage megközelítés |
|---|---|---|
| Fénykép mentése | File(path).writeBytes() | MediaStore + ContentResolver |
| PDF olvasása | File(path).inputStream() | SAF ACTION_OPEN_DOCUMENT |
| Saját fájlok | Environment.getExternalStorageDirectory() | context.getExternalFilesDir() |
| Gyorsítótár | File(cacheDir).writeBytes() | context.cacheDir (változatlan) |
A migrálás után tesztelje az alkalmazást Android 11+ (API 30) rendszeren, a build.gradle fájlban targetSdk = 30 vagy magasabb beállítással. Győződjön meg arról, hogy minden fájlművelet a megosztott tárhelyen MediaStore-on vagy SAF-on keresztül működik, nem közvetlen fájlútvonalakon keresztül.
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()
}
}
A Google Play Console (2024) szerint az aktív Android-eszközök több mint 78%-a Android 10+-on fut Scoped Storage-szel. A migrálás kötelező lépés a frissítések Google Play-ben történő közzétételéhez: az új alkalmazásoknak API 31+, a frissítéseknek API 30+ célt kell megadniuk.
Gyakran Ismételt Kérdések
Nem, Android 11-től (API 30) kezdve a Scoped Storage minden alkalmazás számára kötelező. A requestLegacyExternalStorage jelző, amely Android 10-ben volt elérhető, nem működik API 30+-on. Az egyetlen mód a fájlrendszerhez való széles körű hozzáférésre a MANAGE_EXTERNAL_STORAGE engedély, de ez csak fájlkezelők és vírusirtók számára készült.
MANAGE_EXTERNAL_STORAGE — egy speciális engedély azoknak az alkalmazásoknak, amelyek teljes hozzáférést igényelnek a fájlrendszerhez (fájlkezelők, biztonsági mentés eszközök). Az engedély kérése egy rendszerképernyőt nyit meg figyelmeztetéssel a felhasználó számára. Google Play-ben történő közzétételkor ki kell tölteni a Declaration űrlapot az engedély szükségességéről.
Scoped Storage-ben Android 11+-on nem lehet közvetlen fájlútvonalat szerezni content URI-ból. Ehelyett használja a ContentResolver.openInputStream() függvényt olvasáshoz és az openOutputStream() függvényt íráshoz. Ha harmadik fél könyvtárával való kompatibilitás miatt fájlútvonalra van szüksége — hozzon létre egy másolatot a fájlról a getCacheDir() könyvtárban, és dolgozzon a másolattal.
A FileProvider változatlanul működik tovább — az alkalmazás homokozójában lévő fájlokhoz való hozzáférés biztosítására szolgál más alkalmazások számára content URI-kon keresztül. A Scoped Storage nem befolyásolja a FileProvider-t, mivel a FileProvider a content URI-k szintjén működik, nem a közvetlen fájlútvonalak szintjén.
Ellenőrizze a Build.VERSION.SDK_INT értékét: ha >= Build.VERSION_CODES.Q (29), az eszköz támogatja a Scoped Storage-t. Android 10-ben azonban a Scoped Storage kikapcsolható a requestLegacyExternalStorage segítségével. Android 11+-on ellenőrizze, hogy a requestLegacyExternalStorage jelző be van-e állítva a manifest-ben — ha nem, a Scoped Storage aktív.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is