MediaStore Basics: Bilder und Videos sicher nutzen
MediaStore hilft dir, geteilte Bilder und Videos sauber über Android-APIs zu lesen und zu schreiben.
Wenn deine App Bilder oder Videos aus der Galerie anzeigen, auswählen, importieren oder speichern soll, arbeitest du nicht direkt mit beliebigen Dateipfaden. Auf modernen Android-Versionen führt der normale Weg über MediaStore und passende Plattform-APIs. So respektierst du Scoped Storage, schützt Nutzerdaten und baust Code, der auch auf aktuellen Geräten zuverlässig funktioniert.
Was ist das?
MediaStore ist eine Android-API, über die du auf geteilte Mediendateien des Geräts zugreifst. Dazu gehören vor allem Bilder, Videos und Audiodateien, die nicht nur deiner App gehören, sondern in gemeinsamen Sammlungen des Systems liegen. Für diesen Artikel stehen Bilder und Videos im Mittelpunkt.
Das mentale Modell ist wichtig: MediaStore ist keine Ordnerliste und kein Ersatz für File("/sdcard/..."). Du fragst stattdessen eine systemverwaltete Datenbank ab. Jede Mediendatei hat Metadaten wie Anzeigename, MIME-Typ, Erstellungszeit, Größe und eine ID. Aus dieser ID baust du eine content://-URI. Mit dieser URI öffnest du Inhalte über den ContentResolver.
Das passt zu Scoped Storage. Seit Android den direkten Zugriff auf gemeinsamen externen Speicher stärker begrenzt, soll deine App nicht mehr frei durch alle Medienordner laufen. Sie soll klar angeben, welche Medien sie lesen oder schreiben will, und dafür die vorgesehenen APIs verwenden. MediaStore ist dabei der richtige Baustein, wenn du Medien im gemeinsamen Speicher finden, anzeigen oder neu anlegen willst.
Im Alltag taucht MediaStore zum Beispiel in Galerie-Apps, Chat-Apps, Profilbild-Funktionen, Video-Uploads, Scan-Apps oder einfachen Export-Funktionen auf. Eine Compose-Oberfläche zeigt vielleicht ein Raster aus Vorschaubildern, aber die eigentliche Medienabfrage gehört nicht in das Composable. Sie gehört in eine Repository- oder Data-Source-Klasse innerhalb deiner Data Layer. Die UI fragt nur einen Zustand ab, etwa eine Liste aus Media-Items, und reagiert auf Ladezustände, Fehler und Auswahl.
Wie funktioniert es?
Der Zugriff läuft meist über drei Schritte. Erstens wählst du die passende MediaStore-Collection, zum Beispiel MediaStore.Images.Media.EXTERNAL_CONTENT_URI für Bilder oder MediaStore.Video.Media.EXTERNAL_CONTENT_URI für Videos. Zweitens definierst du eine Projection, also die Spalten, die du lesen willst. Drittens rufst du ContentResolver.query() auf und liest den Cursor aus.
Die ID ist dabei besonders wichtig. Du liest die Spalte MediaStore.MediaColumns._ID und kombinierst sie mit der Collection-URI. Daraus entsteht eine konkrete URI für genau ein Medium. Diese URI gibst du an Image-Loader, Video-Player, Upload-Code oder deinen eigenen I/O-Code weiter. Du speicherst also nicht den Dateipfad als zentrale Wahrheit, sondern die URI oder deine eigene fachliche Referenz.
Berechtigungen hängen von Android-Version und gewünschter Aktion ab. Für viele moderne Auswahl-Workflows ist der Photo Picker besser geeignet, weil er gezielten Zugriff ohne breite Medienberechtigung ermöglicht. MediaStore bleibt wichtig, wenn du eigene Medienübersichten baust, Medien sammelst, exportierst oder mit systemweiten Sammlungen arbeitest. Für breite Lesezugriffe brauchst du je nach Android-Version passende Berechtigungen für Bilder und Videos. Prüfe diese Entscheidung bewusst, statt alte Beispiele mit pauschalem Speicherzugriff zu kopieren.
Technisch ist MediaStore I/O. Eine Query kann dauern, besonders bei vielen Bildern oder Videos. Deshalb gehört sie nicht auf den Main Thread. In Kotlin nutzt du dafür Coroutines und wechselst für blockierende Arbeit auf Dispatchers.IO. In einer sauberen Architektur kapselst du diese Arbeit in der Data Layer. Ein Repository kann eine suspendierende Funktion anbieten, etwa loadRecentImages(), während das ViewModel die Daten lädt und als UI-State bereitstellt.
Für Offline-First-Denken ist MediaStore ein lokaler Datenanbieter. Die Medien liegen bereits auf dem Gerät, aber deine App kann zusätzliche eigene Metadaten speichern: Auswahlstatus, Upload-Zustand, Bearbeitungsschritte oder Server-IDs. Diese App-Daten gehören nicht in MediaStore, sondern in deine eigene Datenbank oder deinen eigenen Cache. MediaStore liefert die geteilte Mediendatei, deine Data Layer verbindet sie mit deinem App-Modell.
In der Praxis
Ein kleines Beispiel zeigt den Grundaufbau. Die Funktion lädt die neuesten Bilder und gibt schlanke Datenobjekte zurück. Sie liest nur benötigte Spalten, baut stabile Content-URIs und führt die Abfrage auf Dispatchers.IO aus.
data class SharedImage(
val id: Long,
val uri: Uri,
val displayName: String?,
val dateAddedSeconds: Long
)
class MediaRepository(
private val contentResolver: ContentResolver
) {
suspend fun loadRecentImages(limit: Int = 50): List<SharedImage> =
withContext(Dispatchers.IO) {
val collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATE_ADDED
)
val sortOrder = "${MediaStore.Images.Media.DATE_ADDED} DESC"
contentResolver.query(
collection,
projection,
null,
null,
sortOrder
)?.use { cursor ->
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
val dateColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_ADDED)
buildList {
while (cursor.moveToNext() && size < limit) {
val id = cursor.getLong(idColumn)
val uri = ContentUris.withAppendedId(collection, id)
add(
SharedImage(
id = id,
uri = uri,
displayName = cursor.getString(nameColumn),
dateAddedSeconds = cursor.getLong(dateColumn)
)
)
}
}
} ?: emptyList()
}
}
In Compose würdest du diese Liste nicht direkt im Composable laden. Das ViewModel ruft das Repository auf, speichert Ergebnis, Ladezustand und Fehler und stellt alles als State bereit. Das Composable rendert dann nur: Ladeanzeige, Fehlermeldung oder Raster. Für Bilder kannst du einen Image-Loader verwenden, der content://-URIs versteht. Für Videos gibst du die URI an die passende Player-Schicht weiter.
Eine praktische Entscheidungsregel lautet: Wenn der Nutzer gezielt ein oder mehrere Medien auswählen soll, prüfe zuerst den Photo Picker. Wenn deine App eine eigene Medienübersicht oder einen Export in die systemweite Galerie braucht, ist MediaStore passend. Wenn die Datei nur intern zu deiner App gehört, nutze app-spezifischen Speicher statt MediaStore.
Eine typische Stolperfalle ist das Speichern alter Dateipfade. Viele ältere Tutorials lesen DATA oder arbeiten mit absoluten Pfaden. Das ist unter Scoped Storage brüchig und kann auf modernen Geräten fehlschlagen. Speichere lieber die Content-URI oder lade die Daten bei Bedarf erneut über MediaStore. Eine zweite Stolperfalle ist zu viel Arbeit auf einmal: Wenn du tausende Medien inklusive Vorschaudaten synchron im UI-Thread abfragst, wirkt die App träge oder stürzt mit ANR-Problemen ab. Lade nur notwendige Spalten, begrenze Ergebnisse, nutze Pagination oder Lazy-Listen und verschiebe I/O konsequent aus dem Main Thread.
Auch beim Schreiben neuer Medien solltest du bewusst vorgehen. Du legst über MediaStore einen Eintrag mit Metadaten an, öffnest danach einen OutputStream über den ContentResolver und schreibst die Daten. Für Bilder oder Videos, die sofort in der Galerie erscheinen sollen, ist das ein sinnvoller Weg. Für temporäre Dateien, Upload-Puffer oder interne Bearbeitungsversionen ist der gemeinsame Medienspeicher dagegen oft die falsche Stelle.
Teste dein Verständnis mit kleinen Prüfungen. Logge die erzeugten URIs und öffne sie mit einem Image-Loader. Entferne testweise die Berechtigung und prüfe, ob deine UI einen verständlichen Fehlerzustand zeigt. Gehe im Debugger durch den Cursor und kontrolliere, ob du nur die Spalten liest, die du wirklich brauchst. In Code-Reviews solltest du besonders auf Main-Thread-I/O, alte Dateipfad-APIs, fehlendes use {} beim Cursor und unklare Trennung zwischen UI und Data Layer achten.
Fazit
MediaStore ist der saubere Einstieg in geteilte Bilder und Videos unter modernem Android. Du behandelst Medien als systemverwaltete Einträge mit content://-URIs, nicht als frei verfügbare Dateien auf einem Pfad. Übe das mit einer kleinen Repository-Funktion, die aktuelle Bilder lädt, im Debugger die Cursor-Werte prüft und in einem Compose-Screen nur den fertigen UI-State rendert. Wenn du danach in einem Code-Review erklären kannst, warum die Query in die Data Layer gehört, warum sie auf Dispatchers.IO läuft und warum du keine absoluten Dateipfade speicherst, hast du die wichtigsten MediaStore-Basics verstanden.