Marc Neumann

May 192011
 

Hier kurz für Euch und mich mein Vorgehen zur Performance Analyse von Android Apps. Es geht darum relevante Stellen im Code zu finden, deren Optimierung sich lohnt. Zwei Wege: Analyse über (1) Memory Allocations und (2) Ausführungszeit der Methoden .. alles basierend auf Eclipse.

Memory Allocation

  1. App in Eclipse mittels Android Development Tools (ADT) starten.
  2. App in den Zustand versetzen ab der die Untersuchung relevant ist (bei mir: in Spielewelt einloggen)
  3. In Eclipse-Perspektive DDMS wechseln.
  4. Den Prozess im Emulator / Device auswählen, der Eurer App entspricht.
  5. Auf den Reiter “Allocation Tracking” wechseln
  6. Button “Start Tracking” klicken
    image
  7. Abwarten bis aus Eurer Sicht genug Daten gesammelt sind (bei mir ein paar Animation-Frames)
  8. “Get Allocations” Button klicken um die gesammelten Informationen anzuzeigen
  9. Interessant für Optimierungen sind für mich häufig auftretende Allokation an denselben Code-Stellen. Dazu die Tabelle nach “Allocated In” sortieren. Für jede Allokation kann im unteren Teil der View der Stack-Trace betrachtet werden um die verantwortliche Stelle im eigenen Code zu identifizieren.
    image

Anhand dieser Analysen habe ich Stellen identifiziert in den z.B. unnötig Collection-Objekte erzeugt wurden. Diese habe ich durch Caching der verwendeten Collection in Instanzvariablen des längerlebigen Objekts optimiert (Vermeidung von Allokationen).

Ausführungszeiten von Methoden

Analog zu den Speicherallokationen können die Ausführungszeiten – genauer der Anteil der Ausführungszeit einzelner Methoden an der Gesamt-Ausführungszeit der App – analysiert werden. Effizienter Code für Android Apps wie Spiele ist zum Einen essentiell für eine akzeptable Frame-Rate und zum Anderen für die Optimierung des Batterieverbrauchs. Wer schon mal Angry Birds gespielt und danach Kopfschüttelnd zum Ladekabel gegriffen hat, kann das sicher nachvollziehen.

Die Infos zu Ausführungszeiten schreibt Emulator / Device auf die SD-Card. Daher muss das “AndroidManifest.xml” die entsprechende Permission anfordern:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Um die Analyse durchzuführen zunächst die Schritte 1 – 4 von oben wie bei Memory Allocations ausführen, dann

5. “Start Method Profiling” Icon-Button klicken
image
6. Warten bis genug Daten gesammelt sind (ein paar Frames in meinem Spiel)
7. “Stop Method Profiling” Icon-Button klicken
8. Trace-File vom Emulator / Device herunterladen
image

9. Trace-File mittels Trace-View-Tool des Android SDK (liegt bei mir unter “D:\java\android-sdk-windows\tools\traceview.bat”) öffnen. Optimalerwise verknüpft Ihr die Endung “.trace” mit dem Trace-View-Tool in Windows. Dann sieht das Ergebnis in Trace-View wie folgt aus:
image

10. Die Prozent-Angaben der Spalte “Incl %” waren für mich relevant. In welchen, möglichst tief in der Aufrufhierarchie liegenden Methoden, wird sehr viel Zeit verbraucht? In einem ersten Schritt habe ich TreeMaps durch eine eigene Implementierung ersetzt, die für meine Zwecke effizienter ist. Ohne diese Analyse mit Trace-View fällt die Identifikation der optimierungsrelevanten Stellen schwer.

Fazit

Optimiert Eure Apps nicht ins Blaue hinein. Aufwand und Nutzen müssen in gesundem Verhältnis stehen. Mit den oben beschrieben Ansätzen lassen sich schnell Stellen für Optimierungspotential finden. Mindestens genauso wichtig ist die Erfolgsprüfung Eurer Optimierungsmaßnahmen und die Nachhaltung über automatisierte Tests.

Mar 192011
 

Gerade konfiguriere ich die Ausführung des Android Emulator mit Jenkins Continuous Integration. Mir hat gerade sehr geholfen, eine Fehlermeldung des Plugin über grepcode.com zu finden. Diese Site erlaubt die konfortable Navigation im Quellcode vieler Open-Source-Projekte. Sehr hilfreich.

Mar 072011
 

In Android teilt sich eine App in Activities auf. In der Android API fehlt die Möglichkeit eine App komplett zu beenden. Meine Lösung verwendet startActivityForResultonActivityResultsetResult und finish.

In meiner Tower Defense App durchläuft der Benutzer drei Activities: WelcomeActivity, WorldSelectActivity und WorldActivity = Spielen. Die Klassenhierarchie wie folgt:

Die im folgenden beschriebenen Methoden sind Bestandteil des Layer-Supertypes AbstractActivity. Der Nutzer drückt “quit” im Menu der WorldActivity, dann soll die aktuelle und alle vorher aufgerufenen Activities der App beendet werden.

  public boolean onOptionsItemSelected(final MenuItem _item) {
    switch (_item.getItemId()) {
      case R.id.menuEntryQuit:
        quitApp();
        return true;
      default:
    }
 
    return super.onOptionsItemSelected(_item);
  }

Beim Beenden der Activity muss ein entsprechender Result-Code gesetzt werden, …

  protected void quitApp() {
    setResult(RESULT_QUIT_APP);
    finish();
  }

…damit die aufrufende Activity das Beenden der App erkennt und sich ebenfalls beendet.

  protected void onActivityResult(final int _requestCode,
      final int _resultCode, final Intent _data) {
    if (_resultCode == RESULT_QUIT_APP) {
      quitApp();
    } else {
      super.onActivityResult(_requestCode, _resultCode, _data);
    }
  }

Obwohl die aufrufende Activity kein echtes Ergebnis der Folge-Activity erwartet, muss es die folgende Actvity in Erwartung eines Ergebnisses starten um das Result-Ergebnis RESULT_QUIT_APP über onActivityResult zu erhalten:

startActivityForResult(intent, REQUEST_WORLD);

Drückt also der Nutzer “quit” so beenden sich alle Activities im Stack der App. Dieser Blog-Post zerstört sich im Übrigen auch in 10 Sekunden selbst ;-)

Mar 042011
 

Es soll IT-Architekten geben, die in der Kernarbeitszeit tatsächlich länger am Stück arbeiten.  Kommt bei mir auch vor. Großprojekte mit vielen Kollegen sind sehr anregend für den eigenen Wissenshorizont, jedoch nur bedingt gut für die Konzentration.
Ich empfehle daher zur Begünstigung unterbrechungsfreien Arbeitens den 24h-Drum’n’Bass Radio-Stream Bassdrive.  Bei durchschnittlichen 175 bpm arbeite ich einfach schneller und konzentrierter. Bei mir läuft gerade Bassdrive auf Media-Monkey.
Und ja, in der restlichen Zeit gebrauchen IT-Architekten Ihr loses Mundwerk und zeichnen Pfeile, Kreise und Rechtecke auf Flipcharts in großen Meetingräumen.

Jan 242009
 

Um die Java-Klassen für WebService-Clients zu generieren empfiehlt sich das JAX-WS-Plugin für Maven. Dieses klinkt sich bei Verwendung von “wsimport” in die Build-Phase “generate-sources” ein.

Für mich ist es sinnvoll, die WSDL-Dateien des anzusprechenden Services direkt in “src/wsdl” meines Projekts abzulegen statt diese über eine URL einzubinden. Dies hat den Vorteil, dass ich Änderungen an der Schnittstelle des Service in der Subversion-Historie nachvollziehen kann. Muss mein Projekt mehrere Service ansprechen, so lege ich die WSDL- plus die Schema-Datei (XSD) jedes Service im genannten Verzeichnis ab.
Continue reading »

Nov 152008
 

Zuletzt habe ich über das Beschleunigen von lokalen JEE-Deployments geschrieben. Für das Beschleunigen von JEE-Deployments ist meiner Einschätzung nach zwingend das exploded Deployment notwendig. Dabei wird ein EAR oder WAR nicht als Archiv-Datei sondern als entpackte Verzeichnisstruktur deployt. Leider ist das Deployment von exploded EARs und WARs noch nicht durch die JEE-Spezifikation abgedeckt. Daher hat jeder JEE Container dabei so seine Eigenheiten. Die folgenden Zeilen liefern ein paar wichtige Informationen zum lokalen Deployment von explodierten EARs unter Glassfish V2.
Continue reading »

Oct 212008
 

Schneller, höher, weiter! Das Entwickeln von JEE-Applikationen ist bestimmt durch Round-Trips aus Code-Änderung, Kompilierung, Re-Deployen und Testen. Da sich diese Round-Trips in kurzen Abständen (Minutenbereich) wiederholen, sollte das Kompilieren und Re-Deployen so wenig Zeit möglich in Anspruch nehmen. Zum einen lässt das schlicht mehr Zeit zum Programmieren und Testen. Zum anderen macht das Arbeiten den Programmierern so einfach mehr Spaß! (… und die Zufriedenheit des Programmierers ist direkt proportional zur Lebensdauer seiner Tastatur).

Continue reading »

Oct 032008
 

Durch zeitgesteuerte Aktionen kann ein Softwaresysteme bestimmte Anforderungen umsetzen. Zum Beispiel könnte eine Anforderung sein, dass eine Applikation jede Nacht bestimmte Datenbereinigungen durchführt. Bei OpenWishes prüfen wir beispielsweise periodisch, ob Geburtstagserinnerungen per E-Mail versendet werden müssen. Dies sind natürlich nur triviale Beispiele. Der Faktor Zeit kann nicht nur Aktionen auslösen, sondern auch das Verhalten von Geschäftslogik beeinflußen. Beispielsweise kann der Kunde im Support-Portal nach Ablauf der Garantiezeit eines gekauften Artikels keinen Reklamationsvorgang mehr einleiten.

Continue reading »

Jul 182008
 

Domain Driven Design ist spätestens seit dem gleichnamigem Buch von Eric Evans in die Ruhmeshallen der Buzzwords eingegangen. DDD reiht sich ein neben Test Driven Development (TDD), Feature Driven Development und Model Driven Development (MDD). Bei so vielen Drivern frag ich mich, ob wir irgendwann einmal ans Ziel kommen. Spass beiseite – aus meiner Sicht sind das alles Paradigmen, die ihre Berechtigung haben und sogar kombiniert eingesetzt werden sollten.

Domain Driven Design zielt in großen Teilen auf Domänenmodelle ab. Aus meiner Erfahrung verwenden Domänenmodelle, die wirklich etwas auf sich halten, eigene atomare Domänentypen und pfeiffen auf die direkte Verwendung von String, Integer und Co. Dieser Schritt bringt viele Vorteile mit sich, muss jedoch am zusätzlichen Aufwand für die Erstellung der zuzsätzlichen Klassen gemessen werden. Natürlich muss die Notwendigkeit von atomaren Domänentypen aufgrund des Mehraufwands durchdacht und gut begründet sein. Bei der architekturellen Betreuung eines Systems bin ich den Weg der atomaren Domänentypen gegangen und möchte in den folgenden Zeilen ein wenig mit Weisheit prahlen sowie zur Diskussion anregen.

Continue reading »