Wenn ein mobiles Projekt sowohl iOS- als auch Android-Clients umfasst, verlagern Teams Android-Prüfungen häufig in eine separate Ausführungsumgebung. Ein ARM64-Emulator auf einem Apple-Silicon-Cloud-Mac ermöglicht es dagegen, Code-Checkout, API-Smoke-Tests und die Abnahme beider Plattformen in derselben Pipeline auszuführen. Fehler entstehen dabei meist nicht durch die Installation der Werkzeuge, sondern durch eine falsch gewählte Architektur, eine verfrühte Bereitschaftserkennung, gemeinsam genutzten Zustand paralleler Jobs und fehlende Artefakte für die nachträgliche Fehleranalyse.
Architektur und Verzeichnisse zuerst festlegen
Der Runner sollte zunächst die Unterstützung für Hardwarevirtualisierung prüfen. Anschließend müssen die Speicherorte für Android SDK, AVDs und Build-Artefakte vereinheitlicht werden. Skripte dürfen nicht von Umgebungsvariablen abhängen, die nur vorübergehend in einer interaktiven Shell gesetzt wurden.
export ANDROID_HOME="$HOME/Library/Android/sdk"
export ANDROID_AVD_HOME="$HOME/.android/avd"
export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"
sysctl kern.hv_support
emulator -accel-check
adb version
kern.hv_support sollte melden, dass die Funktion verfügbar ist, und auch emulator -accel-check muss erfolgreich sein. Falls die Ergebnisse voneinander abweichen, sollte zuerst geprüft werden, ob die Befehle tatsächlich im Benutzerkontext des ausführenden Jobs laufen, statt das SDK wiederholt neu zu installieren.
Auf Apple-Silicon-Knoten ist ein System-Image für arm64-v8a zu verwenden. Ein x86_64-Image erhöht nicht nur den Übersetzungsaufwand, sondern kann auch dazu führen, dass Probleme beim Laden nativer Bibliotheken fälschlicherweise dem Anwendungscode zugeschrieben werden. Die Version des System-Images sollte über eine Repository-Variable fest vorgegeben werden. Aktualisierungen müssen über Merge Requests erfolgen, statt bei jedem Job automatisch der neuesten Version zu folgen.
Wiederverwendbares Basis-AVD erstellen
Zunächst werden die vom Projekt ausdrücklich benötigte Plattform, der Emulator und das System-Image installiert. Danach wird ein Basisgerät erstellt, das nicht an den interaktiven Zustand eines einzelnen Benutzers gebunden ist.
API_LEVEL=35
IMAGE="system-images;android-${API_LEVEL};google_apis;arm64-v8a"
AVD_NAME="ci-arm64-api-${API_LEVEL}"
sdkmanager "platform-tools" "emulator" "platforms;android-${API_LEVEL}" "$IMAGE"
printf "no\n" | avdmanager create avd \
--force \
--name "$AVD_NAME" \
--package "$IMAGE" \
--device "pixel_6"
Nach der Erstellung sollte config.ini geprüft werden. In der Continuous-Integration-Umgebung werden Kamera, Mikrofon und ein großer beschreibbarer Datenträger normalerweise nicht benötigt. Nicht verwendete Geräte können deaktiviert und Arbeitsspeicher, Pixeldichte sowie Auflösung fest eingestellt werden. Je weniger Parameter variieren, desto leichter lässt sich die Ausgangsbasis reproduzieren.
Das Basis-AVD dient ausschließlich als saubere Vorlage, deren erster Start bereits abgeschlossen ist. Testdaten, Anmeldestatus und Anwendungscaches dürfen nicht in das Basisverzeichnis zurückgeschrieben werden.
Beim ersten Start muss die Systeminitialisierung abgeschlossen werden. Sobald die Desktop-Dienste verfügbar sind, werden Animationen deaktiviert und temporäre Anwendungen entfernt. Anschließend wird der Snapshot ci-base gespeichert. Die Emulatorversion, mit der der Snapshot erstellt wurde, muss mit der Version übereinstimmen, die ihn wiederherstellt. Nach einem Emulator-Upgrade ist der Snapshot neu zu erstellen, statt den alten weiterzuverwenden.
Beim Headless-Start nicht nur auf ADB warten
In der Pipeline wird das grafische Fenster mit -no-window deaktiviert. Jeder Job erhält außerdem einen eigenen geraden Port. Wenn ein Gerät in adb devices als device erscheint, bedeutet das lediglich, dass der Transportkanal aufgebaut wurde – nicht, dass der Systemstart abgeschlossen ist.
AVD_NAME="ci-arm64-api-35"
EMULATOR_PORT=5556
SERIAL="emulator-${EMULATOR_PORT}"
emulator "@${AVD_NAME}" \
-no-window \
-no-audio \
-no-boot-anim \
-port "$EMULATOR_PORT" \
-snapshot ci-base \
-no-snapshot-save &
adb -s "$SERIAL" wait-for-device
for attempt in $(seq 1 90); do
status="$(adb -s "$SERIAL" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')"
[ "$status" = "1" ] && break
sleep 2
done
[ "$status" = "1" ] || exit 1
Für das Warten auf den Systemstart muss ein Gesamt-Timeout gelten. Bei einer Zeitüberschreitung sollten getprop, logcat und die Standardfehlerausgabe des Emulators gesichert werden, bevor der Prozess beendet wird. Unbegrenztes Warten blockiert lediglich einen Ausführungsplatz und verschleiert beschädigte Images oder Portkonflikte.
Animationen deaktivieren und Zustand normalisieren
Auch nach dem Wiederherstellen eines Snapshots sollte eine Reihe idempotenter Einstellungen angewendet werden. So bleiben wichtige Zustände erhalten, selbst wenn das Basis-Image neu erstellt wird.
adb -s "$SERIAL" shell settings put global window_animation_scale 0
adb -s "$SERIAL" shell settings put global transition_animation_scale 0
adb -s "$SERIAL" shell settings put global animator_duration_scale 0
adb -s "$SERIAL" shell input keyevent 82
Diese Befehle ersetzen keine Test-Fixtures. Sprache, Zeitzone, Berechtigungen und Netzwerkzustand müssen weiterhin von den Testfällen explizit gesetzt und nach dem Test wiederhergestellt werden.
Minimalen Abnahmezyklus mit ADB aufbauen
Sobald der Emulator bereit ist, sollten zunächst Installation, Anwendungsstart und laufender Prozess geprüft werden. Erst danach wird die vollständige Testsuite ausgeführt. So lassen sich Umgebungsfehler von fehlgeschlagenen fachlichen Assertions unterscheiden.
adb -s "$SERIAL" install -r "$APK_PATH"
adb -s "$SERIAL" shell am force-stop "$APP_ID"
adb -s "$SERIAL" shell am start -W -n "${APP_ID}/${LAUNCH_ACTIVITY}"
adb -s "$SERIAL" shell pidof "$APP_ID"
am start -W gibt das Startergebnis und Felder mit Zeitmessungen zurück. Das Skript sollte sowohl den erfolgreichen Status prüfen als auch bestätigen, dass pidof eine Ausgabe liefert. Ein Exitcode von null beim Installationsbefehl beweist weder, dass die Start-Activity aufgelöst werden kann, noch dass der Prozess startet oder die Architektur nativer Bibliotheken korrekt ist.
Bei einem Fehler sollte mindestens der folgende Nachweissatz erhalten bleiben:
| Nachweis | Befehl oder Speicherort | Zweck |
|---|---|---|
| Geräteeigenschaften | adb shell getprop |
API, ABI und Startstatus prüfen |
| Systemprotokoll | adb logcat -d -v threadtime |
Abstürze sowie Berechtigungs- und Dienstfehler lokalisieren |
| Installationsinformationen | adb shell dumpsys package "$APP_ID" |
Version, Einstiegspunkt und ABI prüfen |
| Bildschirminhalt | adb exec-out screencap -p |
Überlagerungen, Dialoge und schwarze Bildschirme erkennen |
| Emulatorausgabe | Standardfehlerdatei des Jobs | Snapshot- und Virtualisierungsprobleme identifizieren |
Vor der Archivierung müssen Protokolle bereinigt werden, damit Umgebungsvariablen, Zugriffstoken oder Anmeldedaten von Testkonten nicht in dauerhaft gespeicherte Pipeline-Artefakte gelangen.
Parallele Jobs isolieren und zuverlässig bereinigen
Wenn mehrere Emulatoren auf demselben physischen Knoten laufen, benötigt jeder Job einen eigenen Port, eine eigene AVD-Kopie und ein eigenes temporäres Verzeichnis. Mehrere Prozesse dürfen nicht dasselbe Basis-AVD direkt öffnen, da sie sonst Sperrdateien, Benutzerdaten und Snapshots gegenseitig überschreiben können.
Zu Beginn eines Jobs kann das Basis-AVD in das Arbeitsverzeichnis kopiert und der zugehörige Pfad in der .ini-Datei angepasst werden. Die Ports werden vom Scheduler vergeben und müssen gerade sowie eindeutig sein. Nach dem Test ist unabhängig von Erfolg oder Fehlschlag stets eine Bereinigung auszuführen:
cleanup() {
adb -s "$SERIAL" emu kill >/dev/null 2>&1 || true
wait "$EMULATOR_PID" 2>/dev/null || true
rm -rf "$JOB_AVD_HOME"
}
trap cleanup EXIT INT TERM
Die maximale Parallelität sollte nicht allein anhand der Anzahl der CPU-Kerne festgelegt werden. Emulatoren, Anwendungen und Build-Jobs beanspruchen gleichzeitig Arbeitsspeicher und Datenträgerbandbreite. Robuster ist es, mit einer Instanz zu beginnen, den maximalen Speicherverbrauch, die Startzeit und die Testdauer zu erfassen und die Parallelität anschließend schrittweise zu erhöhen. Sobald Startzeit und Fehlerrate gleichzeitig steigen, sollte die Parallelität wieder um eine Stufe reduziert werden.
Für eine reproduzierbare Ausführung ist nicht ein bestimmter Startparameter entscheidend, sondern die Einhaltung von vier Grenzen: fest vorgegebene Image-Versionen, ein überprüfbarer Abschluss des Systemstarts, voneinander isolierter Jobzustand und archivierbare Fehlerdaten. Sobald diese vier Punkte Bestandteil des Pipeline-Vertrags sind, wird der Android-Emulator von einem provisorischen Werkzeug zu einer stabilen technischen Ausführungseinheit.
Häufig gestellte Fragen
Warum reicht der Status device in adb devices nicht aus?
Der Status bestätigt nur die ADB-Verbindung. Erst wenn sys.boot_completed den Wert 1 liefert, sollten Installation, App-Start und Tests beginnen.
Dürfen parallele CI-Aufträge dasselbe AVD verwenden?
Nein. Jeder Auftrag sollte eine eigene AVD-Kopie, einen eigenen Port und ein eigenes Datenverzeichnis erhalten, damit Sperren und Zustandsreste ausgeschlossen bleiben.
Den nächsten Build auf einem exklusiven Cloud-Mac ausführen
Wählen Sie Modell, Standort und Abrechnungszeitraum. Konfiguration und Preise in USD werden vor der Bestellung vollständig angezeigt; der Verfügbarkeitsstatus wird in Echtzeit über die Konsole ermittelt.