Если мобильный проект включает клиенты для iOS и Android, проверку Android часто выносят в отдельную среду выполнения. Однако развертывание эмулятора ARM64 на облачном Mac с Apple Silicon позволяет выполнять получение исходного кода, дымовые проверки API и приемочное тестирование обеих платформ в одном конвейере. Основные ошибки связаны не с установкой инструментов, а с неверно выбранной архитектурой, преждевременным определением готовности системы, совместным состоянием параллельных заданий и отсутствием диагностических данных после сбоя.
Сначала зафиксируйте архитектуру и каталоги
На исполнительном узле сначала следует проверить поддержку аппаратной виртуализации, а затем задать единые расположения для Android SDK, AVD и артефактов сборки. Скрипты не должны зависеть от переменных среды, временно заданных в интерактивной оболочке.
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 должен сообщать о доступности виртуализации, а проверка emulator -accel-check должна завершаться успешно. Если результаты двух проверок различаются, сначала убедитесь, что команды выполняются в контексте пользователя, от имени которого фактически запускается задание, а не переустанавливайте SDK снова и снова.
На узлах Apple Silicon необходимо выбирать системный образ arm64-v8a. Использование образа x86_64 не только увеличивает накладные расходы на трансляцию, но и может привести к тому, что проблемы с загрузкой нативных библиотек будут ошибочно отнесены к коду приложения. Версия системного образа должна фиксироваться переменной репозитория, а обновляться — через запрос на слияние, а не автоматически переходить на последнюю версию при каждом запуске задания.
Создайте переиспользуемый базовый AVD
Сначала установите платформу, эмулятор и образ, явно требуемые проектом, а затем создайте базовое устройство, не зависящее от персонального интерактивного состояния.
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"
После создания проверьте config.ini. Для непрерывной интеграции обычно не нужны камера, микрофон и большой записываемый диск с данными, поэтому неиспользуемые устройства можно отключить, а объем памяти, плотность экрана и разрешение — зафиксировать. Чем меньше параметров, тем проще воспроизвести базовую конфигурацию.
Базовый AVD должен служить только чистым шаблоном, уже прошедшим первый запуск. Тестовые данные, состояние авторизации и кеш приложения не должны записываться обратно в базовый каталог.
Во время первого запуска необходимо завершить инициализацию системы. Убедитесь, что службы рабочего стола доступны, затем отключите анимацию, удалите временные приложения и сохраните снимок ci-base. Версия эмулятора, создавшего снимок, должна совпадать с версией, которая его восстанавливает. После обновления эмулятора снимок следует создать заново, а не продолжать использовать старый.
При запуске без интерфейса недостаточно дождаться ADB
В конвейере отключайте графическое окно с помощью -no-window и выделяйте каждому заданию отдельный четный порт. Статус device в выводе adb devices означает лишь, что транспортный канал установлен, но не подтверждает завершение загрузки системы.
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
Для ожидания загрузки необходимо задавать общий тайм-аут. При его превышении следует сохранить getprop, logcat и стандартный поток ошибок эмулятора, а затем завершить процесс. Бесконечное ожидание лишь занимает исполнительный слот и скрывает повреждение образа или конфликт портов.
Отключите анимацию и нормализуйте состояние
Даже после восстановления снимка следует выполнять набор идемпотентных настроек, чтобы не потерять критически важное состояние при пересоздании базового образа.
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
Эти команды не заменяют тестовые фикстуры. Язык, часовой пояс, разрешения и состояние сети по-прежнему должны явно задаваться тестами и восстанавливаться после их завершения.
Создайте минимальный цикл приемки с помощью ADB
После подготовки эмулятора сначала проверьте установку, запуск и наличие работающего процесса, а уже затем переходите к полному набору тестов. Это позволяет отличить сбой среды от неуспешной бизнес-проверки.
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 возвращает результат запуска и поля с данными о его длительности. Скрипт должен проверять успешность статуса и одновременно убеждаться, что pidof выдал результат. Нулевой код возврата команды установки сам по себе не доказывает, что входная Activity разрешается корректно, процесс способен запуститься, а архитектура нативных библиотек выбрана правильно.
При сбое рекомендуется сохранять следующий минимальный набор диагностических данных:
| Данные | Команда или расположение | Назначение |
|---|---|---|
| Свойства устройства | adb shell getprop |
Проверка API, ABI и состояния загрузки |
| Системный журнал | adb logcat -d -v threadtime |
Поиск сбоев, проблем с разрешениями и службами |
| Сведения об установке | adb shell dumpsys package "$APP_ID" |
Проверка версии, точки входа и ABI |
| Состояние экрана | adb exec-out screencap -p |
Выявление перекрытий, диалоговых окон и черного экрана |
| Вывод эмулятора | Файл стандартного потока ошибок задания | Выявление проблем со снимками и виртуализацией |
Перед архивацией журналы необходимо очищать от конфиденциальных данных, чтобы переменные среды, токены доступа и учетные данные тестовых аккаунтов не попадали в артефакты конвейера, предназначенные для длительного хранения.
Изолируйте параллельные задания и обеспечьте надежную очистку
Если на одном физическом узле работают несколько эмуляторов, каждому заданию необходимы отдельный порт, собственная копия AVD и временный каталог. Несколько процессов не должны напрямую открывать один и тот же базовый AVD, иначе файлы блокировок, пользовательские данные и снимки могут перезаписывать друг друга.
В начале задания базовый AVD можно скопировать в рабочий каталог и заменить соответствующий путь в .ini. Порты должен назначать планировщик: они должны быть четными и не повторяться. После завершения тестов очистка должна выполняться независимо от результата:
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
Не следует определять предел параллелизма только по числу ядер CPU. Эмуляторы, приложения и задания сборки одновременно потребляют память и пропускную способность диска. Надежнее начать с одного экземпляра, зафиксировать пиковое потребление памяти, время запуска и продолжительность тестов, а затем постепенно повышать параллелизм. Как только время запуска и частота сбоев начнут расти одновременно, следует вернуться на один уровень назад.
Итоговая воспроизводимость определяется не конкретным параметром запуска, а четырьмя границами: версия образа зафиксирована, завершение загрузки проверяется, состояние заданий изолировано, а данные о сбоях архивируются. Когда эти четыре требования закреплены в контракте конвейера, эмулятор Android превращается из временного инструмента в стабильную исполнительную единицу инженерного процесса.
Часто задаваемые вопросы
Достаточно ли статуса device в выводе adb devices?
Нет. Этот статус подтверждает только доступность транспорта ADB. Установку приложения и тесты следует начинать после того, как sys.boot_completed вернёт 1.
Можно ли нескольким заданиям CI использовать один AVD?
Не следует. Каждому заданию нужны отдельная копия AVD, собственный порт и каталог данных, иначе возможны блокировки, перезапись снимков и утечка состояния.
Запустите следующую сборку на выделенном облачном Mac
Выберите модель, узел и период оплаты. Конфигурация и сумма в долларах США будут полностью указаны до оформления заказа; актуальный статус доступности отображается в консоли в реальном времени.