DimaSOS

Android SDK

Инструменты SDK и сборка. Версии актуальны на август 2026: AGP 9.3, максимальный поддерживаемый API level — 37.

#sdkmanager и avdmanager

Живут в $ANDROID_HOME/cmdline-tools/latest/bin. Если команд нет — в Android Studio доустанавливается пакет «Android SDK Command-line Tools».

export ANDROID_HOME="$HOME/Android/Sdk"
export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator"

Минимальный набор переменных. ANDROID_SDK_ROOT устарел в пользу ANDROID_HOME.

sdkmanager --list | head -40
sdkmanager --list_installed
sdkmanager --update

Что доступно и что уже стоит.

sdkmanager "platform-tools" "platforms;android-36" "build-tools;36.0.0"
sdkmanager "system-images;android-36;google_apis;x86_64"
sdkmanager --licenses

Установка компонентов. --licenses нужен в CI, иначе сборка падает на непринятых лицензиях.

yes | sdkmanager --licenses > /dev/null

Автоматическое принятие лицензий для CI.

avdmanager list device | head -30
avdmanager create avd -n test36 -k "system-images;android-36;google_apis;x86_64" -d pixel_7
avdmanager list avd
avdmanager delete avd -n test36

Создание и удаление эмуляторов из консоли.

# образ БЕЗ Google Play — на нём работает adb root
sdkmanager "system-images;android-36;google_apis;x86_64"

# образ С Google Play — root недоступен
sdkmanager "system-images;android-36;google_apis_playstore;x86_64"

Выбор образа определяет, будет ли доступен root.

Для автотестов почти всегда нужен google_apis без Play Store: и root есть, и Google Play Services на месте.

#Эмулятор

emulator -list-avds
emulator -avd test36 &
emulator -avd test36 -no-window -no-audio -no-boot-anim -gpu swiftshader_indirect

Второй вариант — headless-режим для CI. swiftshader_indirect — программный рендер, когда GPU недоступен.

emulator -avd test36 -no-snapshot-load -wipe-data

Чистый старт с нуля. Обязательно перед прогоном, если нужна воспроизводимость.

emulator -avd test36 -writable-system     # изменяемый /system
emulator -avd test36 -http-proxy 127.0.0.1:8888
emulator -avd test36 -dns-server 8.8.8.8

Частые флаги. Прокси на уровне эмулятора удобнее, чем через settings put global http_proxy.

adb emu geo fix 37.617 55.755          # долгота, широта
adb emu sms send +79001234567 "код 1234"
adb emu power capacity 15
adb emu kill

Консоль эмулятора через adb: геолокация, входящая SMS, батарея. Так тестируются сценарии, которые на реальном устройстве не воспроизвести.

# хост со стороны эмулятора
10.0.2.2        # localhost машины разработчика
10.0.2.3        # DNS хоста

Специальные адреса эмулятора. Для физического устройства вместо этого — adb reverse.

#Gradle

AGP 9.x требует Gradle 8.11 и новее. Начиная с AGP 9.0 поддержка Kotlin встроена: плагин org.jetbrains.kotlin.android применять больше не нужно.

./gradlew tasks --all | head -40
./gradlew :app:dependencies --configuration releaseRuntimeClasspath
./gradlew :app:assembleDebug --scan

Список задач, дерево зависимостей, build scan с разбором времени сборки.

./gradlew assembleDebug          # apk
./gradlew bundleRelease          # aab для Play
./gradlew installDebug           # собрать и поставить
./gradlew :app:testDebugUnitTest         # unit-тесты
./gradlew :app:connectedDebugAndroidTest # инструментальные, нужен девайс

Базовый набор задач.

./gradlew clean build --refresh-dependencies
./gradlew --stop                 # погасить демонов
rm -rf ~/.gradle/caches/build-cache-1   # снести кеш сборки

Когда «у меня не собирается, а у коллеги собирается». Порядок именно такой: сначала мягкое, потом кеши.

./gradlew assembleDebug --offline
./gradlew assembleDebug -PsomeFlag=true
./gradlew assembleDebug --console=plain    # для логов CI
./gradlew assembleDebug --info --stacktrace

Полезные флаги. --console=plain убирает перерисовку прогресса, из-за которой логи CI превращаются в кашу.

// gradle/libs.versions.toml
[versions]
agp = "9.3.0"
kotlin = "2.2.0"
composeBom = "2026.06.01"

[libraries]
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
compose-ui = { group = "androidx.compose.ui", name = "ui" }
compose-material3 = { group = "androidx.compose.material3", name = "material3" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0" }

[bundles]
compose = ["compose-ui", "compose-material3"]

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }

Version catalog — четыре секции: [versions], [libraries], [bundles], [plugins]. Файл лежит в gradle/libs.versions.toml.

Дефис в имени алиаса превращается в точку при обращении: compose-uilibs.compose.ui.

// build.gradle.kts (модуль)
plugins {
    alias(libs.plugins.android.application)
    // org.jetbrains.kotlin.android НЕ НУЖЕН начиная с AGP 9.0
}

dependencies {
    implementation(platform(libs.compose.bom))
    implementation(libs.bundles.compose)
    implementation(libs.okhttp)
}

Обращение к каталогу: libs.<alias> для библиотек, libs.plugins.<alias> для плагинов, libs.bundles.<alias> для наборов.

android {
    namespace = "com.example.app"
    compileSdk = 36

    defaultConfig {
        applicationId = "com.example.app"
        minSdk = 24
        targetSdk = 36
    }

    buildFeatures {
        compose = true
    }
}

Компактный блок. buildFeatures { compose = true } включает Compose; компилятор Compose с Kotlin 2.0 живёт в репозитории Kotlin и поставляется вместе с ним, так что версии всегда совпадают.

Отдельный плагин org.jetbrains.kotlin.plugin.compose нужен только если хочется переопределить версию компилятора или задать опции — он перекрывает координаты, которые AGP подставляет сам.

#Подпись и проверка APK

keytool -genkeypair -v \
  -keystore release.jks -alias upload \
  -keyalg RSA -keysize 4096 -validity 10000

Создание keystore. validity в днях — для Play нужен запас лет на 25.

keytool -list -v -keystore release.jks -alias upload
keytool -printcert -jarfile app-release.aab

Отпечатки SHA-1 и SHA-256 — нужны для Google Maps, Firebase и App Links.

$ANDROID_HOME/build-tools/36.0.0/apksigner verify --verbose --print-certs app.apk

Проверка подписи и схем (v1/v2/v3/v4). Самый быстрый способ понять, чем подписан чужой или CI-артефакт.

zipalign -c -v 4 app.apk                   # проверить выравнивание
zipalign -v 4 in.apk out.apk               # выровнять
apksigner sign --ks release.jks --ks-key-alias upload out.apk

Порядок важен: сначала zipalign, потом apksigner. Наоборот — сломается подпись.

aapt2 dump badging app.apk | head -20
aapt2 dump permissions app.apk
aapt2 dump xmltree app.apk --file AndroidManifest.xml | head -40

Что внутри APK без распаковки: applicationId, версии, разрешения, манифест. Незаменимо при проверке, что собралось именно то, что ожидалось.

unzip -l app.apk | sort -k1 -rn | head -20

Что занимает место в APK. Быстрая замена APK Analyzer для консоли.

#bundletool

AAB нельзя поставить на устройство напрямую. bundletool превращает бандл в набор APK и умеет ставить нужный вариант.

bundletool build-apks \
  --bundle=app-release.aab \
  --output=app.apks \
  --ks=release.jks --ks-key-alias=upload

Полный набор сплитов для всех конфигураций.

bundletool build-apks \
  --bundle=app-release.aab --output=app.apks \
  --connected-device \
  --ks=release.jks --ks-key-alias=upload

bundletool install-apks --apks=app.apks

Собрать только под подключённое устройство и поставить. Самый короткий путь проверить релизный бандл руками.

bundletool build-apks --bundle=app.aab --output=app.apks --mode=universal
unzip -p app.apks universal.apk > universal.apk

Один универсальный APK со всеми ABI и ресурсами — для раздачи вне Play и для ручного тестирования.

bundletool get-size total --apks=app.apks
bundletool get-size total --apks=app.apks --dimensions=SDK,ABI

Оценка размера загрузки для пользователя. Именно эту цифру видно в Play, а не размер AAB.

bundletool extract-apks --apks=app.apks --output-dir=out \
  --device-spec=device.json
bundletool get-device-spec --output=device.json

Спецификация устройства и извлечение подходящих сплитов.

#Сборка в CI

#!/usr/bin/env bash
set -euo pipefail

export ANDROID_HOME="${ANDROID_HOME:-$HOME/Android/Sdk}"
export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools"
export GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false"

yes | sdkmanager --licenses > /dev/null

./gradlew --no-daemon --console=plain \
  :app:testDebugUnitTest \
  :app:assembleDebug \
  :app:assembleDebugAndroidTest

Каркас шага сборки. --no-daemon в CI экономит память и убирает залипшие демоны между джобами.

# отчёты, которые стоит забирать артефактами
app/build/reports/tests/                      # unit-тесты, HTML
app/build/reports/androidTests/connected/      # инструментальные
app/build/outputs/apk/debug/                   # сам apk
app/build/outputs/logs/                        # логи AGP

Без сохранённых отчётов разбор упавшего прогона превращается в гадание.

./gradlew lint
./gradlew :app:lintDebug
# отчёт: app/build/reports/lint-results-debug.html

Android Lint. В CI полезно ставить abortOnError и белый список, иначе предупреждения копятся годами.

СимптомПричина
Failed to install the following SDK componentsне приняты лицензии, нужен sdkmanager --licenses
Minimum supported Gradle version is 8.11старый wrapper, править gradle-wrapper.properties
No matching client found for package nameapplicationId не совпадает с тем, что в google-services.json
INSTALL_FAILED_UPDATE_INCOMPATIBLEдругая подпись — снести приложение перед установкой
AAB не ставится на устройствонужен bundletool, напрямую AAB не устанавливается
Сборка падает только в CIразный JDK; зафиксировать jvmToolchain
Эмулятор не стартует в CIнет KVM или нужен -gpu swiftshader_indirect