Dependencies, Signierung, AAB und R8
Du lernst, wie Dependencies, Signierung, AAB und R8 zusammenhängen. So lieferst du sicherer und schlanker aus.
Wenn du eine Android-App veröffentlichst, reicht sauberer Kotlin-Code allein nicht aus. Du musst kontrollieren, welche Bibliotheken in deine App kommen, wie deine App eindeutig signiert wird, welches Artefakt du an Google Play übergibst und wie dein Release-Build verkleinert wird. Dependencies, Signing, AAB und R8 gehören deshalb zusammen: Sie entscheiden mit über Sicherheit, App-Identität, Downloadgröße, Stabilität und Wartbarkeit deiner App.
Was ist das?
Dependencies sind externe oder interne Bausteine, die dein Projekt verwendet. Das können Jetpack-Bibliotheken wie Compose, Lifecycle, Room oder Navigation sein, aber auch Kotlin-Coroutines, Testbibliotheken oder eigene Module. Ohne Dependency-Management würdest du Versionen verstreut in mehreren Gradle-Dateien pflegen. Mit Version Catalogs legst du zentrale Namen und Versionen in libs.versions.toml ab. Dadurch wird sichtbar, welche Bibliotheken dein Projekt nutzt und welche Versionen zusammengehören.
Signing bedeutet, dass deine App mit einem kryptografischen Schlüssel signiert wird. Android nutzt diese Signatur, um die Identität einer App zu prüfen. Ein Update darf nur installiert werden, wenn es zur bestehenden Signatur passt. Für reale Veröffentlichungen ist das kein Detail, sondern die Grundlage dafür, dass Nutzerinnen und Nutzer deine App sicher aktualisieren können. Mit Play App Signing verwaltet Google Play den App-Signing-Key für die Auslieferung, während du mit einem Upload-Key neue Releases hochlädst.
AAB steht für Android App Bundle. Das ist kein direkt installierbares APK für alle Geräte, sondern ein Veröffentlichungsformat. Google Play erzeugt daraus passende APKs für konkrete Geräte, zum Beispiel mit passenden Ressourcen, CPU-Architekturen und Sprachpaketen. Das senkt die Downloadgröße und passt gut zu modernen Android-Apps, die mit vielen Ressourcen, Compose, Feature-Modulen oder mehreren Sprachen arbeiten.
R8 ist der Shrinker und Optimizer im Android-Build. In Release-Builds entfernt R8 ungenutzten Code, benennt Klassen und Methoden um und optimiert Bytecode. Das Ergebnis ist meist kleiner und schwerer zu analysieren. Gleichzeitig kann R8 Code beschädigen, wenn Bibliotheken über Reflection, Annotationen oder generierte Adapter auf Namen zugreifen und du keine passenden Keep-Regeln setzt.
Das mentale Modell ist: Dependencies definieren, was in deine App hineinkommt. Signing definiert, wer diese App offiziell ausliefert. AAB definiert, wie die App verteilt wird. R8 definiert, wie der fertige Release-Build reduziert und optimiert wird.
Wie funktioniert es?
Im Alltag beginnt das Thema meistens in Gradle. Du fügst eine Bibliothek hinzu, aktualisierst Compose oder ziehst eine Testabhängigkeit nach. Wenn du Versionen direkt in mehreren build.gradle.kts-Dateien verstreust, entstehen schnell Widersprüche. Ein Modul nutzt dann vielleicht eine andere Coroutines-Version als ein anderes Modul. Version Catalogs helfen dir, diese Entscheidung an einer Stelle zu treffen. Der Name libs.androidx.lifecycle.runtime.ktx ist dabei nicht nur Komfort, sondern eine lesbare Vereinbarung im Team.
Wichtig ist die Trennung zwischen verschiedenen Dependency-Arten. implementation ist für normalen App-Code. debugImplementation ist nur im Debug-Build aktiv, etwa für Debugging-Werkzeuge. testImplementation gilt für lokale Unit-Tests, androidTestImplementation für instrumentierte Tests auf Gerät oder Emulator. Diese Grenzen sind wichtig, weil Test- oder Debug-Bibliotheken nicht versehentlich im Release landen sollten. Gerade bei Security- und Privacy-Themen musst du wissen, welche Bibliotheken wirklich ausgeliefert werden.
Signing kommt später im Build-Prozess, ist aber konzeptionell früh mitzudenken. Jede installierbare Android-App braucht eine Signatur. Debug-Builds werden automatisch mit einem Debug-Key signiert. Release-Builds brauchen eine saubere Konfiguration. Für den Play Store ist Play App Signing der Standardweg: Du lädst ein signiertes Bundle mit deinem Upload-Key hoch, Google Play signiert die ausgelieferten APKs mit dem App-Signing-Key. So kannst du den Upload-Key bei Bedarf wechseln, ohne die App-Identität für Nutzer zu verlieren. Den App-Signing-Key solltest du wie ein zentrales Produktionsgeheimnis betrachten.
Das AAB wird über Gradle aus deinem Release-Variant gebaut. Es enthält Code, Ressourcen, Manifest, native Bibliotheken und Metadaten, aber Google Play übernimmt die Aufteilung in gerätespezifische Pakete. Für dich bedeutet das: Du testest nicht nur den Debug-Build aus Android Studio, sondern auch den Release-Pfad. Ein Fehler kann nur im minifizierten Release auftreten, obwohl Debug stabil wirkt.
R8 ist in modernen Android-Release-Builds eng mit minifyEnabled verbunden. Wenn Minification aktiv ist, analysiert R8 den erreichbaren Code. Nicht erreichbare Klassen werden entfernt, Namen werden gekürzt, und Optimierungen werden angewendet. Viele Android- und Jetpack-Bibliotheken liefern eigene Consumer-ProGuard-Regeln mit. Trotzdem bist du verantwortlich, wenn dein eigener Code Reflection nutzt oder wenn eine Bibliothek besondere Regeln verlangt. Typische Beispiele sind JSON-Serialisierung, Dependency Injection, Datenklassen, WebView-JavaScript-Bridges oder dynamisch geladene Klassennamen.
Qualitätssicherung verbindet diese Themen. Tests prüfen Fachlogik und UI-Verhalten, aber sie ersetzen keine Release-Prüfung. Continuous Integration sollte mindestens bauen, testen und möglichst auch einen Release-Build erzeugen. So findest du früh heraus, ob Dependency-Versionen kollidieren, R8-Regeln fehlen oder Signing-Konfigurationen nicht sauber getrennt sind. Ein Build, der nur lokal auf deinem Rechner funktioniert, ist für einen stabilen Release-Prozess zu schwach.
In der Praxis
Ein kleines Beispiel zeigt, wie die Teile zusammenspielen. In einem Projekt verwaltest du Bibliotheken über einen Version Catalog und aktivierst R8 nur für den Release-Build. Die Datei gradle/libs.versions.toml könnte so aussehen:
[versions]
kotlin = "2.0.21"
coreKtx = "1.15.0"
lifecycle = "2.8.7"
composeBom = "2024.12.01"
[libraries]
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
androidx-compose-ui = { module = "androidx.compose.ui:ui" }
androidx-compose-material3 = { module = "androidx.compose.material3:material3" }
[plugins]
android-application = { id = "com.android.application", version = "8.7.3" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
Im App-Modul verwendest du dann sprechende Aliase statt verstreuter Versionsstrings:
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "de.beispiel.releasecheck"
compileSdk = 35
defaultConfig {
applicationId = "de.beispiel.releasecheck"
minSdk = 26
targetSdk = 35
versionCode = 12
versionName = "1.4.0"
}
buildTypes {
debug {
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug"
}
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.material3)
}
Daran erkennst du mehrere Regeln. Erstens: Versionen gehören zentral verwaltet, besonders in Projekten mit mehreren Modulen. Zweitens: Debug- und Release-Builds dürfen sich bewusst unterscheiden, aber nicht zufällig. Ein applicationIdSuffix für Debug verhindert, dass du versehentlich die Produktiv-App mit einer lokalen Debug-Version überschreibst. Drittens: R8 wird im Release getestet, nicht erst kurz vor dem Upload.
Für Signing solltest du sensible Werte nicht direkt in Git speichern. Keystore-Pfade, Passwörter und Alias-Namen gehören in lokale Properties, CI-Secrets oder einen geschützten Secret Store. In Code-Reviews solltest du darauf achten, dass keine .jks-Datei, kein Passwort und kein privater Schlüssel im Repository landet. Play App Signing nimmt dir nicht die Pflicht ab, den Upload-Key sauber zu schützen.
Eine typische Stolperfalle ist der Satz: “Im Debug-Build funktioniert es, also ist der Release sicher.” Das stimmt nicht. R8 kann Klassen entfernen, die nur indirekt benutzt werden. Beispiel: Eine JSON-Bibliothek erzeugt Objekte über Reflection, aber R8 sieht keinen direkten Konstruktoraufruf. Dann läuft der Debug-Build, während der Release beim Parsen abstürzt. Die Lösung ist nicht, R8 dauerhaft auszuschalten. Besser ist, die Ursache zu verstehen, Bibliotheksdokumentation zu prüfen und gezielte Keep-Regeln zu setzen.
Eine weitere Stolperfalle betrifft Dependencies. Wenn du eine neue Bibliothek einfügst, bewertest du nicht nur die API. Du prüfst auch Pflegezustand, Lizenz, Transitive Dependencies, Größe und Sicherheitswirkung. Für eine Compose-App kann eine kleine UI-Bibliothek bequem wirken, aber sie kann viele zusätzliche Abhängigkeiten mitbringen. Im Team ist deshalb eine einfache Regel hilfreich: Jede neue Runtime-Dependency braucht einen klaren Nutzen, eine bekannte Quelle und einen Blick auf die Release-Auswirkung. Test-Dependencies dürfen großzügiger sein, gehören aber in den richtigen Scope.
Für AABs solltest du den Veröffentlichungsweg praktisch testen. Baue lokal oder in CI ein Release-Bundle, lade es in einen internen Testtrack hoch und installiere die daraus erzeugte App auf mindestens einem realen Gerät. Prüfe Start, Login, Navigation, Netzwerkfehler, lokale Datenbank, Hintergrundarbeit und zentrale Compose-Screens. Ergänze automatisierte Tests dort, wo Fehler teuer wären. Unit-Tests prüfen Logik schnell, instrumentierte Tests prüfen Android-Integration, und CI macht sichtbar, ob der Release-Pfad wiederholbar ist.
Eine sinnvolle Code-Review-Frage lautet: “Ändert dieser Pull Request etwas an ausgeliefertem Code, App-Identität oder Release-Größe?” Wenn ja, prüfst du genauer. Dependency-Updates sollten nicht als reine Routine behandelt werden. Signing-Änderungen brauchen besondere Aufmerksamkeit. R8-Regeln sollten möglichst eng sein, denn zu breite Regeln verringern den Nutzen des Shrinkings. Ein -keep class ** { *; } kann zwar einen Crash verstecken, macht aber Optimierung und Obfuskierung fast wirkungslos.
Fazit
Dependencies, Signing, AAB und R8 bilden den Übergang von “die App läuft auf meinem Gerät” zu “die App kann kontrolliert veröffentlicht und gewartet werden”. Du solltest zentrale Versionen pflegen, Schlüssel schützen, das Android App Bundle als Release-Artefakt verstehen und R8 als hilfreichen, aber prüfpflichtigen Optimierer behandeln. Übe das an einem kleinen Projekt: Füge eine Bibliothek über den Version Catalog hinzu, baue ein Release-AAB, aktiviere Shrinking, installiere die App über einen Testtrack oder ein lokales Release-APK und prüfe Logs, Tests und Code-Review-Fragen. So lernst du nicht nur die Befehle, sondern den Release-Prozess, den du in echten Android-Projekten brauchst.