Lektion 14: Mit Debugging Programmfehler im Quellcode finden

In dieser Lektion werden wir die Debugging-Fähigkeiten von Android Studio kennenlernen.

Debugging ist eines der wichtigsten Hilfsmittel bei der Entwicklung von mobilen Anwendungen. Durch Debugging können schwer zu findende Programmfehler schnell im Quellcode lokalisiert und sicher behoben werden.

Besonders wenn die Komplexität der Android App zunimmt und Zeilenanzahl im Quellcode ansteigt, kann auf Debugging bei der Fehlersuche nicht verzichtet werden.


In Android Studio ist ein mächtiger, interaktiver Debugger integriert, den wir zu Beginn dieser Lektion ausführlich vorstellen werden. Er wird uns später bei der Entwicklung unserer Android App noch sehr wichtige Dienste erweisen und helfen, so manchen gut verstecken Programmfehler ausfindig zu machen.

Nachdem wir den Debugger von Android Studio kennengelernt haben, werden wir einige Zeilen Debugging-Code zu Testzwecken in unsere Anwendung einfügen. Anschließend starten wir mit dem Debuggen unserer Android App. Dabei erfahren wir was Breakpoints sind und wie man mit ihrer Hilfe zur Laufzeit durch den Quellcode navigiert.

Am Ende dieser Lektion werden wir uns mit dem Stack Trace beschäftigen. Er wird immer dann von Android Studio ausgegeben, wenn es zum Absturz der Anwendung kommt. Der Stack Trace enthält Informationen über die Absturzstelle im Quellcode. Er ist sozusagen die Spur im Speicher, die direkt zur Absturzursache führt.

1. Der Debugger von Android Studio

Ein Debugger ist ein Werkzeug zum Auffinden, Diagnostizieren und Beheben von Fehlern in Anwendungen. Er wird hauptsächlich für folgende Aufgaben eingesetzt:

  • Steuerung des Programmablaufs – Durch Haltepunkte, sog. Breakpoints, wird zur Laufzeit an vorher festgelegte Zeilen im Quelltext navigiert. An diesen Stellen wird das Programm angehalten und kann anschließend durch Verarbeitung von Befehlen schrittweise fortgesetzt werden.

  • Betrachten von Daten – Mit dem Debugger können die verfügbaren Variablen ausgelesen werden. Welche Variablen das sind, ist von der Stelle im Quellcode abhängig, an dem die Anwendung angehalten wurde. Es können primitive und komplexe Datentypen inspiziert werden.

  • Auswerten von Ausdrücken – Der Debugger kann zur Laufzeit beliebige Ausdrücke auswerten. Die zu berechnenden Ausdrücke können frei vom Entwickler vorgegeben werden. Es können alle am Haltepunkt verfügbaren Variablen in den Ausdrücken benutzt werden.

Android Studio besitzt einen eigenen Debugger, der in dem Debug Tool Window der Entwicklungsumgebung integriert ist. Mit dem Debugger können sowohl in Java als auch in C und C++ geschriebene Quelltexte analysiert werden.

Um auf das Debug Tool Window zugreifen zu können, muss die eigene Android App im Debug-Modus ausgeführt werden. Damit dies möglich ist, muss die App als debuggable konfiguriert sein. Was das bedeutet und wie dies erfolgt, werden wir in dem nächsten Abschnitt erklären.

1.1 Voraussetzungen für das Debugging der eigenen Android App

Debugging kann nur an Android Apps vorgenommen werden, für die ein Debug Build Variant konfiguriert wurde. Ein Build Variant ist eine speziell konfigurierte Version der App. Mit Hilfe von Build Variants können verschiedene Versionen der eigenen App erstellt werden, bspw. eine Free– und eine Paid-Version der App oder eben eine Debug-Version.

Die verschiedenen Build Variants der App werden in der build.gradle Datei auf Modulebene konfiguriert. Wenn für einen Build Variant das Debugging ermöglicht werden soll, muss die debuggable build type-Eigenschaft in der entsprechenden build.gradle Datei auf true gesetzt werden.

Wir müssen diesen Schritt jedoch nicht mehr ausführen, da dies Android Studio bereits für uns übernommen hat. Immer wenn eine neues Projekt in Android Studio erstellt wird, wird für das mit erstellte Modul (bei uns das Modul app) jeweils ein Debug Build Variant und ein Release Build Variant angelegt.

Hinweis: Der Debug Build Variant wird automatisch von Android Studio mit debuggable true konfiguriert. Man kann ihn nicht in der build.gradle Konfigurationsdatei auffinden, obwohl er dort eigentlich konfiguriert werden müsste. Dies geschieht jedoch für diesen Sonderfall automatisch im Hintergrund.

Welche Build Variants bereits von Android Studio für unsere Anwendung konfiguriert worden sind, können wir mit Hilfe des Build Variants Tool Windows überprüfen. Darin legen wir auch fest, welcher Build Variant für den Zusammenbau unserer Android App verwendet werden soll.

Das Build Variants Tool Window öffnen wir folgendermaßen:

  1. Mit der Maus auf den Menüeintrag View in der oberen Menüleiste klicken.
  2. Anschließend auf Tool Windows klicken.
  3. Schließlich auf den Eintrag Build Variants klicken.
menu_build_variants

Über die obere Menüleiste öffnen wir das Build Variant Tool Window

Anschließend klappt am rechten Bildschirmrand das Build Variant Tool Window auf.

build_variant_tool_window

Festlegen welcher Build Variant für den Zusammenbau der Android App verwendet werden soll

In dem Build Variant Tool Window (Markierung A) wird für jedes Modul der Android App der jeweils ausgewählte Build Variant angezeigt. Unsere Android App besteht aus genau einem Modul, dem app Modul. Android Studio hat für dieses Modul zum Projektstart automatisch zwei Build Variants angelegt, einen Debug- und einen Release-Build Variant.

Welcher Build Variant für den Zusammenbau unserer Android App verwendet werden soll, kann durch Klicken in das Build Variant-Feld (Markierung B) festgelegt werden. Damit an unsere App Debugging durchgeführt werden kann, muss als Build Variant der Eintrag debug ausgewählt werden.

Da, wie in der oberen Abbildung zu sehen, als Build Variant der Eintrag debug ausgewählt wurde, wird unsere Android App im Debug-Modus ausgeführt. Somit sind alle Voraussetzungen für das Debugging unserer eigenen Android App erfüllt.

1.2 Aufbau und Funktion des Debug Tool Window von Android Studio

An dieser Stelle möchten wir den Aufbau und die grundlegenden Funktionen des Debug Tool Windows von Android Studio besprechen. Von diesem Tool Window aus wird das Debugging des Quellcodes überwacht und gesteuert. Welche Informationen darin angezeigt werden, ist von den angesteuerten Breakpoint abhängig und ändert sich dynamisch.

In der unteren Abbildung ist das Debug Tool Window in der Standardansicht dargestellt:

debug_tool_window

Das Debug Tool Window ist nur im Debug-Mode verfügbar

Wird die Android App im Debug-Modus ausgeführt und ein Breakpoint (Haltepunkt) im Quellcode angesteuert, öffnet sich automatisch das Debug Tool Window, wie in der oberen Abbildung zu sehen. In der Tool Window-Leiste befindet sich dann auch der Debug-Eintrag (Markierung A).

Das Debug Tool Window besitzt zwei übergeordnete Tabs, den Debugger– und Console-Tab. Für das Debugging der eigenen App muss der Debugger-Tab (Markierung B) aktiviert sein. Dieser enthält die Steuerelemente des Debuggers und stellt Informationen über die aufgerufenen Methoden und sichtbaren Variablen zum Ausführungszeitpunkt dar.

In der unteren Abbildung ist der Debugger-Tab des Debug Tool Windows dargestellt:

debug_tool_window_aufbau

Die verschiedenen Tabs (Frames, Variables, Watches) des Debug Tool Windows

Der Debugger-Tab ist in die folgenden drei Bereiche unterteilt:

  1. Frames / Threads – In dem Frames Bereich wird der Call Stack (Aufrufstapel) angezeigt. Der Call Stack ist ein Stapel der Methodenaufrufe die zu dem gegenwärtigen Breakpoint geführt haben. In dem Aufrufstapel befindet die zuletzt aufgerufene Methode ganz oben. Die Methode die sie aufgerufen hat, befindet sich eine Position darunter. Alle Methode, die zu der Android Runtime gehören, sind gelb umrahmt. Die Methoden der eigenen App sind zur besseren Unterscheidung weiß umrahmt.

  2. Variables – In dem Variables Bereich werden die gerade sichtbaren Variablen aufgeführt. Referenziert die Variable auf ein Objekt, kann man sich den Objektbaum der Variable aufklappen lassen und komfortabel durch diesen navigieren. Ist in der Variable ein einfacher Wert gespeichert, wird dieser direkt angezeigt.

  3. Watches – In dem Watches Bereich können beliebige Variablen unter Beobachtung gestellt werden. Die zu beobachtenden Variablen müssen dafür nur der Watches-Liste hinzugefügt werden.

Neben den drei oben genannten Bereichen enthält der Debugger-Tab noch zwei sehr wichtige Menüleisten. Sie befinden sich am oberen und linken Rand des Debugger-Tabs. Mit diesen beiden Menüleisten kann auf alle wichtigen Debugging-Funktionen schnell und komfortabel zugegriffen werden.

In der unteren Abbildung sind die beiden Debugger-Menüleisten dargestellt (wichtige Buttons umrahmt):

debug_tool_window_buttons

Die wichtigsten Debugging-Funktionen in den Toolbars des Debug Tool Windows

Folgende Bereiche der Debugger-Menüleisten sind für das Debugging besonders wichtig:

  1. Resume / Pause / Stop – Mit diesen drei Buttons kann der Ablauf der eigenen App gezielt gesteuert werden. So kann die Anwendung durch Klick des jeweiligen Buttons wieder fortgesetzt (Resume), pausiert (Pause) oder beendet (Stop) werden. Das Beenden der Anwendung führt jedoch nicht zum Schließen des Debuggers.

  2. Settings – Mit dem Settings Button können wichtige Debugger-Einstellungen geändert werden. Man kann bspw. das Anzeigen der Variablenwerte innerhalb des Quellcodes deaktivieren oder die Rückgabewerte von Methoden ausgeben lassen.

  3. Step Over / Step Into / Force Step Into / Step Out – Mit diesen vier Buttons wird vorgegeben, welche Stelle im Quellcode als nächste angelaufen werden soll, wenn die App an einem Breakpoint angehalten wurde.

    Mit dem Step Over Button wird über die Zeile im Quellcode gesprungen, an welcher die App gerade angehalten wurde. Dabei werden alle Befehle dieser Zeile, Methodenaufrufe mit eingeschlossen, sofort ausgeführt. Die App pausiert automatisch, sobald die nächste Zeile im Code erreicht wird.

    Mit dem Step Into Button werden alle Befehle der aktuellen Zeile ausgeführt, bis der erste Methodenaufruf dieser Zeile erreicht wird. Dann wird in die Methode hinein gesprungen und die App an der ersten Zeile in dieser Methode angehalten. Werden mehrere Methode in einer Zeile aufgerufen, werden diese der Reihe nach von links nach rechts ausgeführt. Manche Methoden, wie Bibliotheksmethoden, werden jedoch übersprungen.

    Der Force Step Into Button verhält sich dem Step Into Button sehr ähnlich. Durch ihn ist es jedoch möglich auch in extern definierte Methoden, wie Bibliotheksmethoden, hinein zu springen.

    Mit dem Step Out Button wird aus der aktuellen Methode, in welcher die App gerade pausiert ist, heraus gesprungen. Dabei werden alle Befehle innerhalb dieser Methode noch ausgeführt und anschließend die übergeordnete Stelle im Quellcode angesprungen, von der aus vorher in die Methode hinein gesprungen wurde. Die Anwendung wird an der Stelle direkt nach dem Rücksprung erneut angehalten.

Wir haben nun den grundlegenden Aufbau und die wichtigsten Funktionen des Debug Tool Windows von Android Studio kennengelernt. Später, im praktischen Teil dieser Lektion, werden wir dieses Tool Window ausgiebig für des Debugging unserer Android App nutzen, wodurch sich die Funktionsweise des Debuggers viel besser erschließen lässt.

1.3 Der Stack Trace und seine Bedeutung für das Debugging

Auf dem Stack (Stapelspeicher) werden die aufgerufenen Methoden hinterlegt, die zu dem aktuellen Anwendungszustand geführt haben. Es entsteht so eine Liste von Methodenaufrufen, deren Rückverfolgung es erlaubt, den Pfad vom Start des Programms bis hin zum aktuellen Zustand zu erkennen.

Der Stack Trace (Stapelspeicherzurückverfolgung) zeigt die Liste der Methodenaufrufe zusammen mit den Dateinamen und Zeilennummern. Er wird zu Diagnosezwecken im Falle eines Programmabsturzes automatisch erstellt, um damit die Aufrufkaskade, die zu dem Fehler führte, zu rekonstruieren.

Der Stack Trace enthält somit präzise Informationen über die Absturzstelle im Quellcode. Er ist sozusagen die Spur im Speicher, die direkt zur fehlerhaften Zeile im Quellcode führt. Diese kann dann mittels Debugging zur Laufzeit analysiert werden, um die Absturzursache zu finden.

Sobald es zum Absturz der eigenen App auf dem angeschlossenen Android Gerät kommt, kann der Stack Trace im Logcat Tool Window von Android Studio betrachtet werden.

In der unteren Abbildung ist ein typischer Stack Trace einer Android App dargestellt:

stack_trace

Der Stack Trace führt zum Fehler im Quellcode und wird im Logcat Tool Window angezeigt

In der oben dargestellten Abbildung sind die folgenden Stellen besonders interessant:

  1. Stack Trace – Von dem großen roten Rahmen wird der gesamte Stack Trace umschlossen. Er enthält Informationen über die aufgerufenen Methoden, die zu dem Fehler geführt haben. Die Methoden sind der Reihenfolge, in der sie aufgerufen wurden, nach sortiert. Ganz oben steht die zuletzt ausgeführten Methode. Ihr folgt die Methode, von der sie aufgerufen wurde. Weiterhin informiert der Stack Trace über die Ausnahme, die zu dem Absturz geführt hat, hier RuntimeException (Laufzeitfehler). Auch wird die genaue Ursache, die zu der Ausnahme geführt hat, hier divided by zero (durch Null geteilt), mit angegeben.

  2. Absturzursache – Mit den Worten „Caused by:“ wird die Ursache des Absturzes eingeleitet. Danach wird die Art der Ausnahme (ArithmeticException) angegeben und der Auslöser (divided by zero) genannt, der zu dem Absturz führte.

  3. Fehlerhafte Zeile – Im Logcat Tool Window wird der Stack Trace als Text mit Verlinkungen, die direkt zu der zugehörigen Stelle im Quellcode führen, dargestellt. Links, die zu Codestellen unseres eigenen Quelltextes führen, sind in blauer Schrift geschrieben. Zu jeder aufgeführten Methode wird ihre zugehörige Klasse angegeben, gefolgt von der Zeilennummer, in welcher der zu dem Fehler führende Aufruf erfolgte.

Für das Debugging ist der Stack Trace von großer Bedeutung. Er führt den Programmierer direkt zu den problematischen Stellen im Quellcode. Werden die aufgezeigten Stellen zur Laufzeit im Debugger genauer untersucht, kann die Absturzursache schnell gefunden werden.

Dazu werden Breakpoints in den Quellcode an den problematischen Zeilen hinterlegt. Wird die Anwendung nun im Debug-Modus ausgeführt und zur Laufzeit ein solcher Breakpoint angelaufen, stoppt der Debugger die App automatisch. Anschließend kann mit seiner Hilfe in Ruhe nach dem Auslöser des Programmfehlers gesucht werden.

Wie das Debugging in Verbindung mit dem Stack Trace im Detail erfolgt, werden wir in Kapitel 4 dieser Lektion noch ausführlicher behandeln.

2. Den Debugging-Code in unsere Android App einfügen

Nachdem wir nun den Debugger von Android Studio im theoretischen Teil etwas näher kennengelernt haben, möchten wir ihn als Nächstes für das Debugging unserer eigenen App in der Praxis einsetzen. Dazu müssen wir den Quellcode unserer App um einige Zeilen Debugging-Code erweitern.

Um die notwendigen Änderungen durchzuführen, öffnen wir die Klassendatei MainActivity.java im Editor von Android Studio. Dazu klicken wir doppelt auf ihren Dateinamen im Project Tool Window. Die Klassendatei befindet sich im Package-Ordner de.codeyourapp.zitate unseres Projekts.

Dem bisherigen Quellcode fügen wir die Methode runDebugExampleCode() einschließlich ihrem Aufruf hinzu. Der bereits überarbeitet Quellcode der MainActivity.java ist unten aufgeführt. Die neu eingefügten Zeilen sind grau markiert:

MainActivity.java

package de.codeyourapp.zitate;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    public static final String LOG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        sendLogMessages();
        runDebugExampleCode();

        setContentView(R.layout.activity_main);
    }

    private void runDebugExampleCode() {
        int a = 0;

        for (int i = 1; i < 5; i++) {
            a = i + a;
            Log.v(LOG, "Schleifendurchlauf: " + i + ", a = " + a);
        }
    }

    private void sendLogMessages() {
        Log.v(LOG, "Verbose     - Meldung.");
        Log.d(LOG, "Debug       - Meldung.");
        Log.i(LOG, "Information - Meldung.");
        Log.w(LOG, "Warning     - Meldung.");
        Log.e(LOG, "Error       - Meldung.");
    }
}

Die vorgenommenen Änderungen am Quellcode sind schnell gemacht. In Zeile 16 rufen wir die neue Methode runDebugExampleCode() auf. Dies geschieht nachdem die fünf Log-Meldungen von unserer App ausgegeben wurden.

Die neue Methode runDebugExampleCode() wird in den Zeilen 21-28 definiert. Sie beinhaltet Beispiel-Quellcode, der für das durchzuführende Debugging sehr aufschlussreich ist. Zuerst legen wir die Variable a an und weisen ihr den Wert 0 zu. Anschließend definieren wir eine for-Schleife, die viermal durchlaufen wird. In dem Schleifenkörper erhöhen wir den Wert der Variable a und lassen eine Log-Meldung, die über den gerade erfolgten Schleifendurchlauf informiert, ausgeben.

In Android Studio sollte die MainActivity.java Klassendatei nun wie folgt aussehen:

debugging_example_code

Die Klassendatei MainActivity.java mit dem eingefügten Debugging-Code

Mit der ersten blauen Markierung (A) rufen wir die neu angelegte Methode runDebugExampleCode() auf. Dieser Methodenaufruf findet in der onCreate() Methode statt, also direkt beim Erzeugen der Activity vom Android System.

Die Definition der neuen Methode runDebugExampleCode() findet weiter unten statt (Markierung B). Die neue Methode enthält keinen für die App nützlichen Code. Der in ihr enthaltene Quellcode dient ausschließlich Debugging-Zwecken, wie wir in den nächsten Kapiteln noch sehen werden.

2.1 Ausführen unserer App auf dem Android Virtual Device

Jetzt haben wir alle notwendigen Änderungen an den Projektdateien vorgenommen. Unsere App gibt von nun an bei jedem Start neben den fünf Log-Meldungen zusätzlich noch vier weitere aus.

Um die am Quellcode vorgenommenen Änderungen zu überprüfen und die zusätzlich ausgegebenen Log-Meldungen zu analysieren, werden wir unsere App auf dem in Lektion 3.4 angelegten Android Virtual Device im Emulator ausführen lassen. Dadurch sollten bei jedem Leser die Log-Meldungen in dem gleichen Ausgabeformat zu sehen sein.

Unsere App starten wir dazu wie gewohnt über den Run > Run 'app' Menüeintrag, den wir über die obere Menüleiste erreichen.

In der unteren Abbildung ist unsere auf dem AVD ausgeführte Android App dargestellt. Wie zu erkennen ist, hat sich die Benutzeroberfläche der App nicht verändert. Das Aussehen unserer App ist also gleichgeblieben.

screenshot_app_ok

Die Benutzeroberfläche unserer Android App wurde durch den Debugging-Code nicht verändert

Wir möchten nun noch das Verhalten unserer App kontrollieren. Dazu werden wir im Logcat Tool Window die von der Anwendung ausgegebenen Log-Meldungen überprüfen. Damit dies möglich ist, muss die App weiterhin im Emulator ausgeführt werden.

Wir öffnen das Logcat Tool Window über den Eintrag View > Tool Windows > Logcat in der oberen Menüleiste. Anschließend klappt der Logcat am unteren Bildschirmrand nach oben auf. In ihm werden uns eine Vielzahl an Log-Meldungen angezeigt. Um die Meldungen unserer App anzuzeigen, werden wir nun nach den Log-Meldungen suchen, die den Suchbegriff MainActivity enthalten.

Um nach den Log-Meldungen unserer Android App zu suchen, führen wir folgenden Schritte aus:

  1. In der Drop-Down Liste stellen wir die Prioritätsstufe auf Verbose ein.
  2. In das anschließende Suchfeld geben wir MainActivity als Suchbegriff ein.
  3. Für die hintere Drop-Down Liste stellen wir sicher, dass der Eintrag No Filters ausgewählt ist.
debugging_log_meldungen

Mit dem Logcat Tool Window kontrollieren wir die ausgegebenen Log-Meldungen

In der oberen Abbildung sind die neun Log-Meldungen unserer Android App zu sehen. Alle Meldungen werden wie erwartet ausgegeben. In den vier neuen Log-Meldungen (Markierung A) wird der Wert der Variable a für jeden Schleifendurchlauf ausgegeben. Diese Information können wir später für das Debugging verwenden.

3. Debuggen unserer App in Android Studio

Nachdem wir den Quelltext unserer Android App um einige Zeilen Debugging-Code erweitert haben, sind jetzt alle Vorkehrungen getroffen, um mit dem Debugging unserer Anwendung zu beginnen. Wir sind nun bereit den Debugger von Android Studio in der Praxis einzusetzen und mit ihm die inneren Zustände unserer App zu analysieren.

Bei dem Debugging unserer Android App werden wir uns an folgenden Ablauf halten:

  1. Breakpoints in den Quellcode einfügen
  2. Die Android App im Debug-Modus ausführen
  3. Das Verhalten unserer App im Debugger analysieren
  4. Mit Watches den Zustand von Variablen überwachen
  5. Bedingungen für Breakpoints festlegen

3.1 Breakpoints in den Quellcode einfügen

Android Studio unterstützt mehrer Arten von Breakpoints (Haltepunkten) die unterschiedliche Aktionen auslösen. Am häufigsten werden die sog. Line Breakpoints (Zeilen-Haltepunkte) verwendet. Durch sie wird die Ausführung der Anwendung an einer bestimmten Zeile im Code pausiert, an der ein solcher Line Breakpoint gesetzt wurde.

Während die App auf diese Weise angehalten ist, könne die Zustände der sichtbaren Variablen im Debug Tool Window und auch direkt im Quelltext untersucht werden. Anschließend kann die Anwendung Zeile für Zeile fortgesetzt oder wieder zum normalen Programmablauf übergegangen werden.

Neben den Line Breakpoints können noch Haltepunkte für ganze Methode gesetzt werden. Diese sog. Method Breakpoints lassen die Anwendung beim Betreten und Verlassen der Methode pausieren. Sie führen jedoch zu einer spürbaren Verlangsamung des Debugging und werden daher seltener eingesetzt. Wir werden uns in dieser Lektion ausschließlich mit den Line Breakpoints beschäftigen und bezeichnen diese von nun an als Breakpoints.

Das Einfügen von Breakpoints in den Quellcode unserer Android App geschieht sehr einfach. Durch Klicken mit der linken Maustaste auf den freien Bereich direkt neben einer Zeilennummer im Editorfenster wird ein Breakpoint an die entsprechende Zeile geheftet.

Hinweis: Um in Android Studio die Zeilennummern im Editorfenster anzeigen zu lassen, muss auf den grauen Bereich links neben dem Quelltext mit der rechten Maustaste geklickt und im sich öffnenden Kontext-Menü der Eintrag Show Line Numbers aktiviert werden.

Wir werden nun zwei Breakpoints in den Quellcode der MainActivity.java Klassendatei, wie in der unteren Abbildung dargestellt, einfügen:

debug_breakpoints

Mit einem Linksklick neben der Zeilennummer wird für die entsprechende Zeile ein Breakpoint (Haltepunkt) angelegt

Dazu klicken wir mit der linken Maustaste direkt neben die Zeilennummern 15 und 25. Es erscheinen umgehend zwei rote Punkte direkt rechts neben den beiden angeklickten Zeilennummern. Diese roten Punkte symbolisieren die eingefügten Breakpoints. Zudem sind Zeilen, die mit Haltepunkte versehen sind, rot markiert. Dadurch kann man beim Durchsehen des Quelltextes sofort erkennen, an welchen Stellen im Code Breakpoints vorhanden sind.

Es ist auch möglich sich alle im Android Projekt vorhandenen Breakpoints anzeigen zu lassen. Dafür stellt Android Studio das Favorites Tool Window bereit. In diesem Tool Window werden neben Lesezeichen (Bookmarks), die man frei im Quellcode platzieren kann, auch die hinzugefügten Breakpoints in Listenform dargestellt.

Das Favorites Tool Window öffnen wir nun folgendermaßen:

  1. Mit der Maus auf den Menüeintrag View in der oberen Menüleiste klicken.
  2. Anschließend auf Tool Windows klicken.
  3. Und schließlich auf Favorites klicken, um das Favorites Tool Window zu öffnen.
debug_menu_favorites

Über die obere Menüleiste öffnen wir das Favorites Tool Window

Nachdem wir auf den entsprechenden Menüeintrag geklickt haben, klappt das Favorites Tool Window am linken Bildschirmrand auf. Es teilt sich diesen Bereich mit dem Project Tool Window, wenn beide gleichzeitig aufgeklappt sind. In unserem Fall ist nur das Favorites Tool Window aufgeklappt.

Zu Beginn werden die vorhandenen Breakpoints noch nicht vollständig angezeigt. Dies ändern wir, indem wir auf das kleine graue Dreieck vor dem Eintrag Breakpoints in dem Favorites Tool Window klicken. Dadurch klappen alle im Projekt vorhandenen Breakpoints, so wie in der unteren Abbildung dargestellt, auf:

debug_favorites_tool_window

In dem Favorites Tool Window werden die gesetzten Breakpoints angezeigt

Wie in der oberen Abbildung zu erkennen ist, werden in dem Favorites Tool Window (Markierung A) unsere beiden hinzugefügten Breakpoints mit aufgelistet. Für jeden Breakpoint wird die Klassendatei angegeben, in der sich dieser befindet. Auch die zugehörigen Zeilennummern sind mit aufgeführt (Markierung B).

Durch Doppelklicken auf einen der beiden Breakpoint-Einträge im Favorites Tool Window kann direkt zu der Zeile im Quellcode, in welcher sich der jeweilige Breakpoint befindet, gesprungen werden.

3.2 Die Android App im Debug-Modus ausführen

Damit unsere beiden nun eingefügten Breakpoints auch angelaufen werden, muss die Android App im Debug-Modus ausgeführt werden. Wird die Anwendung im normalen Run-Modus ausgeführt, werden keine Breakpoints angelaufen und die Ausführung der App wird zu keinem Zeitpunkt pausiert.

Wir werden nun unsere Android App im Debug-Modus starten. Dazu führen wir die folgenden Schritte aus:

  1. Unser Hardware-beschleunigtes AVD Nexus 9 (Markierung A) auswählen.
  2. Mit der Maus auf den Menüeintrag Run in der oberen Menüleiste klicken.
  3. Anschließend auf Debug 'app' klicken.
debug_app_target

Über die obere Menüleiste lassen wir unsere App im Debug-Modus ausführen

Ab Android Studio 3.5 wird nun das AVD, auf dem unsere App ausgeführt werden soll, automatisch im Android Emulator gestartet. Bei älteren Android Studio Versionen erfolgte noch folgender Zwischenschritt, der in der unteren Akkordion-Box beschrieben wird.

Nun wird die Android App auf unserem erstellten AVD installiert und anschließend automatisch von Android Studio im Debug-Modus gestartet. Unsere App wird zwar wie sonst im Emulator-Fenster ausgeführt, jedoch sind noch nicht alle Elemente der grafischen Benutzeroberfläche vollständig gezeichnet worden.

Dies liegt daran, dass die Anwendung gleich zu Beginn vom Debugger angehalten wurde. Und zwar an der Stelle des ersten Breakpoints in der onCreate() Methode. Zu diesem Zeitpunkt wurde der Bildschirminhalt, das Layout unserer App, noch nicht vom Android System gezeichnet.

In der unteren Abbildung ist unsere am ersten Breakpoint angehaltene App in Android Studio zu sehen:

debug_modus

Unsere App wird im Debug-Modus ausgeführt und ist momentan am Breakpoint in Zeile 15 angehalten worden

Wie bereits erwähnt, wurde die Ausführung unserer App am ersten Breakpoint, in Zeile 15, unterbrochen. Im Editorfenster von Android Studio ist die betroffene Zeile blau markiert. Auch der rote Punkt, welcher den Breakpoint symbolisiert, besitzt nun im Inneren einen kleinen Haken (Markierung A).

Die Stelle im Code an der unsere Anwendung gerade pausiert, wird uns auch im Frames-Bereich des Debug Tool Windows angezeigt. Dort ist an der obersten Stelle des Aufrufstapels (Call Stack) die zuletzt aufgerufene Methode (Markierung B) aufgeführt. Auch die Zeilennummer, an der die Ausführung der Anwendung in dieser Methode angehalten wurde, ist hinter dem Methodennamen angegeben.

Wir werden im nächsten Abschnitt die Bedienelemente des Debuggers kennenlernen. Dazu werden wir mit der App im angehaltenen Zustand weiterarbeiten. Die Abschnitte 3.2 und 3.3 bilden somit eine Einheit und sollten daher an einem Stück abgearbeitet werden.

3.3 Das Verhalten unserer App im Debugger analysieren

Unsere Android App ist nun am ersten Breakpoint in Zeile 15, wie in der letzten Abbildung des vorherigen Abschnitts dargestellt, pausiert. Wir werden jetzt von diesem App-Zustand aus mit dem Debugging beginnen.

Zunächst möchten wir uns mit den wichtigsten Bedienelementen des Debuggers von Android Studio der Reihe nach vertraut machen. Wie bereits in Abschnitt 1.2 beschrieben, befinden sich die Buttons für einige sehr wichtige Debugging-Funktionen in den beiden Menüleisten am linken und oberen Rand des Debug Window Tools.

Beginnen werden wir mit dem Step Into Button. Mit diesem Button ist es möglich Methoden zu betreten. Dazu muss die Anwendung an einer Zeile angehalten sein, in der sich ein Methodenaufruf befindet. Durch Klick des Step Into Buttons springt dann der Debugger in die zugehörige Methode hinein.

Wir klicken nun mit der linken Maustaste auf den Step Into Button (Markierung A):

debug_step_into

Die App ist vor dem Methodenaufruf sendLogMessages() pausiert. Mit Klick auf Step Into wird in die Methode gesprungen.

Da unsere App momentan am ersten Breakpoint in Zeile 15 angehalten ist, wird durch den Step Into Button direkt in die Methode sendLogMessages() hinein gesprungen. Und zwar in die Zeile, in der sich die erste Anweisung befindet. In der unteren Abbildung ist dies dargestellt (Markierung B).

Als Nächstes klicken wir auf den Step Out Button (Markierung C), um wieder aus der Methode sendLogMessages() herauszuspringen:

debug_step_out

Wir sind zu der Methode sendLogMessages() gesprungen, aus der wir mit Step Out wieder heraus springen

Der Klick auf den Step Out Button bewirkt, dass alle Anweisungen der sendLogMessages() Methode ausgeführt werden und anschließend aus der Methode herausgesprungen wird. Die Anwendung wird dann an der Rücksprungstelle wieder angehalten. In unserem Fall ist das die Zeile 16, direkt nach dem erfolgten Aufruf der sendLogMessages() Methode und vor dem Methodenaufruf runDebugExampleCode(). Dies ist in der unteren Abbildung dargestellt (Markierung D).

Wir klicken nun auf den Step Over Button (Markierung E), um über die Zeile 16 und somit den Methodenaufruf runDebugExampleCode() zu springen:

debug_step_over

Wir springen mit dem Step Over Button über den Methodenaufruf runDebugExampleCode()

Da wir auf den Step Over Button geklickt haben, wäre zu erwarten gewesen, dass die Methode runDebugExampleCode() komplett übersprungen und die Anwendung in Zeile 18 wieder angehalten wird. Dies ist aber nicht der Fall. Stattdessen wird unsere App mitten in der runDebugExampleCode() Methode pausiert und zwar in Zeile 25.

Der Grund für dieses unerwartete Debugging-Verhalten ist der zweite Breakpoint, der sich in Zeile 25 befindet und plötzlich beim Ausführen der App erreicht wurde. In der unteren Abbildung ist dies zu erkennen (Markierung F):

debug_variables

Die App ist nun am zweiten Breakpoint angehalten worden. Die Werte der Variablen werden direkt im Quellcode angezeigt.

Wie in der oberen Abbildung zu erkennen ist, wurde die App aufgrund des Breakpoints in Zeile 25 vom Debugger angehalten. Zu diesem Zeitpunkt sind die beiden Variablen a und i bereits deklariert und initialisiert. Der Debugger kann daher auch die momentan in beiden Variablen gehaltenen Werte direkt im Quellcode anzeigen (Markierung G).

Da diese beiden Variablen nun sichtbar (im Scope) sind, wurden sie automatisch dem Variables-Bereich des Debug Tool Windows hinzugefügt (Markierung H). Auch die in ihnen gespeicherten Werte werden direkt angezeigt, da beide Variablen von primitivem Datentyp sind.

Wir werden als Nächstes unsere App fortsetzen. Dazu klicken wir auf den Resume Button (Markierung I):

debug_resume

Durch Klick auf den Resume Button wird die App wieder fortgesetzt

Nun wird die Anwendung solange normal ausgeführt, bis erneut ein Breakpoint während der Ausführung erreicht wird. Dieser Fall tritt schon relativ schnell ein, nämlich bereits beim nächsten Schleifendurchlauf der for-Schleife. Wieder pausiert der Debugger unsere App in Zeile 25. Diesmal sind in den Variablen a und i jedoch andere Werte gespeichert (Markierung J in der unteren Abbildung).

Wir könnten nun noch mehrmals den Resume Button drücken und das Debugging solange fortsetzen, bis kein Breakpoint mehr während der Ausführung der Anwendung angelaufen wird. Dies wäre nach Verlassen der for-Schleife der Fall. Danach würde der Debugger in eine Art Wartemodus übergehen und erst wieder aktiv werden, wenn erneut ein Breakpoint angelaufen wird.

Wir werden aber an dieser Stelle den Debugger mit einem Klick auf den Stop Button (Markierung K) schließen:

debug_close

Schließen des Android Studio Debuggers durch Klick auf den Stop Button

Nun wird der Prozess unserer Anwendung von Android Studio beendet und daraufhin auch der Debugger. Das Debug Tool Window bleibt dabei aufgeklappt, jedoch wird unsere App auf dem Android Virtual Device geschlossen. Die Debugging-Sitzung ist nun vollständig beendet.

3.4 Mit Watches den Zustand von Variablen überwachen

Mit dem Variables-Bereich des Debug Tool Windows können Variablen untersucht werden. Welche Variablen in diesem Bereich angezeigt werden, ist von der momentanen Position im Quellcode abhängig. Denn aus dieser ergibt sich, welche Variablen gerade sichtbar sind und deren Werte daher ausgegeben werden können. Im Laufe einer Debugging-Sitzung ändern sich aus diesem Grund die im Variables-Bereich aufgeführten Einträge häufig.

Um bestimmte Variablen dauerhaft zu überwachen, müssen diese dem Watches-Bereich hinzugefügt werden. In diesem Bereich verweilen sie dann dauerhaft. Auch dann, wenn ihr Wert gerade nicht ausgelesen werden kann. Dem Watches-Bereich zugewiesene Variablen überdauern zudem die Debugging-Sitzung.

Wir werden nun zwei Variablen dem Watches-Bereich hinzufügen. Dazu lassen wir die App im Debug-Modus, wie in Abschnitt 3.2 beschrieben, ausführen. Anschließend klappt das Debug Tool Window am unteren Bildschirmrand auf und der Debugger stoppt die Ausführung unserer App in Zeile 15 am ersten Breakpoint.

Wenn in dem Debug Tool Window der Watches-Bereich nicht angezeigt wird, können wir dies leicht beheben. Dazu klicken wir auf den Restore 'Watches' View Button (Markierung A):

debug_watches

Falls der Watches-Bereich nicht angezeigt wird, kann man ihn über einen Button (Markierung A) anzeigen lassen

Als Nächstes klicken wir auf den Resume Button (Markierung B) und lassen dadurch unsere Anwendung bis zum zweiten Breakpoint ausführen. Der Debugger stoppt die Anwendung in Zeile 25. Zu diesem Ausführungszeitpunkt sind die beiden Variablen a und i sichtbar und werden daher auch im Variables-Bereich angezeigt.

Wir werden nun die beiden Variablen dem aktivierten Watches-Bereich (Markierung C untere Abbildung) hinzufügen. Dazu führen wir die folgenden Schritte durch:

  1. Mit der rechten Maustaste auf die Variable a im Variables-Bereich klicken (Markierung D).
  2. Es öffnet sich ein Kontext-Menü, in welchem wir auf den Eintrag Add to Watches klicken.
  3. Diese beiden Schritte wiederholen wir für die Variable i, um auch sie dem Watches-Bereich hinzuzufügen.
debug_add_watch

Über das Kontext-Menü des Variables-Bereichs können Watches schnell hinzugefügt werden

Die beiden Variablen sollten nun, wie in der unteren Abbildung (Markierung E) dargestellt, als Einträge des Watches-Bereichs erscheinen.

Als Nächstes klicken wir den Resume Button (Markierung F) und setzen die Anwendung wieder fort:

debug_two_watches

Dem Watches-Bereich des Debug Tool Windows wurden zwei Watches hinzugefügt

Es dauert nur einen Moment und der Debugger hält unsere App wieder an dem Breakpoint in Zeile 25 an. Diesmal haben sich die Werte der beiden Variablen a und i geändert. Dies wird uns sowohl in dem Variables-Bereich des Debug Tool Windows als auch in dem Watches-Bereich (Markierung G untere Abbildung) angezeigt.

Wir werden nun den Debugger mit einem Klick auf den Stop Button (Markierung H) schließen und dadurch auch die Debugging-Sitzung beenden:

debug_watches_close

Über die beiden Einträge in dem Watches-Bereich können wir nun die Variablen dauerhaft untersuchen

Nun wird der Prozess unserer Anwendung von Android Studio beendet und daraufhin auch der Debugger. Das Debug Tool Window bleibt dabei aufgeklappt, jedoch wird unsere App auf dem Android Virtual Device geschlossen. Die Debugging-Sitzung ist nun vollständig beendet.

Da Watches über die laufende Debugging-Sitzung hinaus bestehen bleiben, eignen sie sich hervorragend, um wichtige Variablen dauerhaft zu überwachen. Wir wollen nun prüfen, ob die beiden hinzugefügten Watches auch in einer neuen Debugging-Sitzung vorhanden sind und welche Werte sie annehmen.

Dazu lassen wir die App erneut im Debug-Modus, wie in Abschnitt 3.2 beschrieben, ausführen. Anschließend klappt das Debug Tool Window am unteren Bildschirmrand auf und der Debugger stoppt die Ausführung unserer App in Zeile 15 am ersten Breakpoint.

Die beiden Watches-Einträge a und i werden im Watches-Bereich angezeigt. Jedoch können für sie keine Werte ausgegeben werden, stattdessen wird der Text „Cannot find local variable a bzw. i“ angezeigt (Markierung I untere Abbildung). Dies liegt daran, dass an dieser Stelle im Quellcode beide Variablen nicht sichtbar sind.

Als Nächstes klicken wir den Resume Button (Markierung J) und setzen die Anwendung wieder fort:

debug_watches_not_initialized

Auch bei neuen Debugging-Sitzung bleiben die angelegten Watches-Einträge erhalten

Es dauert nur einen Moment und der Debugger hält unsere App an dem Breakpoint in Zeile 25 an. Da beide Variablen a und i an dieser Stelle im Quellcode sichtbar sind, werden für sie jetzt Zahlenwerte im Watches-Bereich (Markierung K untere Abbildung) angezeigt. Auf diese Weise kann man wichtige Variablen dauerhaft, sogar über mehrere Debugging-Sitzungen hinaus, überwachen.

Wir werden nun den Debugger mit einem Klick auf den Stop Button (Markierung L) schließen und dadurch die Debugging-Sitzung erneut beenden. Die beiden Watches-Einträge werden, wie wir gesehen haben, bestehen bleiben und stehen somit auch in späteren Debugging-Sitzungen zur Verfügung.

debug_watches_initialized

Sobald wir den Geltungsbereich (Scope) der Watches-Variablen betreten, werden ihre Werte angezeigt

Nun wird der Prozess unserer Anwendung von Android Studio beendet und daraufhin auch der Debugger. Die Debugging-Sitzung ist nun vollständig beendet.

3.5 Bedingungen für Breakpoints festlegen

Bisher haben wir nur Breakpoints kennengelernt, die zum Pausieren der Anwendung führen, sobald die Zeilen im Quellcode erreicht werden, an denen sie sich befinden. Android Studio stellt die Möglichkeit bereit, das Verhalten von Breakpoints anzupassen, so dass bestimmte Aktionen ausgeführt werden, wenn ein Breakpoint erreicht wird. Auch ist es möglich Bedingungen für Breakpoints vorzugeben, unter denen sie aktiv werden.

Das Konfigurieren und Festlegen von Bedingungen für Breakpoints wird in dem Breakpoints-Dialog von Android Studio durchgeführt. Wir werden nun mit Hilfe dieses Dialogs Bedingungen für einen unserer beiden Breakpoints festlegen.

Dazu öffnen wir den Breakpoints-Dialog folgendermaßen:

  1. Mit der Maus auf den Menüeintrag Run in der oberen Menüleiste klicken.
  2. Anschließend auf View Breakpoints… klicken.
debug_view_breakpoints_2b

Über die obere Menüleiste öffnen wir den Breakpoints-Dialog

Es öffnet sich der Breakpoints-Dialog, in dem die Einstellungen für jeden im Quellcode vorhandenen Breakpoint vorgenommen werden können. Auf der linken Seite des Dialogs wird ein Überblick über alle vorhandenen Breakpoints gegeben. Sobald einer der dort aufgeführten Breakpoints ausgewählt wird, werden seine Konfigurationsdetails auf der rechten Seite des Dialogs ausgegeben.

Wir werden nun mit Hilfe dieses Dialogs die Einstellungen unseres zweiten Breakpoints ändern. Und zwar soll der Breakpoint in Zeile 25 nur dann ausgelöst werden, wenn die Variable a einen Wert größer 4 besitzt.

Dazu führen wir die folgenden Schritte aus:

  1. Auf den Breakpoint MainActivity.java:25 mit der linken Maustaste klicken.
  2. Danach die Checkbox Condition anklicken und dadurch aktivieren.
  3. Anschließend in das darunter liegende Textfeld die Bedingung a > 4 eintragen.
  4. Und schließlich den Dialog mit einem Klick auf den Done Button bestätigen.
debug_breakpoints_dialog

Folgende Einstellungen werden für den Breakpoint in Zeile 25 vorgenommen

Durch Bestätigen des Breakpoints-Dialogs haben wir eine Bedingung für unseren zweiten Breakpoint festgelegt. Dieser wird nun nicht mehr jedes mal ausgelöst, wenn bei der Ausführung unserer App Zeile 25 im Quellcode erreicht wird. Stattdessen wir in solchen Situationen zusätzlich überprüft, ob die vorgegebene Bedingung (a > 4) erfüllt ist. Ist dies der Fall, dann wird die Anwendung pausiert und der Breakpoint ausgelöst.

Wir werden nun das Verhalten des neu konfigurierten Breakpoints überprüfen. Dazu lassen wir die App erneut im Debug-Modus, wie in Abschnitt 3.2 beschrieben, ausführen. Anschließend klappt das Debug Tool Window am unteren Bildschirmrand auf und der Debugger stoppt die Ausführung unserer App in Zeile 15 am ersten Breakpoint.

Dieses Debugging-Verhalten haben wir erwartet, da an den Einstellungen des ersten Breakpoints keine Änderungen vorgenommen wurden. Uns interessiert das Verhalten des zweiten Breakpoints vielmehr, da wir ihn mit einer Bedingung ausgestattet haben.

Als Nächstes klicken wir den Resume Button (Markierung A) und setzen die Anwendung wieder fort:

debug_condition_start

Der Debugger unterbricht die Ausführung unserer App wie erwartet am ersten Breakpoint

Es dauert nur einen kleinen Moment und der Debugger hält unsere App an dem zweiten Breakpoint in Zeile 25 an. Zu diesem Zeitpunkt sind beide Variablen a und i sichtbar, daher können wir ihre Werte sowohl im Variables– als auch im Watches-Bereich ablesen.

Wie anhand der unteren Abbildung zu erkennen ist, befinden wir uns bereits im vierten Schleifendurchlauf, da die Variable i den Wert 4 besitzt (Markierung B). Der Wert der Variable a beträgt 6, wodurch die Breakpoint-Bedingung a > 4 erfüllt ist, was zu dem Auslösen des zweiten Haltepunkts geführt hat.

debug_condition_end

Der Debugger unterbricht unsere App wegen der Bedingung a > 4 erst im vierten Schleifendurchlauf

Die grundlegenden Funktionen des Debuggers von Android Studio haben wir nun in der Praxis kennengelernt. Als Nächstes werden wir uns zur Kontrolle den Log-Output unserer Android App ausgeben lassen. Dies ist sehr einfach innerhalb des Debug Tool Windows mit Hilfe des Console-Bereichs möglich.

Wir klicken nun auf den Console-Tab (Markierung C oben bzw. D unten) und lassen uns dadurch die bis zum aktuellen Breakpoint ausgegebenen Log-Meldungen ausgeben:

debug_condition_console

Im Console-Bereich des Debug Tool Windows können die Log-Meldungen betrachtet komfortabel werden

Der Console-Bereich des Debug Tool Window ist ein sehr nützliches Werkzeug. Mit seiner Hilfe können die Log-Meldungen der eigenen Anwendung schnell und komfortabel analysiert werden. In ihm sind alle Meldungen dargestellt, die bis zum Erreichen des aktuellen Haltepunkts von der App ausgegeben wurden.

In der oberen Abbildung sind die acht bisher ausgegebenen Log-Meldungen (Markierung E) unserer Android App zu sehen. Wie zu erkennen ist, wurde die for-Schleife bereits dreimal vollständig durchlaufen. Wir befinden uns momentan im vierten Schleifendurchlauf am Conditional Breakpoint in Zeile 25.

Die Arbeit mit dem Debugger haben wir nun beendet und werden diesen jetzt schließen. Dazu klicken wir auf den Stop Button (Markierung F) des Debug Tool Window. Der Prozess unserer Anwendung wird nun beendet und die Debugging-Sitzung vollständig geschlossen.

4. Fehlerursachen mit Hilfe des Stack Trace finden

Auf dem Stack sind die aufgerufenen Methoden hinterlegt, die zu dem aktuellen Anwendungszustand geführt haben. Er enthält eine Liste von Methodenaufrufen, deren Rückverfolgung es erlaubt, den Pfad vom Start des Programms bis hin zum aktuellen Zustand zu erkennen. Diese Spur nennt man auch Stack Trace.

Wenn nun die eigene Anwendung aufgrund eines Programmfehlers zur Laufzeit abstürzt, kann man mit Hilfe des Stack Traces die Liste der aufgerufenen Methoden analysieren und dadurch die Stelle im Quellcode, die zu dem Fehler führte, ausfindig machen.

Wir werden nun absichtlich einen Programmfehler in unseren Quellcode einfügen, der zu einem Laufzeitfehler führt und dadurch unsere Android App abstürzen lässt. Anschließend werden wir den automatisch erstellten Stack Trace im Logcat Tool Window von Android Studio analysieren und mit seiner Hilfe die Aufrufkaskade, die zu dem Fehler führte, rekonstruieren.

Um die notwendige Änderung durchzuführen, öffnen wir die Klassendatei MainActivity.java im Editor von Android Studio. Dazu klicken wir doppelt auf ihren Dateinamen im Project Tool Window. Die Klassendatei befindet sich im Package-Ordner de.codeyourapp.zitate unseres Projekts.

Wir ändern nun den Ausdruck in Zeile 25 von a = i + a; zu a = i / a; um.

In dem unten aufgeführten Quellcode der MainActivity.java ist die Änderung bereits durchgeführt und die überarbeitete Zeile grau markiert worden:

MainActivity.java

package de.codeyourapp.zitate;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    public static final String LOG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        sendLogMessages();
        runDebugExampleCode();

        setContentView(R.layout.activity_main);
    }

    private void runDebugExampleCode() {
        int a = 0;

        for (int i = 1; i < 5; i++) {
            a = i / a;
            Log.v(LOG, "Schleifendurchlauf: " + i + ", a = " + a);
        }
    }

    private void sendLogMessages() {
        Log.v(LOG, "Verbose     - Meldung.");
        Log.d(LOG, "Debug       - Meldung.");
        Log.i(LOG, "Information - Meldung.");
        Log.w(LOG, "Warning     - Meldung.");
        Log.e(LOG, "Error       - Meldung.");
    }
}

Durch die Änderung in Zeile 25 lassen wir eine Division durch null durchführen. Eine Division durch null löst auf praktisch allen Rechnern einen Laufzeitfehler (eine Ausnahme) vom Typ divide by zero aus und führt zum Programmabsturz. Wir führen also auf diese Weise absichtlich den Crash unserer Android App herbei.

In Android Studio sollte die MainActivity.java Klassendatei nun wie folgt aussehen:

stack_trace_code

Die Klassendatei MainActivity.java mit dem überarbeiteten Quellcode

Als Nächstes werden wir unsere App normal, also nicht im Debug-Modus ausführen lassen. Die beiden, in dem vorherigen Kapitel angelegten, Breakpoints werden nun nicht mehr benötigt und sollten daher entfernt werden. Um sie zu löschen, klicken wir nun mit der linken Maustaste auf den jeweiligen Breakpoint.

Jetzt lassen wir unsere App, wie in Abschnitt 2.1 beschrieben, ausführen. Dazu starten wir die App über die obere Menüleiste, indem wir auf Run > Run 'app' klicken. Sobald unsere Anwendung gestartet wurde, tritt der einprogrammierte Laufzeitfehler ein und die App stürzt ab.

Als Resultat erhalten wir folgende Meldung auf dem Android Gerät angezeigt:

screenshot_app_crash

Der einprogrammierte Fehler bringt unsere Anwendung direkt nach dem Start zum Absturz

Wir wechseln nun zurück zu Android Studio und öffnen das Logcat Tool Window über die obere Menüleiste, indem wir auf den Menüeintrag View > Tool Windows > Logcat klicken.

In dem Logcat Tool Window möchten wir nun nach der Fehlermeldung, dem Stack Trace, suchen. Momentan werden uns von logcat noch zu viele Log-Meldungen angezeigt, daher werden wir das Suchfeld nutzen, um damit nach dem Stack Trace zu suchen.

Um nach dem Stack Trace im Logcat Tool Window zu suchen, führen wir folgenden Schritte aus:

  1. In der Drop-Down Liste stellen wir die Prioritätsstufe auf Verbose ein.
  2. In das anschließende Suchfeld geben wir fatal als Suchbegriff ein.
  3. Für die hintere Drop-Down Liste stellen wir sicher, dass der Eintrag Show only selected application ausgewählt ist.
stack_trace_logcat

Mit dem Suchbegriff „fatal“ suchen wir im Logcat Tool Window nach dem Stack Trace

Als Ergebnis der Suchanfrage wird uns eine sehr lange Log-Meldung in roter Schrift zurückgegeben. Diese Meldung wird als Stack Trace bezeichnet und enthält alle Informationen, die wir benötigen, um die Position des Programmfehlers im Quellcode zu lokalisieren.

In der ersten Zeile werden wir darüber informiert, dass es sich bei dem Programmfehler um einen fatalen Laufzeitfehler (Ausnahme) handelt, der von unserer eigenen Android App verursacht wurde. Uns interessiert besonder die Ursache, die zu dem Programmabsturz führte. Diese ist mit den Worten Caused by: angegeben, welche sich weiter unten im Text (Markierung B obere Abbildung) befindet.

In der unteren Abbildung ist die Meldung über die Absturzursache (Markierung C) dargestellt:

stack_trace_cause

Die Ursache des Programmabsturzes ist im Stack Trace mit den Worten „Caused by“ angegeben

Wie in der oberen Abbildung zu erkennen ist, wurde eine ArithmeticException wegen divide by zero (Division durch null) ausgelöst. In der direkt darauf folgenden Zeile ist die exakte Position im Quellcode angegeben, die für diesen Laufzeitfehler verantwortlich war.

Und zwar kam es in der Methode runDebugExampleCode() der MainActivity Klasse unserer App in Zeile 25 zu der Division durch null. Wir können mit Hilfe der Verlinkung direkt zu der besagten Zeile in unserem Quellcode springen. Dazu klicken wir mit der linken Maustaste auf den blauen Link (Markierung D), wie in der oberen Abbildung dargestellt.

Wir beheben nun den Programmfehler, indem wir den Ausdruck a = i / a; durch a = i + a; in Zeile 25 ersetzen.

In Android Studio sollte die MainActivity.java Klassendatei nun wie folgt aussehen:

stack_trace_code_end

Die Klassendatei MainActivity.java mit dem jetzt korrigierten Quellcode

Der Fehler in Zeile 25, Division durch null, wurde nun von uns behoben (Markierung E). Somit ist unsere App jetzt wieder lauffähig und hoffentlich frei von Programmfehlern.

Wir lassen nun unsere App ein letztes Mal in dieser Lektion, wie in Abschnitt 2.1 beschrieben, ausführen. Dazu starten wir die App über die obere Menüleiste, indem wir auf Run > Run 'app' klicken. Diesmal sollte alles wie erwartet funktionieren und unsere Anwendung fehlerfrei ausgeführt werden.

Zusammenfassung

Wir haben in dieser Lektion einen Einblick in die Debugging-Möglichkeiten von Android Studio erhalten. Dazu haben wir im theoretischen Teil erfahren was die Begriffe Debugging und Stack Trace in der Entwicklung von Android Apps bedeuten. Anschließend haben wir im praktischen Teil den Quellcode unserer App mit Hilfe des Debuggers von Android Studio analysiert.

Dabei haben wir folgende Debugging-Funktionen kennengelernt:

  1. Breakpoints in den Quellcode einfügen
  2. Die Android App im Debug-Modus ausführen
  3. Das Verhalten unserer App im Debugger analysieren
  4. Mit Watches den Zustand von Variablen überwachen
  5. Bedingungen für Breakpoints festlegen

Anschließend haben wir uns mit der Analyse des Stack Trace beschäftigt. Dazu haben wir einen Fehler absichtlich in den Quellcode unserer App einprogrammiert, welcher zu einer RuntimeException führte. Die Fehlerursache haben wir schließlich mit Hilfe des Stack Trace lokalisiert und behoben.

Somit sind wir am Ende dieses Themenblocks angelangt. Wir haben in den letzten drei Lektionen viel über Logging, Refactoring und Debugging erfahren und sollten nun in der Lage sein, das Verhalten unserer Anwendung mit Hilfe von Log-Meldungen zu überwachen und Programmfehler selbständig zu lokalisieren und beheben.

Diese etwas trockenen Themen lassen wir nun hinter uns und rücken die Praxis, den Ausbau unserer Android App, wieder in den Fokus. In der nächsten Lektion werden wir dafür den nächsten Schritt tun und unsere App um einen ListView erweitern. Ein ListView-Objekt wird in Android zum effektiven Darstellen einer beliebig langen Liste gleicher Elemente verwendet. Wir werden den ListView nutzen, um durch ihn Zitate deutscher Lyriker darstellen zu lassen.

Weiterführende Literatur




Diese Vorschau-Lektion endet an dieser Stelle. Wir hoffen, dass du nun einen kleinen Einblick in unser Android Online-Kurs Gesamtpaket erhalten konntest und es uns gelungen ist, dein Interesse für die Entwicklung von Android Apps zu wecken.

Wir haben noch weitere Lektionen unseres Android Online-Kurs Gesamtpakets für die Vorschau freigegeben. In der unteren Liste sind alle frei zugänglichen Lektionen aufgeführt:

Vorschau-Lektionen des Hauptkurses: Der große Android Apps Programmieren Online-Kurs:

Vorschau-Lektionen des Spezialkurses: Der SQLite Datenbank App Programmieren Online-Kurs:

Haben wir dein Interesse wecken können? Dann kannst du noch heute mit dem Programmieren deiner eigenen Android Apps beginnen.

Durch den Kauf erhältst du unbegrenzten Zugang zu allen 43 Lektionen unseres Android Online-Kurs Gesamtpakets. Wir werden in Zukunft weitere Lektionen hinzufügen. Auch auf alle zukünftigen Lektionen erhältst du vollen Zugriff.

Einmal kaufen für 29,95 €* und dadurch zeitlich unbegrenzten Zugriff auf alle Inhalte unserer Android Online-Kurse erhalten.