Android Coden
Android 4 min lesen

Kontaktzugriff in Android

Android sichert Kontaktdaten über Permissions und ContentProvider. Dieser Artikel zeigt den datenschutzkonformen Zugriff auf das Adressbuch.

Kontakte gehören zu den sensibelsten Daten auf einem Smartphone – sie enthalten nicht nur Informationen über den Gerätebesitzer, sondern auch über alle Menschen in seinem Umfeld. Android schützt diesen Datenschatz deshalb hinter einem zweiteiligen Mechanismus: einer expliziten Laufzeit-Permission und einem strukturierten ContentProvider namens ContactsContract. Wer beides versteht und bewusst einsetzt, kann Kontaktfunktionen bauen, die sowohl nützlich als auch datenschutzkonform sind.

Was ist das?

Kontaktzugriff in Android bedeutet, über den ContactsContract-ContentProvider auf die zentrale Kontaktdatenbank des Betriebssystems zuzugreifen. Ein ContentProvider ist Androids Standardmechanismus, um strukturierte Daten zwischen Apps und dem System auszutauschen – konzeptionell ähnlich einer SQL-Tabelle, die du über eine URI adressierst.

Der Zugriff auf Kontakte ist als gefährliche Permission (dangerous) eingestuft. Das unterscheidet ihn von harmlosen Berechtigungen wie INTERNET: Der Nutzer muss ausdrücklich zustimmen, und zwar zur Laufzeit – nicht beim Installieren der App. Android kennt zwei Berechtigungen für das Adressbuch:

  • READ_CONTACTS – erlaubt das Lesen aller Kontaktdaten
  • WRITE_CONTACTS – erlaubt das Anlegen, Ändern und Löschen von Kontakten

Beide müssen im AndroidManifest.xml deklariert und über das ActivityResult-API zur Laufzeit eingeholt werden. Wenn du nur liest, brauchst du WRITE_CONTACTS nicht – das Minimalprinzip gilt schon bei der Permission-Deklaration.

Wie funktioniert es?

ContactsContract organisiert Kontaktdaten in drei hierarchischen Ebenen:

  • Contact – die aggregierte, sichtbare Person im Adressbuch
  • RawContact – ein separater Datensatz je synchronisiertem Konto (z. B. Google, SIM, Exchange)
  • Data – einzelne Datenpunkte wie Telefonnummer, E-Mail-Adresse oder Postanschrift

Eine Abfrage läuft immer über den ContentResolver, den du per context.contentResolver erhältst. Du übergibst eine URI, eine Projection (gewünschte Spalten), eine optionale Selection mit Argumenten und eine Sortierung.

Permission anfragen mit dem ActivityResult-API

Seit Android 6.0 (API 23) gehören Laufzeit-Permissions zum Pflichtprogramm. Mit Jetpack und Compose nutzt du rememberLauncherForActivityResult, um den Lifecycle sauber zu verwalten:

@Composable
fun ContactsScreen(onPermissionGranted: () -> Unit) {
    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.RequestPermission()
    ) { granted ->
        if (granted) onPermissionGranted()
    }

    LaunchedEffect(Unit) {
        launcher.launch(Manifest.permission.READ_CONTACTS)
    }
}

Kontaktnamen abfragen

fun readDisplayNames(resolver: ContentResolver): List<String> {
    val projection = arrayOf(
        ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
    )
    val cursor = resolver.query(
        ContactsContract.Contacts.CONTENT_URI,
        projection,
        null,
        null,
        ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + " ASC"
    ) ?: return emptyList()

    return buildList {
        cursor.use {
            while (it.moveToNext()) {
                val name = it.getString(
                    it.getColumnIndexOrThrow(
                        ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
                    )
                )
                add(name)
            }
        }
    }
}

cursor.use { } schließt den Cursor automatisch – auch bei Ausnahmen. Das Vergessen dieses Schritts ist eine klassische Ressourcen-Leckage.

In der Praxis

Data-Layer: ContentResolver gehört ins Repository

Ruf ContentResolver niemals direkt in einer Composable oder einem ViewModel auf. Die Architektur-Richtlinie von Android ist eindeutig: Datenzugriff gehört in den Data-Layer. Ein einfaches Repository sieht so aus:

class ContactsRepository(private val resolver: ContentResolver) {
    fun getDisplayNames(): List<String> = readDisplayNames(resolver)
}

Das ViewModel injiziert das Repository, startet die Abfrage in einer Coroutine und stellt das Ergebnis als StateFlow bereit. Dadurch bleibt die UI frei von Android-API-Importen und lässt sich mit einem gefakten Repository in Unit-Tests prüfen.

Stolperfalle: null als Projection

Viele Anfänger übergeben null als Projection, weil das eine schnelle Lösung ist – es liest dann alle verfügbaren Spalten zurück. Das verlangsamt die Abfrage spürbar, erhöht den Speicherverbrauch und liest Daten ein, die die App gar nicht benötigt. Wähle immer explizit nur die Spalten, die du tatsächlich verwendest.

Stolperfalle: Ablehnungsfall ignorieren

Wenn der Nutzer die Permission einmal abgelehnt hat, zeigt Android beim nächsten launch()-Aufruf unter Umständen keinen Systemdialog mehr. Du musst shouldShowRequestPermissionRationale() prüfen: Gibt die Methode true zurück, erkläre dem Nutzer zuerst in eigener UI, warum die Funktion die Berechtigung braucht. Gibt sie false zurück und die Permission fehlt noch immer, schicke den Nutzer aktiv in die Systemeinstellungen. Apps, die diesen Schritt übergehen, wirken aufdringlich und erhalten schlechte Bewertungen.

Minimalprinzip konsequent durchhalten

Google-Play-Richtlinien und die DSGVO fordern, dass du nur so viele Daten liest, wie für den konkreten Anwendungsfall nötig sind. Brauchst du lediglich Namen und Telefonnummern für eine Schnellwahl-Funktion, frage nicht auch nach E-Mail-Adressen, Geburtstagen oder Adressen. Beschränke die Berechtigung auf READ_CONTACTS, solange du nicht aktiv schreibst. Jede unnötige Datenmenge erhöht das Risiko bei einem möglichen Datenleck und signalisiert mangelndes Vertrauen gegenüber deinen Nutzern.

Fazit

Kontaktzugriff ist keine komplizierte API, aber sie fordert diszipliniertes Arbeiten: Permissions korrekt und erklärt anfragen, den Ablehnungsfall vollständig behandeln, nur die nötigen Felder lesen und die Abfragelogik sauber im Data-Layer kapseln. Öffne jetzt ein bestehendes Projekt oder starte eine neue App und baue eine einfache Kontaktliste: Frage READ_CONTACTS an, lies ausschließlich Anzeigenamen und Telefonnummern, zeige die Liste in Compose – und entziehe dann im Emulator die Berechtigung über die Systemeinstellungen. Wenn deine App diesen Entzug elegant abfängt und dem Nutzer eine nachvollziehbare Erklärung anzeigt, hast du den wichtigsten Teil dieses Themas wirklich verstanden.

Quellen (6)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.