Home

 

 

Richtig Fasches Finden


Ein Beitrag von Arno BeckerArno Becker

Neben dem klassischen Debuggen einer Android-Anwendung gibt es einige weitere Möglichkeiten, Fehler in Android-Programmen zu finden. Eine Kenntnis der richtigen Hilfsmittel und Tools erleichtert es, Fehler zu finden. Insbesondere auf echten Android-Geräten sind Fehler während der Laufzeit schwer zu finden.

Debuggen ist weit mehr, als Zeile für Zeile durch ein Programm zu gehen und sich Variablenwerte anzuschauen. Im Kontext mobiler Anwendungsentwicklung kommt beispielsweise hinzu, dass sich Emulator und die verschiedenen Geräte doch im Verhalten unterscheiden. Dieser Artikel stellt einige Tools, Hilfsmittel und Techniken vor, die bei der Fehlersuche helfen. Sowohl im Emulator, als auch auf dem Android-Gerät.

ADB – Android Debug Bridge

Die ADB (Android Debug Bridge) ist ein Programm im /tools-Verzeichnis des Android-SDK. Man arbeitet selten direkt mit der ADB. Sie stellt eine Verbindung zwischen dem Android-Plugin, welches man für die Android-Entwicklung in Eclipse eingebunden hat, und dem Emulator her.

Die ADB ermöglicht es unter anderem dem Debugger des JDK auf laufenden Programmcode im Emulator oder auf dem per USB angeschlossenen Android-Gerät zuzugreifen. Weitere Funktionen der ABD sind das Kopieren von Dateien auf und vom Gerät, das Installieren von Anwendungen, die Bereitstellung einer Remote-Shell auf dem Gerät und das Auslesen von System-Logausgaben. Über die Remote-Shell kann man Systemprogramme aufrufen, die auf dem Android-Gerät installiert sind. Beispiele dafür sind die SQLite-Shell und Monkey, ein Programm für Stresstests von Android-Anwendungen.

Das Android Plugin für Eclipse integriert die ADB in den Entwicklungsprozess. Viele Funktionen der ADB lassen sich bequem über den Dalvik Debug Monitor Server (DDMS) steuern (siehe Abbildung 1). Wir kommen später darauf zurück.

bild1-debugging

Abbildung 1 | Hilfreich bei Debuggen und Profilen: der DDMS

Klassisches Debugging

Grundsätzlich unterscheidet das Debugging bei Android nichts vom Debugging eines normalen Java-Programms, welches man unter Eclipse entwickelt.

Führt man einen Doppelklick auf dem grauen Rand links vom Quellcode aus, setzt man einen Breakpoint. Hier hält die JVM/DVM zur Laufzeit an und man kann zeilenweise durch den Programmcode laufen. Eclipse kann bei Erreichen eines Breakpoints automatisch in die Debug-Perspektive wechseln. Hier verwendet man die Taste F6, um von Programmzeile zu Programmzeile zu springen und die Taste F5, um bei Methodenaufrufen in die Methode hinabzusteigen. Mittels F8 setzt man das Programm fort. Eclipse-Neulinge sollten sich zunächst mit dem Debugger vertraut machen. Eine gute Einführung bietet [1].

Debugging auf einem Android-Gerät

Um direkt auf einem Android-Gerät zu debuggen, muss dieses per USB angeschlossen werden. Für Windows gibt es einen USB-Treiber, den man installieren muss. Er wird in Eclipse über den AVD-Manager (Window -> Android SDK and AVD Manager -> Available Packages) heruntergeladen und ist in der Liste unter dem Namen „Usb Driver package, revision 3“ zu finden. Nach dem Herunterladen befindet er sich im Ordner /usb_driver des Android-SDK..

Bei Linux als Betriebssystem muss eine Rules-Datei unter /etc/udev/rules.d erstellt werden. Hier Hilft die Google-Dokumentation weiter [2]. Mac-Besitzer müssen nichts tun.

Auf dem Android-Gerät ist unter Einstellungen -> Anwendungen -> Entwicklung ein Häkchen bei „USB-Debugging“ zu setzen. Zum Testen von Location Based Services sollte auch das Häkchen bei „Falsche Standorte“ gesetzt werden, da man während der Entwicklung oft in Gebäuden sitzt in denen man keinen GPS-Empfang hat.

Abschließend ist noch ein Eintrag im Android-Manifest vorzunehmen. Hier ist der <application>-Tag um das Attribut

android:debuggable="true"

zu erweitern. Dieses Flag sollte man wieder entfernen oder auf false setzen, bevor man die Anwendung signiert und veröffentlicht. Anschließend kann es losgehen. Startet man nun die Anwendung im Debug-Modus, erscheint ein Dialog, in dem man als Laufzeitumgebung der Anwendung den Emulator (wenn dieser vorher gestartet wurde) oder das Android-Gerät auswählen kann.

Debug-Ausgaben mittels LogCat

Android bringt einen eigenen Logging-Mechanismus mit. Allerdings ist er nicht so vielseitig konfigurierbar wie z.B. Log4J. Dieser Mechanismus heißt LogCat. Ihm liegt ein Ringpuffer zugrunde, der eine bestimmte Anzahl Logeinträge aufnehmen kann. Die ältesten werden nach einer Weile durch neue überschrieben. Das Eclipse-Plugin für Android bringt eine View mit (siehe Abbildung 2), mit der man die Einträge in der LogCat anschauen kann. Diese View ruft man über Window -> Show View -> Other… -> Android -> LogCat auf.

bild2-debugging

Abbildung 2 | Anzeige der Logeinträge in der Eclipse-View „LogCat“

Logeinträge generiert man im Programmcode mittels der Klasse android.util.Log.

Ein Logeintrag besteht aus einem Zeitstempel, einem Loglevel (Debug, Info, Warning etc.), der Prozess-Id, einem Tag und einer Message. Um im Programmcode Logeinträge zu generieren nutzt man statische Methoden des Klasse android.util.Log. Die Methodennamen zum Loggen lauten wie der Anfangsbuchstabe des Loglevels, also beispielsweise d für Debug-Level oder e für Error-Level. Als Parameter übergibt man einen Tag und die Lognachricht als String:

Log.e(TAG, "onSaveLoan(): View txtAmount enthält keinen Wert");

Der Parameter Tag dient später zum Filtern aller Logeinträge der LogCat. Er sollte als Konstante deklariert werden. Bewährt hat sich hier entweder den Namen der Komponente (Activity, Service etc.) oder den Namen der Anwendung zu verwenden. Da in der LogCat auch alle System-Logs und die Logeinträge aller anderen laufenden Anwendungen zusammenkommen, ist es hilfreich, hier sinnvolle Bezeichner zu wählen. Man kann beispielsweise den Klassennamen verwenden:

private static final String TAG="LoanForm";

Es hat sich bewährt, in der Logausgabe auch den Namen der Methode mit anzugeben. So behält man die Übersicht im Programmablauf und kann sofort sehen, wo die Logausgabe erzeugt wurde.

Hat man nun mittels der Klasse Log allerhand Logausgaben erzeugt, wirft man einen Blick in die LogCat-View (Abbildung 2).

Anhand des Tags kann man die Logeinträge filtern, so dass man nur die eigenen Logeinträge sieht. Zum Definieren eines Filters (siehe Abbildung 3) drückt man auf das grüne Pluszeichen („Create Filter“) in der Titelleiste der LogCat.

bild3-debugging

Abbildung 3 | Einen Filter für die LogCat definieren.

Leider kann man nur einen Tag angeben, nach dem man Filtern will. Bei „Log Level“ stellt man den Level ein, ab dem man Filtern will. Loglevel mit niedrigerer Priorität als der eingestellte Loglevel werden dann nicht berücksichtigt. Die Prioritäten sind folgende (aufsteigend):

V: Verbose
D: Debug
I: Info
W: Warning
E: Error
F: Fatal
S: Silent

Mit geeigneten Logausgaben kann man auch ohne Debugger gut den Programmablauf verfolgen und sich Exceptions ausgeben lassen. Die LogCat-View in Eclipse funktioniert natürlich auch, wenn die Anwendung auf einem per USB angeschlossenen Gerät läuft.

Will man später die Anwendung marktreif machen, sollte man unnötige Logausgaben beseitigen, da pro Aufruf einer Log-Methode intern mehrere Objekte angelegt werden, um die Logausgabe zu erzeugen. Dies geht auf Kosten des Speichers und der Performanz der Anwendung.

Dazu definiert man zentral eine Konstante, die den Compiler befähigt, die Logausgabe zu entfernen:

public static final boolean DEBUG = true;

Jede Ausgabe einer Log-Meldung sollte folgendermaßen implementiert werden:

if (DEBUG) Log.d(TAG, "…");

Setzt man die Konstante DEBUG auf false, überspringt der Compiler die so gekennzeichneten Zeilen und die Log-Ausgabe ist im compilierten Programmcode nicht enthalten. Man sollte zumindest für die Loglevel Verbose und Debug einen solchen Schalter verwenden. Ausgaben vom Level Warning, Error und Fatal sollten natürlich erhalten bleiben. Auf Info-Ausgaben bleiben normalerweise in der fertigen Anwendung, daher sollte man sie sparsam verwenden. Vor dem Ausliefern einer signierten Anwendung legt man dann den Schalter um und erhält die von unnötigen Logausgaben bereinigten compilierten Klassen.

DDMS – Dalvik Debug Monitor Server

Der DDMS ist eine Eclipse-Perspektive. Aufrufen kann man ihn über Window -> Open Perspektive -> Other… -> DDMS (siehe Abbildung 1).

Der DDMS bietet zahlreiche Möglichkeiten für Debugging, Profiling und Testen einer Anwendung. Wir betrachten hier nur die Bestandteile, die beim Debuggen helfen.

Im linken Panel mit dem Namen „Emulator Control“ gibt es zwei Möglichkeiten, die Anwendung zu Debuggen (siehe Abbildung 4).

bild4-debugging

Abbildung 4 | Hilfreiche Kontrollelemente zum Debuggen von Android-Anwendungen

Zum einen sind das die „Telephony Actions“. Hiermit kann man Sprachanrufe und SMS-Nachrichten simulieren. Es spielt keine Rolle, welche Telefonnummer man eingibt, der Anruf, bzw. die SMS landet immer auf dem Gerät oder dem Emulator, auf dem die Anwendung läuft.

Hat man eine Anwendung implementiert, die auf Sprachanrufe oder SMS reagiert, kann man ein solches Ereignis auslösen. Im Programmcode kann man einen Breakpoint setzen und auf Fehlersuche gehen.

Zum anderen gibt es die „Location Controls“. Hier gibt es verschiedene Möglichkeiten, das GPS-Modul zu simulieren. Am einfachsten geht dies, in dem man im Reiter „Manual“ den Längen und Breitengrad angibt und auf die Schaltfläche „Send“ drückt. Trotz englischer Beschriftung müssen die Zahlen auf einem Betriebssystem mit deutschen Spracheinstellungen mit Komma statt mit Dezimalpunkt eingegeben werden.

Unter dem Reiter „GPX“ und „KML“ kann man Dateien im entsprechenden Format hochladen. Diese Dateien können nahezu beliebig viele Ortspunkte enthalten. Die Dateien lassen sich abspielen, so dass die Anwendung laufend neue Ortspunkte aus der Datei empfängt. Leider ist die Abspielgeschwindigkeit schon in der langsamsten Stufe sehr hoch.

Auch hier lassen sich wieder Breakpoints in der Anwendung setzen und man kann unter realistischen Bedingungen das Verhalten der Anwendung untersuchen.

Ein weiterer Vorteil des DDMS ist, dass man auf das Dateisystem einer Anwendung zugreifen kann und Dateien auf das Gerät, bzw. von diesem herunterladen kann. Hierzu klickt man im rechten Panel des DDMS auf den Reiter „File Explorer“ (Abbildung 5).

bild5-debugging

Abbildung 5 | Dateien auf dem Gerät über den File Explorer verwalten

Nutzt also die Anwendung das Dateisystem, so kann man hierüber beispielsweise wechselnde Testdaten bereitstellen oder das Ergebnis von Speicheroperationen zur Analyse herunterladen.

Der Speicherort einer Anwendung für Dateien ist das Verzeichnis /data/data/package.name.der.anwendung. Hierunter (Ordner /databases) werden zum Beispiel auch Datenbanken abgelegt, die sich dank des File Explorers schnell austauschen lassen.

Alternativ steht natürlich die SD-Karte als Speicherort zur Verfügung, falls es eine gibt. Hier lautet das Verzeichnis im File Explorer /mnt/sdcard.

Rechts vom Reiter „File Explorer“ findet man die beiden Schaltflächen, mit denen sich Dateien herauf- und herunterladen lassen.

Debugger synchronisieren

Bisweilen tritt der Effekt auf, dass der Debugger nicht an einem Breakpoint hält. Will man beispielsweise in der Anwendung auf eingehende Telefonanrufe oder SMS reagieren, verwendet man einen Broadcast Receiver. Will man auf Veränderung der Ortsposition reagieren, hat man evtl. einen LocationListener in einem Service implementiert, der im Hintergrund läuft und nicht über eine Activity gestartet wurde.

In beiden Fällen ist der Programmeintrittspunkt der Anwendung keine Activity, sondern eine andere Komponente, nämlich ein Broadcast Receiver oder ein Service. Die ADB kann hier nicht ohne Hilfe den Programmablauf mit dem Debugger synchronisieren. Die Komponente muss warten, bis sich der Debugger mit ihr verbunden hat. Daher fügt man

Debug.waitForDebugger();

vor dem ersten Breakpoint in der Komponente ein. Dies ist wohlgemerkt nur notwendig, wenn diese Komponenten nicht über eine Activity aufgerufen wurden.

SQLite-Datenbanken debuggen

Verwendet man die SQLite-Datenbank zum Speichern von Daten, ist es manchmal praktisch, direkt auf der Datenbank zu arbeiten und nach fehlerhaften Datensätzen zu suchen. Dies geht schneller, als bei jedem Verdacht falscher oder fehlender Daten eine Testroutine zu schreiben.

Empfehlenswert ist das Plugin „Questoid SQLite Browser“ von Questoid [3]. Es handelt sich um ein Eclipse-Plugin, welches sich in die DDMS einklinkt.

Die Installation ist einfach. Das Plugin wird auf der Webseite als Jar-Datei angeboten. Man lädt es in den Plugin-Ordner der Android-Eclipse-IDE. Anschließend wird Eclipse neu gestartet und man hat im DDMS eine zusätzliche Schaltfläche, über die man eine Datenbankdatei im SQLite Browser öffnen kann (Abbildung 6).

Hat man eine Anwendung, die eine Datenbank angelegt hat, nutzt man den oben beschriebenen File Explorer des DDMS. Dort navigiert man zur Datenbankdatei wie oben beschrieben. Nachdem man die Datei mit der Maus angeklickt hat, drückt man die Schaltfläche des SQLite-Browsers („Open File in SQLite Browser…“. Hier stehen zwei Reiter zur Verfügung. Unter dem einen („Database Structure“) kann man sich die Tabellenstruktur anschauen. Der andere (siehe Abbildung 7) zeigt die Datensätze in einer Tabelle an. Die Tabelle lässt sich über eine Drop-Down-Box auswählen.

Das wahre Leben

Fehlerfrei Programmieren auf mobilen Geräten ist viel schwieriger, als es auf den ersten Blick erscheint. Dadurch, dass man sich oft mit dem Android-Gerät nicht an einem Ort aufhält oder sich in einem Gebäude befindet, kann man sich nicht auf stabile Netzwerkverbindungen oder ein durchgehendes GPS-Signal verlassen. Hinzu kommt, dass Android im Falle knapper Systemressourcen Anwendungen oder auch nur Teile davon beendet. Speicher ist auch oft knapp und dann ist auf einmal der Akku leer.

Eine Vielzahl von Fehlerquellen werden bei Tests gar nicht richtig berücksichtigt. Verwendet man die Anwendung dann fernab des Entwicklungsrechners, kommt es zu Fehlern oder sogar Programmabstürzen. Um diesen Fehlern auf die Spur zu kommen, empfiehlt es sich, eine Anwendung auf dem Gerät zu installieren, sie die LogCat ausliest und entweder anzeigt oder in eine Datei speichert.

So kann man eine Anwendung im praktischen Alltag testen und schwer zu findende Fehler provozieren (in Funklöcher gehen, sehr viele Apps gleichzeitig verwenden etc.). Tritt ein Fehler auf, hilft die LogCat beim nachträglichen Debuggen. Da die LogCat alle Log-Ausgaben aller Anwendungen enthält, wird man hier den Stack-Trace der eigenen Anwendung im Falle einer Exception finden, wenn man nicht zu lange mit der Auswertung wartet (es handelt sich ja um einen Ringpuffer).

Wichtig ist natürlich. Dass man in einer solchen Endgeräte-testphase möglichst viele Debug-Ausgaben generiert, die einem später helfen, den Fehler auch ohne Debugger zu finden. Denn meistens ist es schwierig, solche Fehler am Entwicklungsrechner nachzustellen.

Es gibt eine Reihe von Android-Programmen im Android Market, die die LogCat auslesen. Ein Projekt, bei dem auch der Quellcode verfügbar ist, ist aLogCat [4] von Jeffrey Blattman. Ein weiteres Beispiel aus dem Android Market, was ständig weiterentwickelt wird, ist CatLog von Nolan Lawson [5].

Fazit

Uns stehen bei Android viele Möglichkeiten zur Verfügung, um auf Fehlersuche zu gehen. Sowohl im Emulator, als auch auf dem per USB angeschlossenen Gerät. Selbst fernab des Entwicklungsrechners können wir noch auf Fehlersuche gehen, wenn wir passende Debugging-Software einsetzen. Dreh- und Angelpunkt während der Entwicklung am Rechner ist jedoch die ADB, die sehr gut über den DDMS in den Entwicklungsprozess integriert ist. Der DDMS bietet auch Möglichkeiten, Prozesse anzustoßen, die schwer zu simulieren sind. Durch das Versenden einer SMS oder durch das Simulieren eines GPS-Moduls lassen sich Komponenten wie Broadcast Receiver oder Services antriggern und mit Hilfe von Breakpoints debuggen.

Quellen:

[1] Lars Vogel: Debugging in Eclipse http://www.vogella.de/articles/EclipseDebugging/article.html
[2] Google Developer Guide http://developer.android.com/guide/developing/device.html#setting-up
[3] Questoid SQLite Browser http://www.questoid.net/Tools/QuestoidSQLiteBrowser.aspx
[4] aLogCat, LogCat-Reader http://code.google.com/p/alogcat/
[5] CatLog, LogCat-Reader http://www.androidpit.de/de/android/market/apps/app/com.nolanlawson.logcat/CatLog-Logcat-Reader