Android'de Uri, content:// ve SAF ile Nasıl Çalışılır?
Kısaca özetlemek gerekirse: net.Uri, Android'de evrensel bir kaynak tanımlayıcısıdır; content://, ContentProvider ve MediaStore üzerinden erişim sağlayan bir şemadır; SAF (Storage Access Framework) ise sistem seçici aracılığıyla klasörlere ve dosyalara güvenli, kalıcı erişim sağlamak için gereklidir. Aşağıda, projenize doğrudan ekleyebileceğiniz kısa kurallar ve çalışan örnekler yer almaktadır.
net.Uri ve content:// Şeması Nedir?
Android'de Uri yalnızca bir dosya yolu değildir; şu yapıya sahiptir: şema://yetkili/yol?sorgu. Harici dosyalarla çalışırken en sık karşılaşılanlar şunlardır:
- content:// — ContentResolver ve sağlayıcılar (MediaStore, Downloads, DocumentsProvider) üzerinden erişim;
- file:// — artık kullanılmayan doğrudan yol (Android 7 ve üzeri sürümlerde FileUriExposedException hatasına neden olur).
Uri oluşturma örneği:
val uri = Uri.parse("content://com.android.externalstorage.documents/document/primary%3ADownload")
Temel kurallar:
- content:// adresinden diske ait gerçek bir yol elde etmeye çalışmayın — bu yol mevcut olmayabilir veya erişilemez olabilir.
- Okuma işlemi için ContentResolver.openInputStream(uri), yazma işlemi için openOutputStream(uri) kullanın.
Storage Access Framework (SAF): Klasör İsteği ve Kalıcı İzinler
SAF, kullanıcının bir klasör/dosya seçmesine ve uygulamanıza kalıcı olarak saklanabilen izinler vermesine olanak tanır.
Klasör için seçiciyi başlatma:
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, REQUEST_CODE_OPEN_TREE)
Sonucu işleme ve izinleri kaydetme:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_OPEN_TREE && resultCode == Activity.RESULT_OK) {
val treeUri = data?.data ?: return
contentResolver.takePersistableUriPermission(
treeUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
val doc = DocumentFile.fromTreeUri(this, treeUri)
// bundan sonra dosyaları arayabilirsiniz: doc.findFile("example.txt")
}
}
takePersistableUriPermission çağrısı yapılmazsa, Uri üzerinden erişim yalnızca süreç canlıyken geçerli olur — yeniden başlatmadan sonra izinleri kaybedersiniz.
Pratik İpuçları ve Kod Örnekleri
SAF üzerinden dosya okuma:
fun readFileFromSaf(context: Context, treeUri: Uri, fileName: String): String? {
val doc = DocumentFile.fromTreeUri(context, treeUri)?.findFile(fileName) ?: return null
return context.contentResolver.openInputStream(doc.uri)?.bufferedReader()?.use { it.readText() }
}
ContentResolver üzerinden okuma (MediaStore/Downloads):
val cursor = contentResolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), selection, null, null)
OpenableColumns.DATA'ya güvenmeyin — birçok Android cihazda bu değer boştur. Akışları (streams) kullanın.
İpuçları:
- Kullanıcı klasörleri ve belgelerle çalışmak için SAF'i tercih edin.
- Medya kütüphanesi için MediaStore ve content:// kullanın.
- READ_EXTERNAL_STORAGE izinleri yalnızca eski API'ler üzerinden doğrudan erişim durumunda gereklidir; SAF için bunlara ihtiyaç yoktur.
- Her zaman SecurityException'ı yakalayın ve uri != null olduğunu kontrol edin.
Sık Yapılan Hatalar
- content:// adresinden dosya sistemi yolunu elde etmeye çalışmak — çoğu zaman imkansızdır.
- takePersistableUriPermission çağrısının yapılmaması — izinler yeniden başlatmadan sonra kaybolur.
- file:// kullanımı, Android 7 ve üzeri sürümlerde FileUriExposedException hatasına neden olur.
- OpenableColumns.DATA'nın her zaman bir yol döndüreceği beklentisi — bunun garantisi yoktur.
SSS
- Dosyalara ait gerçek yollarla uğraşmak gerekir mi?
- Genellikle hayır. InputStream/OutputStream ve DocumentFile/sağlayıcı API'leri üzerinden çalışın.
- SAF ne zaman, MediaStore ne zaman kullanılmalı?
- SAF — belgeler ve kullanıcı klasörleri için. MediaStore — medya içeriği (fotoğraflar, videolar, sesler) için.
- Erişimi kalıcı olarak nasıl saklayabilirim?
- contentResolver.takePersistableUriPermission(...) kullanarak ve Uri'yi saklayarak (örneğin SharedPreferences içinde).
Açıklanan yaklaşımları kullanın — bu, dosya işlemlerini güvenilir hale getirecek ve Android 10+ ile Scoped Storage ile uyumlu olacaktır.