Fotos aufnehmen in Android: Dateien, Berechtigungen und sichere Ausgabe
Du lernst, wie du mit Activity-Result-Contracts Fotos aufnimmst, Ausgabedateien per FileProvider absicherst und Berechtigungen gezielt einsetzt.
Eine App, die Fotos aufnehmen soll, braucht mehr als einen einfachen Kamera-Intent. Sie braucht eine sichere Ausgabedatei, das richtige Berechtigungskonzept und einen klaren Übergabepunkt zwischen deiner App und dem System-Kamera-Prozess. Dieser Artikel zeigt dir, wie Image Capture in modernen Android-Apps richtig funktioniert – ohne eigene Kamera-Activity und ohne unnötige Berechtigungen.
Was ist das?
Image Capture beschreibt das Muster, mit dem eine Android-App das Kamerasystem des Geräts aufruft, die Aufnahme an die System-Kamera-App delegiert und das fertige Bild kontrolliert zurückerhält. Deine App schreibt dabei selbst keinen einzigen Pixel – sie legt lediglich fest, wohin das Foto gespeichert werden soll, und reagiert auf das Ergebnis.
Dieses Delegationsmuster ist kein Zufall. Android trennt bewusst zwischen App-Prozess und Kamera-Prozess, um Sicherheit und Datenschutz zu gewährleisten. Die System-Kamera besitzt die CAMERA-Berechtigung, deine App muss sie nicht selbst halten, solange sie keinen direkten Hardwarezugriff benötigt. Das Ergebnis ist eine klare Verantwortungsaufteilung: Die Kamera-App übernimmt Vorschau und Auslösung, deine App übernimmt Dateiverwaltung und Weiterverarbeitung.
Im Kontext der Android-Roadmap gehört Image Capture zu Phase 9, die sich mit Platform APIs und Gerätefähigkeiten befasst. Das Muster nutzt Activity Result Contracts – ein modernes Jetpack-Konzept aus der androidx.activity-Bibliothek, das den alten startActivityForResult-Zyklus durch typsichere, registrierungsbasierte Callbacks ersetzt.
Wie funktioniert es?
Der Einstiegspunkt ist ActivityResultContracts.TakePicture(). Dieser Contract nimmt eine Uri entgegen – den Speicherort für das Foto – und liefert einen Boolean zurück: true bei erfolgreicher Aufnahme, false wenn der Nutzer abbricht oder ein Fehler auftritt.
Die Ausgabe-URI vorbereiten
Bevor du den Launcher auslöst, musst du eine Datei anlegen und daraus eine teilbare URI erzeugen. Dafür ist FileProvider aus Jetpack Core zuständig. Er erlaubt es, eine Datei aus deinem app-privaten Verzeichnis sicher mit einem externen Prozess zu teilen, ohne das gesamte Dateisystem freizugeben.
In AndroidManifest.xml registrierst du den Provider einmalig:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
Die Pfaddefinition in res/xml/file_paths.xml legt fest, welche Verzeichnisse freigegeben werden dürfen:
<paths>
<cache-path name="camera_images" path="images/" />
</paths>
Berechtigungen: weniger ist mehr
Solange du nur den System-Kamera-Intent verwendest, benötigst du keine CAMERA-Berechtigung in deinem Manifest. Die System-Kamera-App besitzt diese bereits. Erst wenn du direkt mit CameraX oder der Camera2 API auf die Hardware zugreifst, musst du die Berechtigung selbst anfordern. Das reduziert den Reibungsverlust beim Nutzer erheblich und entspricht Googles Empfehlung, nur Berechtigungen anzufordern, die wirklich notwendig sind.
In der Praxis
Das folgende Beispiel zeigt ein vollständiges Compose-Setup mit ViewModel. Das ViewModel erzeugt die URI und speichert sie zustandssicher, die Composable-Funktion registriert den Launcher und reagiert auf das Ergebnis.
class CameraViewModel : ViewModel() {
private val _photoUri = MutableStateFlow<Uri?>(null)
val photoUri: StateFlow<Uri?> = _photoUri.asStateFlow()
fun createTempImageUri(context: Context): Uri {
val imageFile = File(
context.cacheDir,
"images/camera_${System.currentTimeMillis()}.jpg"
).also { it.parentFile?.mkdirs() }
return FileProvider.getUriForFile(
context,
"${context.packageName}.fileprovider",
imageFile
).also { _photoUri.value = it }
}
fun onPhotoTaken(success: Boolean) {
if (!success) _photoUri.value = null
}
}
@Composable
fun CameraScreen(viewModel: CameraViewModel = viewModel()) {
val context = LocalContext.current
val photoUri by viewModel.photoUri.collectAsState()
val takePicture = rememberLauncherForActivityResult(
contract = ActivityResultContracts.TakePicture()
) { success ->
viewModel.onPhotoTaken(success)
}
Column(modifier = Modifier.padding(16.dp)) {
photoUri?.let { uri ->
AsyncImage(
model = uri,
contentDescription = "Aufgenommenes Foto",
modifier = Modifier.fillMaxWidth()
)
}
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
val uri = viewModel.createTempImageUri(context)
takePicture.launch(uri)
}) {
Text("Foto aufnehmen")
}
}
}
Stolperfalle: URI-Verlust bei Bildschirmrotation
Ein sehr häufiger Fehler ist, die URI als lokale Variable in der Composable-Funktion zu speichern. Nach einer Bildschirmrotation wird die Composable-Funktion neu aufgebaut, die lokale Variable ist weg – das Foto liegt zwar auf dem Gerät, aber deine App weiß nicht mehr, wo. Das ViewModel überlebt Konfigurationsänderungen automatisch und ist deshalb der richtige Ort für die URI. Alternativ funktioniert rememberSaveable mit einem geeigneten Saver, wenn kein ViewModel vorhanden ist.
Berechtigungen gezielt einfordern
Wenn dein Use Case doch eine explizite Berechtigung erfordert – etwa Mikrofon für Videoaufnahmen oder Speicherzugriff auf älteren API-Levels – fordere sie unmittelbar vor der Nutzung an, nicht beim App-Start. Zeige dem Nutzer in der UI kurz den Kontext, bevor der System-Dialog erscheint. Das verbessert die Akzeptanzrate und entspricht den Android-Security-Best-Practices, die den Grundsatz des minimalen Privilegs explizit als Leitlinie nennen.
Fazit
Image Capture ist ein präzises Beispiel für Androids Sicherheitsarchitektur in der Praxis: Du delegierst die eigentliche Aufnahme an das System, übernimmst aber die Verantwortung für Ausgabedatei und Zustandsverwaltung. Um dein Verständnis zu festigen, baue eine kleine App, die ein Foto aufnimmt, es anzeigt und die URI über mehrere Rotationen hinweg korrekt behält. Überprüfe anschließend im Device File Explorer von Android Studio, ob die Datei tatsächlich im Cache-Verzeichnis landet, und verifiziere im Manifest, dass dein FileProvider keine ungewollten Pfade freigibt. Ein kurzer Code-Review mit dem Fokus auf URI-Lebenszyklus und Berechtigungsanforderungen deckt die meisten typischen Fehler in diesem Bereich zuverlässig auf.