Debugger Basics: Breakpoints, Watches und Stepping in Android Studio
Lerne, wie du mit Breakpoints, Watches und Stepping in Android Studio Code anhältst, Werte prüfst und Bugs systematisch findest.
Wenn deine App abstürzt oder sich anders verhält als erwartet, hast du zwei Möglichkeiten: Du streust Log-Aufrufe in den Code und startest die App erneut, oder du nutzt den Debugger und schaust live zu, was passiert. Der Debugger in Android Studio ist eines der mächtigsten Werkzeuge in deinem Werkzeugkasten, und gerade als Einsteiger lohnt es sich, die Grundlagen früh zu lernen. Du sparst nicht nur Zeit, du verstehst deine eigene App auch besser, weil du sie an jeder Stelle anhalten und ihren Zustand inspizieren kannst.
Was ist das?
Ein Debugger ist ein Programm, das deine laufende App kontrolliert beobachten kann. Er kann die Ausführung an einer beliebigen Codezeile anhalten, dir die aktuellen Werte aller Variablen zeigen und dich Schritt für Schritt durch den Code führen. In Android Studio läuft der Debugger über die Android Debug Bridge (ADB) und kommuniziert mit deiner App auf dem Emulator oder dem angeschlossenen Gerät.
Drei Konzepte bilden das Fundament: Breakpoints sind Markierungen an Codezeilen, an denen die App pausieren soll. Watches sind Ausdrücke, deren Werte du im Auge behalten willst, sobald der Debugger anhält. Stepping beschreibt das Vorwärtsbewegen durch den Code in kleinen, kontrollierten Schritten. Mit diesen drei Bausteinen kannst du fast jeden Bug in einer Android-App eingrenzen, von einer Compose-UI, die nicht neu zeichnet, bis zu einer Coroutine, die im falschen Dispatcher landet.
Wichtig zu verstehen: Der Debugger zeigt dir nur, was passiert, nicht warum. Das Warum musst du selbst aus dem beobachteten Verhalten ableiten. Genau deshalb ist der Debugger so wertvoll für das Lernen — du siehst die Maschinerie unter der Oberfläche und baust ein präziseres mentales Modell deiner App.
Wie funktioniert es?
Du startest den Debugger über das kleine Käfer-Icon in der Toolbar (statt des grünen Play-Buttons) oder mit der Tastenkombination Shift+F9 auf Linux/Windows beziehungsweise Ctrl+D auf macOS. Android Studio installiert die App, hängt den Debugger an den Prozess und wartet auf den ersten Breakpoint.
Breakpoints setzen
Einen Breakpoint setzt du, indem du in der Editor-Gutter links neben einer Zeilennummer klickst. Es erscheint ein roter Punkt. Sobald die App diese Zeile erreicht, wird die Ausführung pausiert, bevor die Zeile ausgeführt wird. Du siehst dann den Zustand kurz vor der relevanten Operation.
Es gibt verschiedene Arten von Breakpoints:
- Line Breakpoints halten an einer bestimmten Zeile an.
- Method Breakpoints halten beim Eintritt oder Verlassen einer Methode an.
- Field Watchpoints triggern, wenn ein Feld gelesen oder geschrieben wird.
- Exception Breakpoints halten an, sobald eine bestimmte Exception geworfen wird.
Besonders nützlich sind Conditional Breakpoints. Du klickst mit der rechten Maustaste auf einen bestehenden Breakpoint und gibst eine Bedingung an, etwa userId == 42. Der Debugger hält dann nur an, wenn die Bedingung wahr ist. Das spart enorm viel Zeit in Schleifen, wo dich nur ein bestimmter Durchgang interessiert.
Variablen und Watches
Sobald der Debugger pausiert, siehst du im Variables-Panel alle Variablen, die im aktuellen Scope sichtbar sind: lokale Variablen, Felder des umgebenden Objekts, Parameter der Methode. Du kannst Objekte aufklappen und ihre Felder rekursiv inspizieren.
Watches gehen einen Schritt weiter. Du legst einen beliebigen Ausdruck an — zum Beispiel users.filter { it.isActive }.size — und der Debugger evaluiert ihn an jedem Stop neu. Das ist hilfreich, wenn du das Ergebnis einer Berechnung verfolgen willst, ohne den Code selbst anzufassen. Verwandt dazu ist Evaluate Expression (Alt+F8), womit du beliebigen Kotlin-Code im aktuellen Kontext ausführst, etwa um eine Methode probeweise aufzurufen.
Stepping durch den Code
Wenn die App pausiert, hast du mehrere Optionen, um weiterzulaufen:
- Step Over (
F8) führt die aktuelle Zeile aus und stoppt an der nächsten Zeile derselben Methode. - Step Into (
F7) springt in eine aufgerufene Methode hinein, sodass du ihrem Verlauf folgen kannst. - Step Out (
Shift+F8) läuft den Rest der aktuellen Methode durch und stoppt eine Ebene höher beim Aufrufer. - Resume (
F9) lässt die App normal weiterlaufen, bis sie auf den nächsten Breakpoint trifft.
Mit diesen vier Befehlen kannst du den Kontrollfluss präzise durch jede beliebige Codestelle steuern. Step Into ist besonders lehrreich, weil du sehen kannst, wie deine eigenen Funktionen tatsächlich abgearbeitet werden.
In der Praxis
Stell dir vor, du hast einen Bug in einer Compose-App: Eine Liste zeigt manchmal die falsche Anzahl an Einträgen. Statt Logs zu setzen und die App zwanzigmal neu zu starten, gehst du systematisch mit dem Debugger vor.
@Composable
fun UserList(viewModel: UserViewModel) {
val users by viewModel.users.collectAsState()
Column {
users.forEach { user ->
if (user.isActive) {
UserRow(user)
}
}
}
}
class UserViewModel : ViewModel() {
private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users.asStateFlow()
fun loadUsers() {
viewModelScope.launch {
val result = repository.fetchUsers()
_users.value = result.filter { it.id > 0 }
}
}
}
So gehst du vor:
- Du setzt einen Breakpoint in
loadUsersdirekt nachrepository.fetchUsers(). Damit prüfst du, was die Datenquelle wirklich liefert. - Im Variables-Panel klappst du
resultauf und siehst die Liste vor dem Filter. Liefert das Repository bereits zu wenige Einträge, liegt der Bug nicht in deinem Code. - Du legst einen Watch auf
result.count { it.id > 0 }an, um die erwartete Anzahl nach dem Filter zu sehen. - Mit Step Over läufst du in die Filter-Operation hinein und beobachtest, ob
_users.valueden richtigen Wert bekommt. - Optional setzt du einen weiteren Breakpoint im
Column-Composable, um zu prüfen, obusersdort tatsächlich aktualisiert ankommt.
Innerhalb weniger Minuten weißt du, ob das Repository, der Filter oder die Compose-Recomposition das Problem verursacht.
Eine typische Stolperfalle
Der häufigste Fehler bei Anfängern: Breakpoints in Code zu setzen, der gar nicht ausgeführt wird, und sich dann zu wundern, dass der Debugger nicht hält. Das passiert oft bei lazy-initialisierten Werten, in Coroutines, die gar nicht starten, oder in Branch-Code, der unter den aktuellen Bedingungen nicht erreichbar ist. Wenn dein Breakpoint nicht greift, prüfe zuerst:
- Wird die Methode überhaupt aufgerufen? Setze einen Breakpoint eine Ebene höher.
- Hast du die App im Debug-Modus gestartet, nicht nur per Run-Button? Nur der Käfer-Button hängt den Debugger an.
- Ist die Code-Variante, in der du arbeitest, die, die tatsächlich kompiliert wurde? Build-Cache-Probleme können dazu führen, dass alter Code läuft.
Eine zweite Falle bei Coroutines: Wenn du in einer suspendierenden Funktion einen Breakpoint setzt und mit Step Over weitergehst, springt der Debugger manchmal scheinbar wirr durch den Code. Das liegt daran, dass Coroutines unter der Haube als State Machines kompiliert werden. Bleib ruhig, nutze stattdessen weitere Breakpoints an den Stellen, die dich interessieren, und vermeide unnötiges Stepping über suspend-Grenzen hinweg.
Wann Logging trotzdem sinnvoll ist
Der Debugger ist nicht für jede Situation das richtige Werkzeug. Wenn du ein zeitabhängiges Problem hast — etwa eine Race Condition oder einen Bug, der nur in Produktion bei echten Nutzern auftritt — kann Pausing das Problem unsichtbar machen. Hier sind strukturierte Logs (Log.d, Logcat) oder ein Tool wie Firebase Crashlytics besser geeignet. Faustregel: Debugger für reproduzierbare Bugs in deiner Entwicklungsumgebung, Logs für alles andere.
Fazit
Der Debugger ist eine Fähigkeit, die du nicht nebenbei lernst, sondern gezielt üben musst. Nimm dir vor, die nächsten zwei Wochen jeden nicht-trivialen Bug zuerst mit Breakpoints, Watches und Stepping anzugehen, bevor du auf Logs zurückgreifst. Setze bewusst Conditional Breakpoints, lege Watches auf interessante Ausdrücke und experimentiere mit Evaluate Expression, um Hypothesen zu testen. Wenn du eine eigene App hast, starte sie im Debug-Modus, halte sie an einer beliebigen Stelle an und klicke dich durch das Variables-Panel, einfach um zu sehen, was Android Studio dir alles zeigt. Schreibe parallel einen kleinen Unit-Test für die Stelle, an der dein letzter Bug saß — so kombinierst du das Verständnis aus dem Debugger mit einem dauerhaften Sicherheitsnetz. Je vertrauter du mit diesen Werkzeugen wirst, desto schneller kannst du komplexe Probleme zerlegen, und desto sicherer wirst du beim Lesen fremden Codes in Code-Reviews.