Scoped Storage, Android 10'da (API 29) tanıtılan ve uygulamaların cihazın paylaşılan depolama alanına keyfi erişimini kısıtlayan bir dosya sistemi erişim modelidir. Google Android Developer Documentation (2024)'e göre Scoped Storage, READ_EXTERNAL_STORAGE ve WRITE_EXTERNAL_STORAGE'in eski izin modelinin yerini alarak uygulamalara yalnızca kendi dosyalarına izole edilmiş bir kum havuzunda ve MediaStore aracılığıyla paylaşılan koleksiyonlara erişim sağlar. Bu model, kullanıcı verilerinin gizliliğini artırır ve uygulamalar tarafından yetkisiz bilgi toplanmasını önler.
Önemli Noktalar
Scoped Storage, Android 10'da (API 29) uygulamaların cihazın dosya sistemine erişme şeklini kökten değiştiren bir mimari değişikliktir. Paylaşılan depolamaya tam erişim yerine, uygulama yalnızca kendi kum havuzuna ve MediaStore aracılığıyla belirli medya koleksiyonlarına erişir.
Scoped Storage'dan önce, READ_EXTERNAL_STORAGE iznine sahip herhangi bir uygulama, paylaşılan depolamadaki tüm dosyaları okuyabiliyordu — fotoğraflar, belgeler, indirilenler, diğer uygulamaların verileri. Bu, gizlilik riskleri yaratıyordu: güvenilmeyen uygulamalar, kullanıcının bilgisi olmadan bilgi toplayabiliyordu.
Google I/O 2019'a göre, Android kullanıcılarının %65'inden fazlası, bir uygulama seçerken veri gizliliğini kritik bir faktör olarak görüyor. Scoped Storage bu talebe doğrudan bir yanıttır: her uygulama izole bir ortamda çalışır ve yalnızca kullanıcının açık onayı ile diğer uygulamaların verilerine erişir.
Önemli: Android 10'da Scoped Storage isteğe bağlıydı (uygulama bunu requestLegacyExternalStorage ile devre dışı bırakabiliyordu). Android 11'den (API 30) itibaren Scoped Storage, target SDK'dan bağımsız olarak tüm uygulamalar için zorunlu hale geldi.
Google, eski dosya erişim modelinin üç temel sorununu çözmek için Scoped Storage'ı tanıttı: gizlilik, izin yönetimi ve artık dosyaların temizlenmesi.
Eski modelde, READ_EXTERNAL_STORAGE olan bir uygulama, tüm paylaşılan depolamayı tarayabilir ve kullanıcı dosyalarının meta verilerini toplayabilirdi — fotoğrafların coğrafi koordinatları, belge adları, dizin yapıları. Scoped Storage bu olasılığı ortadan kaldırır: izin olsa bile, uygulama yalnızca kendi oluşturduğu dosyaları ve kullanıcının SAF aracılığıyla açıkça seçtiği dosyaları görür.
Scoped Storage'dan önce, uygulamalar kaldırıldıktan sonra paylaşılan depolamada dosyalar bırakabiliyordu. Zamanla, çöp dizinler birikiyordu. Scoped Storage bunu çözer: uygulamanın kum havuzundaki tüm dosyalar uygulamayla birlikte silinir ve MediaStore aracılığıyla dosyaların bir sahibi vardır ve sistem tarafından temizlenebilir.
| Yön | Eski model (Legacy) | Scoped Storage |
|---|---|---|
| Paylaşılan depolamaya erişim | Tam (izinle) | Yalnızca kendi kum havuzu |
| Medya dosyaları | Doğrudan dosya yolu | MediaStore URI ile |
| Diğer uygulamaların dosyaları | Her zaman erişilebilir | Yalnızca SAF ile |
| Kaldırıldığında temizlik | Dosyalar kalır | Kum havuzu silinir |
| Kullanıcı kontrolü | Minimum | Her dosya için açık onay |
Scoped Storage, Android'de Google'ın genel gizlilik stratejisinin bir parçasıdır ve ayrıca "yalnızca kullanım sırasında" izinleri, kamera ve mikrofon erişim göstergeleri ve Gizlilik Panosu'nu içerir.
Her Android uygulaması, Context.getFilesDir() ve context.getCacheDir() ile erişilebilen kendi özel dizinini alır. Harici (paylaşılan) özel dizine erişmek için Context.getExternalFilesDir() kullanılır.
Scoped Storage'da, bir uygulamanın herhangi bir izin olmadan harici özel dizinine tam erişimi vardır. Burası, diğer uygulamalar tarafından erişilememesi gereken veya uygulamanın kendi kullanımı için oluşturduğu dosyaları depolamak için ana yerdir.
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()
}
}
Harici özel dizinin yolu: /storage/emulated/0/Android/data/{packageName}/files/. Android 11'den itibaren doğrudan dosya yolu mevcut değildir — yalnızca API aracılığıyla. Bu, Scoped Storage'ın bir başka güvenlik iyileştirmesidir.
MediaStore, Scoped Storage'da paylaşılan medya dosyalarına (görseller, videolar, ses) erişmek için kullanılan ana API'dir. Uygulama dosya yollarıyla değil, MediaStore sağlayıcısı tarafından sağlanan content URI'leri ile çalışır.
MediaStore üç ana koleksiyona ayrılmıştır: Images, Video ve Audio. Her koleksiyon, ContentResolver aracılığıyla CRUD işlemlerini destekler. Images koleksiyonuna yeni bir dosya eklemek için MediaStore.Images.Media kullanılır; mevcut dosyaları sorgulamak için — ilgili URI ile query kullanılır.
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
)
}
}
Önemli: MediaStore rastgele dosya yollarını desteklemez — yalnızca content URI'leri. MediaStore.Files.getContentUri() ile bir URI'den dosya yolu almaya çalışmak, Android 11+'da dosyaya doğrudan bir yol vermez. Bunun yerine, içerikle çalışmak için ContentResolver.openInputStream() ve openOutputStream() kullanın.
Storage Access Framework (SAF), uygulamanın kum havuzunun dışındaki rastgele dosyalara ve dizinlere erişmek için bir API'dir. SAF, kullanıcıya bir dosya veya dizin seçme arayüzü sağlar ve ardından uygulama geçici erişimle birlikte bir content URI alır.
SAF, MediaStore tarafından kapsanmayan senaryolar için kullanılır: rastgele belgelerle (PDF, ZIP, APK) çalışmak, SD karttaki dizinlere erişmek, diğer uygulamalardan dosya içe ve dışa aktarmak. Kullanıcı, sistem dosya seçici aracılığıyla açıkça bir dosya seçer — bu, erişim için onayını garanti eder.
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, uygulamaya kullanıcı tarafından seçilen dizine cihaz yeniden başlatılana kadar erişim sağlar (takePersistableUriPermission kullanıldığında). Android 11+'da paylaşılan depolamada rastgele dosyalara erişim elde etmenin tek yolu budur.
Mevcut bir Android uygulamasını Scoped Storage'a taşımak, birkaç önemli alanda değişiklik gerektirir. Google, Android 11+'da test ile aşamalı bir yaklaşım önermektedir.
Paylaşılan depolamadaki tüm doğrudan File işlemlerini ContentResolver.openInputStream / openOutputStream ile değiştirin. Uygulamanın kum havuzundaki (getExternalFilesDir) dosyalar için dosya yolları çalışmaya devam eder.
Belirli senaryolar için gerekli değilse (örneğin, dosya yönetimi — bunun için özel izin MANAGE_EXTERNAL_STORAGE vardır) READ_EXTERNAL_STORAGE ve WRITE_EXTERNAL_STORAGE'i kaldırın.
| Senaryo | Eski yaklaşım | Scoped Storage yaklaşımı |
|---|---|---|
| Fotoğraf kaydetme | File(path).writeBytes() | MediaStore + ContentResolver |
| PDF okuma | File(path).inputStream() | SAF ACTION_OPEN_DOCUMENT |
| Kendi dosyaları | Environment.getExternalStorageDirectory() | context.getExternalFilesDir() |
| Önbellek | File(cacheDir).writeBytes() | context.cacheDir (değişiklik yok) |
Taşıma işleminden sonra, build.gradle'da targetSdk = 30 veya üstü olarak ayarlayarak uygulamayı Android 11+ (API 30)'da test edin. Paylaşılan depolamadaki tüm dosya işlemlerinin doğrudan dosya yollarıyla değil, MediaStore veya SAF aracılığıyla çalıştığından emin olun.
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()
}
}
Google Play Console (2024)'e göre, aktif Android cihazlarının %78'inden fazlası Scoped Storage ile Android 10+ çalıştırmaktadır. Google Play'de güncelleme yayınlamak için taşıma işlemi zorunlu bir adımdır: yeni uygulamalar API 31+'i, güncellemeler API 30+'ı hedeflemelidir.
Sıkça Sorulan Sorular
Hayır, Android 11'den (API 30) itibaren Scoped Storage tüm uygulamalar için zorunludur. Android 10'da mevcut olan requestLegacyExternalStorage bayrağı API 30+'da çalışmaz. Dosya sistemine geniş erişim elde etmenin tek yolu MANAGE_EXTERNAL_STORAGE iznidir, ancak bu yalnızca dosya yöneticileri ve antivirüs uygulamaları için tasarlanmıştır.
MANAGE_EXTERNAL_STORAGE, tam dosya sistemi erişimi gerektiren uygulamalar (dosya yöneticileri, yedekleme araçları) için özel bir izindir. İzin istendiğinde, kullanıcı için bir uyarı ile sistem ekranı açılır. Google Play'de yayınlarken, bu iznin gerekliliği hakkında bir Beyan formu doldurmanız gerekir.
Android 11+'da Scoped Storage'da bir content URI'den doğrudan dosya yolu alınamaz. Bunun yerine, okumak için ContentResolver.openInputStream() ve yazmak için openOutputStream() kullanın. Bir üçüncü taraf kitaplıkla uyumluluk için dosya yoluna ihtiyacınız varsa, getCacheDir()'de dosyanın bir kopyasını oluşturun ve kopya ile çalışın.
FileProvider, değişiklik yapılmadan çalışmaya devam eder — uygulamanın kum havuzundaki dosyalara diğer uygulamalara content URI'leri aracılığıyla erişim sağlamak için kullanılır. Scoped Storage, FileProvider'ı etkilemez çünkü FileProvider, doğrudan dosya yollarıyla değil, content URI'leri düzeyinde çalışır.
Build.VERSION.SDK_INT'i kontrol edin: >= Build.VERSION_CODES.Q (29) ise, cihaz Scoped Storage'ı destekler. Ancak Android 10'da Scoped Storage, requestLegacyExternalStorage ile devre dışı bırakılabilir. Android 11+'da, manifest bayrağı requestLegacyExternalStorage'ın ayarlanıp ayarlanmadığını kontrol edin — ayarlanmamışsa, Scoped Storage etkindir.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun