Gradle build times: practical fixes

Gradle Build Times: Praktische Fixes, die wir selbst anwenden

Es gibt diese Vormittage in unserem Büro im siebten Wiener Gemeindebezirk, an denen der Melange längst kalt geworden ist und das einzige Geräusch im Raum das Surren der Lüfter ist, während Android Studio wieder einmal einen Build durchzieht. Einer dieser Vormittage war es, an dem unser Team beschlossen hat, dass es so nicht weitergehen kann. Wenn die Build-Zeit länger dauert als die Kaffeepause, läuft etwas grundlegend falsch. Also haben wir uns hingesetzt, analysiert und systematisch optimiert – und genau diese praktischen Fixes wollen wir heute mit euch teilen.

Warum uns langsame Builds überhaupt stören sollten

Langsame Builds sind nicht nur lästig, sie kosten bares Geld und vor allem mentale Energie. In einer internen Umfrage in unserem Team haben wir festgestellt, dass jeder Entwickler durchschnittlich 17 Minuten pro Tag mit Warten auf Builds verbringt. Das klingt nach wenig, summiert sich aber auf über eine Stunde pro Woche – Zeit, in der man aus dem Flow gerissen wird und danach wieder mühsam hineinfinden muss. Google selbst empfiehlt in seinen Best Practices, Build-Zeiten unter zehn Minuten zu halten, idealerweise deutlich darunter. Alles darüber hinaus gilt als Produktivitätsbremse, die den gesamten Entwicklungszyklus ausbremst.

Der Flow-Killer Nummer eins im Entwickleralltag

Wer schon einmal mitten in einer komplexen Refactoring-Session gesteckt hat und dann drei Minuten auf einen inkrementellen Build warten musste, weiß genau, wovon wir sprechen. Der Zustand tiefer Konzentration – der berühmte Flow – ist nach so einer Unterbrechung dahin. Studien zeigen, dass es bis zu 23 Minuten dauern kann, um nach einer Unterbrechung wieder voll in eine Aufgabe einzutauchen. Bei mehreren Builds pro Stunde summiert sich das auf einen massiven Produktivitätsverlust, der in keiner Sprint-Planung abgebildet ist.

Wie sich 5 Minuten Wartezeit auf 2 Wochen Urlaub summieren

Rechnen wir das einmal konkret durch: Fünf Minuten Wartezeit pro Build, zehn Builds am Tag, an 220 Arbeitstagen im Jahr – das ergibt über 180 Stunden, die man vor einem Fortschrittsbalken verbringt. Das entspricht mehr als vier vollen Arbeitswochen, die man stattdessen in Features, Bugfixes oder eben in einen längeren Urlaub investieren könnte. Allein diese Zahl hat bei uns im Team für betretenes Schweigen gesorgt und den nötigen Handlungsdruck erzeugt.

Build Analyzer: Unser erster Blick in die Blackbox

Bevor man optimiert, muss man verstehen, wo die Zeit überhaupt hingeht. Der Build Analyzer in Android Studio war für uns der natürliche erste Schritt. In Android Studio Giraffe findet man ihn unter dem Menüpunkt “Build” und dann “Build Analyzer”. Das Tool zeigt nicht nur die Gesamtzeit an, sondern schlüsselt jeden Task einzeln auf und markiert besonders zeitfressende Vorgänge mit Warnsymbolen. Parallel dazu haben wir das Gradle Profiler Tool von JetBrains eingesetzt, das über die Kommandozeile läuft und noch detailliertere Flame Graphs generiert. Die Kombination aus beiden Werkzeugen hat uns in kürzester Zeit die größten Engpässe offengelegt.

So findest du den Build Analyzer in Android Studio Giraffe

In Android Studio Giraffe ist der Build Analyzer direkt in das Build Output-Fenster integriert. Nach jedem Build erscheint dort ein Link zur Analyse. Alternativ navigiert man über “View” zu “Tool Windows” und dann zu “Build”. Im Reiter “Build Analyzer” sieht man eine komplette Timeline des Build-Vorgangs, inklusive aller Tasks und deren Dauer. Besonders praktisch: Das Tool schlägt automatisch Optimierungen vor, etwa wenn bestimmte Tasks parallelisierbar wären oder wenn Annotation Processors unnötig viel Zeit beanspruchen.

Tasks, die heimlich die meiste Zeit fressen

Bei unserer Analyse sind uns einige wiederkehrende Übeltäter aufgefallen, die man auf den ersten Blick nicht vermuten würde:

  • MergeResources – dauert oft länger als nötig, wenn zu viele Ressourcendateien im Projekt liegen.
  • Dexing – wird bei jedem Build neu ausgeführt, wenn man Multidex nicht richtig konfiguriert hat.
  • ProcessAnnotations – Annotation Processors wie ButterKnife oder Dagger laufen standardmäßig bei jedem inkrementellen Build.
  • PackageDebug – das finale Zusammenpacken der APK zieht sich bei großen Assets in die Länge.

Allein das Identifizieren dieser Tasks hat uns geholfen, gezielt an den richtigen Stellschrauben zu drehen, anstatt im Blindflug zu optimieren.

Abhängigkeiten auf Diät setzen: Weniger ist mehr

Ein Muster, das uns bei Projekten aus Wiener Agenturen immer wieder begegnet, ist der sorglose Umgang mit Abhängigkeiten. Da wird kurzerhand die gesamte Google Play Services Suite eingebunden, obwohl das Projekt nur Maps und Location benötigt. Das aufgeblähte Dependency-Graph führt zu längeren Auflösungszeiten, größeren APKs und vor allem zu langsameren Builds. Wir haben uns angewöhnt, bei jedem neuen Projekt und bei jedem größeren Refactoring die Abhängigkeiten kritisch zu hinterfragen und gezielt auszumisten.

implementation statt api: Ein kleiner Unterschied mit großer Wirkung

Der Unterschied zwischen implementation und api in der build.gradle ist vielen Entwicklern nicht bewusst, hat aber massive Auswirkungen auf die Build-Zeit. Während api die Abhängigkeit an alle Module weitergibt, die das aktuelle Modul einbinden, bleibt sie bei implementation intern. Das bedeutet: Ändert sich eine Library, die nur per implementation eingebunden ist, muss Gradle nur das direkte Modul neu kompilieren, nicht die gesamte Kette. In einem Projekt mit 15 Modulen macht das schnell mehrere Minuten Unterschied pro inkrementellem Build.

Veraltete Bibliotheken erkennen und ersetzen

Veraltete Libraries sind nicht nur ein Sicherheitsrisiko, sie bremsen auch den Build aus, weil sie oft auf veraltete Transitive Dependencies verweisen, die Gradle mühsam auflösen muss. Wir nutzen das Gradle-Plugin “Gradle Versions Plugin”, das mit einem einfachen ./gradlew dependencyUpdates alle verfügbaren Updates auflistet. In einem Projekt für einen Kunden aus Graz haben wir damit 14 veraltete Abhängigkeiten identifiziert und nach dem Update eine Reduktion der Build-Zeit um 22 Prozent gemessen – ohne eine einzige Zeile Produktivcode anzufassen.

JVM-Parameter und Gradle Properties: Unser Feintuning

Die gradle.properties ist das Cockpit für Build-Performance, und erstaunlich viele Projekte fliegen noch mit den Default-Einstellungen. Wir haben für unsere Projekte ein Set an Werten definiert, das sich über die Zeit bewährt hat. Die wichtigste Stellschraube ist org.gradle.jvmargs – hier setzen wir standardmäßig -Xmx4g, also vier Gigabyte Heap für den Gradle-Daemon. Zusammen mit org.gradle.parallel=true und org.gradle.caching=true ergibt das ein Trio, das in unseren Projekten für 30 bis 40 Prozent schnellere Builds sorgt.

org.gradle.parallel und der Daemon: Zwei unterschätzte Helfer

Der Gradle Daemon läuft im Hintergrund und hält den JVM-Prozess warm, sodass nicht bei jedem Build ein neuer gestartet werden muss. In Kombination mit org.gradle.parallel=true werden unabhängige Tasks parallel ausgeführt, was vor allem in Multi-Modul-Projekten einen dramatischen Unterschied macht. Der Daemon allein spart je nach Projektgröße zwischen 5 und 15 Sekunden pro Build – über den Tag verteilt kommt da einiges zusammen. Wichtig ist nur, dem Daemon mit -Xmx4g genug Speicher mitzugeben, sonst stößt er bei großen Projekten schnell an seine Grenzen.

Remote Build Cache für Teams: Lohnt sich das in einem KMU?

Der Gradle Build Cache speichert die Ergebnisse von Tasks und stellt sie bei unveränderten Inputs wieder zur Verfügung. Lokal ist das ein No-Brainer, aber der Remote Build Cache – also ein zentraler Cache-Server für das gesamte Team – ist eine andere Hausnummer. Wir haben das in unserem Team ausgiebig getestet und sind zu einem gemischten Fazit gekommen: Für Teams mit fünf oder mehr Entwicklern, die am selben Codebase arbeiten, bringt ein Remote Cache spürbare Vorteile, vor allem nach CI/CD-Läufen. Für kleinere Teams oder Einzelkämpfer ist der Setup-Aufwand mit einem HTTP-Backend jedoch kaum gerechtfertigt. In einem typischen österreichischen KMU mit zwei bis drei Android-Entwicklern würden wir den Fokus eher auf lokales Caching und saubere Modularisierung legen.

Modularisierung: Der harte, aber nachhaltige Weg

Die Aufteilung eines gewachsenen Monolithen in Feature-Module ist kein Spaziergang, aber sie ist die nachhaltigste Methode, um Build-Zeiten langfristig in den Griff zu bekommen. Wir haben das bei einem Projekt für einen Kunden in Graz durchgezogen, bei dem die Clean-Build-Zeit auf über acht Minuten angewachsen war. Die anfängliche Skepsis im Team war groß – wer reißt schon gerne eine funktionierende App auseinander? Nach der Aufteilung in ein App-Modul, ein Core-Modul und fünf Feature-Module lag die inkrementelle Build-Zeit um 40 Prozent niedriger, und auch die Testausführung wurde deutlich schneller, weil nur betroffene Module neu gebaut werden mussten.

Wann sich Modularisierung wirklich auszahlt

Modularisierung ist kein Allheilmittel und lohnt sich nicht für jedes Projekt. Als Faustregel gilt: Wenn die Clean-Build-Zeit dauerhaft über fünf Minuten liegt und das Team aus mindestens drei Entwicklern besteht, sollte man ernsthaft über Module nachdenken. Auch die Art des Projekts spielt eine Rolle – Apps mit klar getrennten Bereichen wie Onboarding, Profil, Checkout eignen sich besser als stark verwobene Anwendungen. Der größte Hebel ist das inkrementelle Bauen: Ändert sich nur Code in einem Feature-Modul, müssen die anderen Module nicht neu kompiliert werden. Das spart im Entwickleralltag mehr Zeit als jeder Hardware-Upgrade.

Unsere Lessons Learned aus dem ersten Modularisierungsprojekt

Aus dem Grazer Projekt haben wir drei zentrale Lehren mitgenommen: Erstens, plant die Modulgrenzen entlang von Features und nicht entlang technischer Schichten – das verhindert zirkuläre Abhängigkeiten. Zweitens, investiert früh in ein Core-Modul für Shared Resources wie Netzwerk-Layer und Design-System, sonst landet ihr schnell in einem Dependency-Höllenpfuhl. Drittens, nehmt euch Zeit für die Migration und macht sie schrittweise – wir haben Modul für Modul herausgelöst und nicht alles auf einmal umgebaut. Das hat zwar länger gedauert, aber das Team blieb handlungsfähig und die Motivation erhalten.

Hardware und alternative Build-Tools: Ein Realitätscheck

Irgendwann stößt man mit Software-Optimierungen an Grenzen, und die Hardware kommt ins Spiel. Wir haben Build-Zeiten auf einem aktuellen MacBook Pro mit M3-Chip und 32 Gigabyte RAM mit einem drei Jahre alten Intel-basierten MacBook Pro verglichen. Der Unterschied war frappierend: Der M3 hat den identischen Clean-Build in 40 Prozent der Zeit durchgezogen. Das liegt vor allem an der massiv schnelleren SSD und der effizienteren CPU-Architektur. Trotzdem ist neue Hardware nicht die Antwort auf alles – wer vorher nicht seine Build-Konfiguration optimiert hat, verschenkt auch auf einem High-End-Gerät wertvolle Minuten.

SSD, RAM und CPU: Was bringt wirklich etwas?

Für Gradle-Builds ist die SSD der mit Abstand wichtigste Faktor, denn während eines Builds werden tausende kleine Dateien gelesen und geschrieben. Eine schnelle NVMe-SSD schlägt jede SATA-Platte um Längen. RAM ist der zweite Flaschenhals: Mit weniger als 16 Gigabyte stößt man schnell an Grenzen, vor allem wenn Emulatoren, Browser und Slack parallel laufen. Die CPU spielt überraschenderweise die geringste Rolle – mehr als vier Kerne bringen Gradle kaum Vorteile, weil nicht alle Tasks parallelisierbar sind. Wer budgetieren muss: Investiert zuerst in eine gute SSD, dann in mehr RAM, und erst dann in eine schnellere CPU.

Buck und Bazel: Zukunftsmusik oder Overkill für den Mittelstand?

Buck und Bazel sind Build-Systeme, die von Facebook respektive Google entwickelt wurden und auf extrem große Codebases mit hunderten Modulen ausgelegt sind. Wir haben beide in einem Testprojekt ausprobiert und waren von der Geschwindigkeit beeindruckt – inkrementelle Builds im Sekundenbereich sind möglich. Aber der Setup-Aufwand ist enorm, die Dokumentation dünn und die Integration in den typischen Android-Entwicklungsworkflow alles andere als reibungslos. Für die meisten heimischen Entwicklerteams in Agenturen und KMUs ist der Umstieg von Gradle auf Bazel schlicht Overkill. Solange das Projekt nicht mehrere hunderttausend Zeilen Code umfasst und kein dediziertes Build-Engineering-Team existiert, bleibt Gradle die pragmatischere Wahl.

Fazit

Build-Zeiten-Optimierung ist kein einmaliges Projekt, das man an einem Nachmittag abhakt und dann für immer vergisst. Es ist eine kontinuierliche Aufgabe, die sich direkt auf die Zufriedenheit im Team und die Qualität der Arbeit auswirkt. Neue Abhängigkeiten, wachsende Codebases und sich ändernde Anforderungen sorgen dafür, dass man immer wieder nachjustieren muss. Aber die Investition lohnt sich: Weniger Wartezeit bedeutet mehr Flow, mehr Iterationen pro Tag und am Ende ein besseres Produkt. Und mal ehrlich – wer trinkt nicht lieber einen warmen Kaffee, während der Build in unter einer Minute durchläuft?

Häufig gestellte Fragen

Wie finde ich heraus, welche Tasks meinen Build verlangsamen?

Der schnellste Weg führt über den Build Analyzer in Android Studio Giraffe. Öffnen Sie nach einem Build das Build Output-Fenster und klicken Sie auf den Link zur Analyse. Dort sehen Sie eine detaillierte Aufschlüsselung aller Tasks inklusive Zeitangaben und konkreten Optimierungsvorschlägen. Für noch tiefere Einblicke lohnt sich ein Blick auf das Gradle Profiler Tool von JetBrains, das über die Kommandozeile gestartet wird und Flame Graphs generiert.

Lohnt sich der Umstieg auf einen Apple Silicon Mac für Android-Entwicklung?

Ja, der Unterschied ist deutlich spürbar. Ein MacBook Pro mit M3-Chip baut unsere Testprojekte im Schnitt 60 Prozent schneller als ein vergleichbares Intel-Modell. Der größte Vorteil liegt in der schnelleren SSD und der effizienteren Speicheranbindung. Wer täglich mehrere Stunden mit Builds verbringt, wird den Umstieg nicht bereuen. Allerdings sollte man vorher die Software-Seite optimieren – auch der schnellste Rechner kann eine schlechte Build-Konfiguration nicht ausgleichen.

Was ist der Unterschied zwischen implementation und api in Gradle?

implementation macht eine Abhängigkeit nur für das aktuelle Modul sichtbar, während api sie an alle Module weitergibt, die das aktuelle Modul einbinden. Der praktische Unterschied für die Build-Zeit: Bei Änderungen an einer implementation-Abhängigkeit muss Gradle nur ein Modul neu kompilieren, bei api potenziell die gesamte Modulkette. In Multi-Modul-Projekten summiert sich das schnell zu mehreren Minuten pro inkrementellem Build.

Brauche ich den Remote Build Cache für mein Team?

Für Teams mit fünf oder mehr Entwicklern, die am selben Projekt arbeiten, bringt ein Remote Build Cache spürbare Vorteile, vor allem nach CI/CD-Läufen. Für kleinere Teams oder Einzelentwickler überwiegt der Setup-Aufwand meist den Nutzen. Konzentrieren Sie sich in diesem Fall lieber auf lokales Caching, saubere Modularisierung und optimierte Gradle Properties – das bringt mehr bei weniger Aufwand.

Ab welcher Build-Zeit sollte ich mir Sorgen machen?

Google empfiehlt, Build-Zeiten unter zehn Minuten zu halten, aber das ist ein absoluter Maximalwert für Clean-Builds. Im Entwickleralltag sind inkrementelle Builds relevant, und die sollten im Idealfall unter 30 Sekunden liegen. Wenn Ihr inkrementeller Build regelmäßig über eine Minute dauert, lohnt sich eine Analyse mit dem Build Analyzer. Ab zwei Minuten sollten Sie dem Thema Priorität einräumen – Ihr Team wird es Ihnen danken.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *