Android'de FileUriExposedException olmadan güvenli dosya paylaşımı
Harici uygulamalara file:// URI göndermek yasaktır — bu durum Android 7+ üzerinde FileUriExposedException hatasına yol açar; çözüm, FileProvider aracılığıyla content:// URI sağlamak ve Intent.FLAG_GRANT_READ_URI_PERMISSION / FLAG_GRANT_WRITE_URI_PERMISSION eklemektir. Aşağıda minimum konfigürasyon, kod ve hızlı kontroller yer almaktadır.
FileProvider kurulumu (manifest ve file_paths.xml)
- Manifest dosyasında
etiketi içine provider ekleyin (AndroidX):
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.myapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
- res/xml/file_paths.xml oluşturun — yalnızca gerçekten izin vermek istediğiniz dizinleri belirtin:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="internal_files" path="." />
<cache-path name="internal_cache" path="." />
<external-files-path name="external_files" path="." />
<external-cache-path name="external_cache" path="." />
</paths>
Son çare olmadıkça root-path eklemeyin — cihazın kök dosya sistemini açar.
URI oluşturma ve Intent gönderme — pratik örnekler
- Bir File için content:// URI elde etme
Kotlin:
val file = File(context.filesDir, "reports/report1.pdf")
val uri = FileProvider.getUriForFile(context, "com.example.myapp.fileprovider", file)
Java:
File file = new File(context.getFilesDir(), "reports/report1.pdf");
Uri uri = FileProvider.getUriForFile(context, "com.example.myapp.fileprovider", file);
- ACTION_SEND (paylaşım) — mutlaka okuma izni bayrağını ekleyin:
val send = Intent(Intent.ACTION_SEND).apply {
type = "application/pdf"
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(send, "Share file"))
- ACTION_VIEW:
val view = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "image/*")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(view)
- Kamera — EXTRA_OUTPUT (dosyanıza yazma):
val photoFile = File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), "shot.jpg")
val photoUri = FileProvider.getUriForFile(context, AUTHORITY, photoFile)
val takePic = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
startActivityForResult(takePic, REQ_CAMERA)
getUriForFile, dosya file_paths.xml içindeki tanımlara uymuyorsa IllegalArgumentException fırlatır — yol eşleşmelerini kontrol edin.
Erişim garantisi: grantUriPermission ve ClipData
Intent birden fazla uygulama tarafından işlenebiliyorsa (chooser) — tüm potansiyel alıcılara açıkça izin verin:
val resList = context.packageManager.queryIntentActivities(sendIntent, PackageManager.MATCH_DEFAULT_ONLY)
for (res in resList) {
val pkg = res.activityInfo.packageName
context.grantUriPermission(pkg, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
Gerekirse revokeUriPermission yapmayı unutmayın. Modern API'lerde ClipData ile ayarlanan bayraklar genellikle izinleri otomatik olarak yayar, ancak açık grant uyumluluğu artırır.
file_paths.xml temel etiketlerinin karşılaştırılması
| Etiket | Ne sağlar | Ne zaman kullanılır |
|---|---|---|
| files-path | getFilesDir() | Dahili özel dosyalar |
| cache-path | getCacheDir() | Geçici dosyalar |
| external-files-path | getExternalFilesDir() | Kullanıcı dosyaları, dışarıdan görünür (ancak uygulama için özeldir) |
| external-cache-path | getExternalCacheDir() | Harici depolamadaki önbellek |
Sık yapılan hatalar
- Manifest ve getUriForFile() içindeki authority eşleşmemesi → IllegalArgumentException veya erişim reddi.
- file_paths.xml res/xml/ içinde yok veya meta-data adı eşleşmiyor.
- FLAG_GRANT_READ_URI_PERMISSION / WRITE unutuldu → alıcıda SecurityException.
- Uri.fromFile() kullanımı → API ≥ 24 üzerinde FileUriExposedException.
- Çok geniş root-path → istenmeyen dosyalara rastgele erişim.
Hata ayıklama sırasında logcat'te FileUriExposedException, IllegalArgumentException ve SecurityException arayın — bu durum sorunu hızla gösterir.
SSS
-
Provider için android:exported="true" gerekli mi?
Hayır — false bırakmak ve erişimi grantUriPermission/Intent bayrakları ile vermek daha iyidir. -
URI'ye kalıcı erişim verilebilir mi?
FileProvider için kalıcı persisted izinler genellikle mevcut değildir; uzun süreli erişim için DocumentsProvider ve CONTENT_RESOLVER.takePersistableUriPermission yöntemlerine bakın. -
Scoped Storage (Android 10+) ile dosyalar nerede saklanmalı?
getFilesDir(), getCacheDir(), getExternalFilesDir() ve file_paths.xml içindeki ilgili etiketleri kullanın.
İsterseniz, kullanım durumunuz için hazır bir file_paths.xml veya Kotlin üzerinde getShareIntent(File) yardımcı programını hazırlayabilirim.