Примеры рассчитаны на свежие Platform Tools, проверьте версию:
Код: Выделить всё
$ adb --version
Android Debug Bridge version 1.0.41
Version 37.0.0-14910828
Installed as /opt/platform-tools/adb
Три типичные причины. Первая: к машине подключено больше одного устройства, и любая команда без флага -s падает:
Код: Выделить всё
$ adb shell getprop ro.build.version.release
adb: more than one device/emulator
Ожидание устройства:
Базовая команда блокируется, пока транспорт не перейдёт в состояние device:
Код: Выделить всё
adb wait-for-device
Код: Выделить всё
adb wait-for-usb-device # только физическое USB-устройство
adb -s R58M42ABCDE wait-for-device # ждать конкретный серийник
adb wait-for-disconnect # дождаться отключения
Код: Выделить всё
adb wait-for-device shell 'while [ "$(getprop sys.boot_completed)" != "1" ]; do sleep 1; done'
Код: Выделить всё
timeout 120 adb wait-for-device || { echo "device not found" >&2; exit 1; }
Код: Выделить всё
$j = Start-Job { adb wait-for-device }
if (-not (Wait-Job $j -Timeout 120)) { Stop-Job $j; Write-Error "device not found"; exit 1 }
Источник правды, вывод adb devices. Первая строка заголовок, дальше пары "серийник состояние". Берём только строки с состоянием device, а offline и unauthorized пропускаем:
Код: Выделить всё
$ adb devices
List of devices attached
R58M42ABCDE device
emulator-5554 device
192.168.1.42:42715 unauthorized
Код: Выделить всё
#!/usr/bin/env bash
set -euo pipefail
for serial in $(adb devices | awk 'NR>1 && $2=="device" {print $1}'); do
echo "== $serial =="
adb -s "$serial" shell getprop ro.product.model
done
Код: Выделить всё
== R58M42ABCDE ==
SM-A546E
== emulator-5554 ==
sdk_gphone64_x86_64
Код: Выделить всё
export ANDROID_SERIAL=R58M42ABCDE
adb shell getprop ro.product.model # уйдёт на R58M42ABCDE
Код: Выделить всё
@echo off
for /f "skip=1 tokens=1,2" %%a in ('adb devices') do (
if "%%b"=="device" (
echo == %%a ==
adb -s %%a shell getprop ro.product.model
)
)
Код: Выделить всё
$devices = adb devices | Select-String "^(\S+)\s+device$" |
ForEach-Object { $_.Matches[0].Groups[1].Value }
foreach ($d in $devices) {
adb -s $d shell getprop ro.product.model
}
Каждый Bash-скрипт начинайте со строгого режима:
Код: Выделить всё
set -euo pipefail
Код: Выделить всё
$ adb shell false; echo $?
1
Код: Выделить всё
out=$(adb shell 'pm clear com.example.app; echo RC=$?' | tr -d '\r')
case "$out" in *"RC=0"*) echo ok ;; *) echo "fail: $out" >&2; exit 1 ;; esac
Код: Выделить всё
res=$(adb -s "$serial" install -r -g app-release.apk 2>&1)
if [[ "$res" == *"Success"* ]]; then
echo "$serial: ok"
else
echo "$serial: FAILED: $res" >&2
fi
Код: Выделить всё
adb install -r app-release.apk
if errorlevel 1 (
echo install failed
exit /b 1
)
Код: Выделить всё
adb install -r app-release.apk
if ($LASTEXITCODE -ne 0) { Write-Error "install failed"; exit 1 }
Парсинг вывода adb:
Правило первое: срезайте \r, если строка пойдёт в сравнение или в имя файла:
Код: Выделить всё
ver=$(adb shell getprop ro.build.version.release | tr -d '\r')
Код: Выделить всё
$ adb shell pm list packages -3 | tr -d '\r' | sed 's/^package://'
com.example.app
org.telegram.messenger
ru.yandex.searchplugin
Код: Выделить всё
$ adb shell dumpsys package com.example.app | grep -m1 versionName
versionName=3.14.1
Код: Выделить всё
$ adb shell pidof -s com.example.app
12843
$ adb shell dumpsys battery | awk '/level:/ {print $2}'
87
Скрипт деплоя: собрать дебаг-сборку, поставить, запустить главную Activity. Имя Activity не хардкодим, спрашиваем у системы (cmd package resolve-activity доступен на Android 7.0 и новее):
Код: Выделить всё
#!/usr/bin/env bash
set -euo pipefail
PKG=com.example.app
APK=app/build/outputs/apk/debug/app-debug.apk
./gradlew assembleDebug
adb wait-for-device
adb install -r -t -g "$APK"
ACT=$(adb shell cmd package resolve-activity --brief "$PKG" | tail -n 1 | tr -d '\r')
adb shell am start -n "$ACT"
Код: Выделить всё
Performing Streamed Install
Success
Starting: Intent { cmp=com.example.app/.MainActivity }
Сценарий: воспроизвели баг, одним скриптом собрали комплект для тикета. Логи фильтруем по PID приложения (фильтр --pid работает с Android 7.0), чтобы не тащить чужой шум:
Код: Выделить всё
#!/usr/bin/env bash
set -euo pipefail
PKG=${1:?usage: grab_logs.sh <package>}
DIR=logs_$(date +%Y%m%d_%H%M%S)
mkdir -p "$DIR"
PID=$(adb shell pidof -s "$PKG" | tr -d '\r' || true)
if [[ -n "$PID" ]]; then
adb logcat -d -v threadtime --pid="$PID" > "$DIR/app.log"
fi
adb logcat -d -v threadtime -b crash > "$DIR/crash.log"
adb shell dumpsys meminfo "$PKG" > "$DIR/meminfo.txt"
adb exec-out screencap -p > "$DIR/screen.png"
echo "saved to $DIR"
Пример: полный сброс приложения:
Состояние "как после чистой установки" без переустановки APK. pm clear сносит данные, кэш и отзывает runtime-разрешения, поэтому для автотестов нужные разрешения возвращаем сразу, чтобы не упереться в системные диалоги:
Код: Выделить всё
#!/usr/bin/env bash
set -euo pipefail
PKG=${1:?usage: app_reset.sh <package> [serial]}
SERIAL=${2:-}
ADB=(adb); [[ -n "$SERIAL" ]] && ADB=(adb -s "$SERIAL")
"${ADB[@]}" shell am force-stop "$PKG"
out=$("${ADB[@]}" shell pm clear "$PKG" | tr -d '\r')
[[ "$out" == "Success" ]] || { echo "pm clear failed: $out" >&2; exit 1; }
"${ADB[@]}" shell pm grant "$PKG" android.permission.POST_NOTIFICATIONS
"${ADB[@]}" shell pm grant "$PKG" android.permission.ACCESS_FINE_LOCATION
echo "reset done: $PKG"
Пример: батч-установка APK:
Задача из жизни тестовой лаборатории или сервис-центра: каталог с APK, парк устройств, поставить всё на всех и получить сводку. Здесь сознательно нет set -e, ошибки обрабатываем сами и не роняем весь прогон из-за одного файла:
Код: Выделить всё
#!/usr/bin/env bash
set -uo pipefail
shopt -s nullglob
APKS=(apk/*.apk)
[[ ${#APKS[@]} -gt 0 ]] || { echo "no apk found" >&2; exit 1; }
fail=0
for serial in $(adb devices | awk 'NR>1 && $2=="device" {print $1}'); do
for apk in "${APKS[@]}"; do
if out=$(adb -s "$serial" install -r -g "$apk" 2>&1) && [[ "$out" == *Success* ]]; then
echo "[$serial] $(basename "$apk"): OK"
else
echo "[$serial] $(basename "$apk"): FAIL ($out)" >&2
fail=1
fi
done
done
exit $fail
Код: Выделить всё
[R58M42ABCDE] telegram-11.9.0.apk: OK
[R58M42ABCDE] 2gis-6.48.apk: OK
[emulator-5554] telegram-11.9.0.apk: FAIL (adb: failed to install apk/telegram-11.9.0.apk: Failure [INSTALL_FAILED_NO_MATCHING_ABIS])
Код: Выделить всё
adb install-multiple base.apk split_config.arm64_v8a.apk split_config.ru.apk
adb install-multi-package app1.apk app2.apk app3.apk
Код: Выделить всё
@echo off
setlocal
set FAIL=0
for %%f in (apk\*.apk) do (
adb install -r -g "%%f" >nul 2>&1
if errorlevel 1 (
echo %%f: FAIL
set FAIL=1
) else (
echo %%f: OK
)
)
exit /b %FAIL%
Команда без -s при двух устройствах: в скриптах либо -s везде, либо ANDROID_SERIAL. Вечное зависание wait-for-device: устройство unauthorized или offline, ставьте timeout и при срабатывании печатайте adb devices в лог. Битый парсинг: забытый tr -d '\r'. Установка сразу после ребута: ждите sys.boot_completed, а не только wait-for-device. И не вписывайте adb kill-server в скрипты: на CI-агенте, где параллельно крутятся несколько джобов, один скрипт убьёт сервер под другим, а все они делят порт 5037 (подробнее в главе 29).
В следующей главе решим те же задачи из Python и Kotlin, где парсить текст почти не придётся, а в главе 24 соберём из этих кирпичей полноценную тестовую автоматизацию.