ADB в языках программирования

Рейтинг: 73% · 17 голосов
Полный курс по Android Debug Bridge: установка, подключение, shell, логи, dumpsys, автоматизация, root, беспроводная отладка. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
android_roman
Сообщения: 45
Зарегистрирован: 11 май 2026, 05:31

ADB в языках программирования

Сообщение android_roman »

АкадемияADB: Android Debug BridgeГлава 23 из 29
Оглавление курса (29)
  1. Введение в Android Debug Bridge
  2. Установка и настройка рабочей среды
  3. Подключение устройства (проводное и беспроводное)
  4. Базовые команды ADB и управление сервером
  5. Команды состояния и перезагрузки
  6. Навигация по файловой системе
  7. Управление пакетами приложений
  8. Логирование с помощью logcat
  9. Системные дампы и диагностика (dumpsys)
  10. Анализ производительности в реальном времени
  11. Эмуляция ввода (input)
  12. Управление Activity и Intent (am)
  13. Работа с оконным менеджером (wm)
  14. Захват экрана и запись видео
  15. Root-доступ и его применение
  16. Модификация системных настроек через settings
  17. Команды для поставщиков контента (content)
  18. Резервное копирование и восстановление (backup)
  19. Проброс портов и туннелирование
  20. Беспроводная отладка (Wi-Fi)
  21. Взаимодействие с эмуляторами
  22. Написание скриптов на Bash/CMD/PowerShell
  23. ADB в языках программирования (вы здесь)
  24. Автоматизация тестирования с ADB
  25. Безопасность и лучшие практики
  26. ADB на Android TV, Wear OS и IoT
  27. Восстановление и низкоуровневые операции
  28. Расширенные возможности оболочки и инструменты
  29. Отладка самого ADB и устранение неполадок
В главе 22 мы писали скрипты на Bash и PowerShell. Они хороши для линейных сценариев, но как только задача звучит как "прогнать смоук на 30 устройствах параллельно и собрать скриншоты в отчет", нужен полноценный язык: структуры данных, потоки, обработка ошибок, интеграция с CI. Разберем, как дергать ADB из Python, Node.js, Java/Kotlin и через REST.

Три способа работать с ADB из кода:

Способы принципиально разные, и от выбора зависит вся архитектура.

Первый: запускать бинарник adb как внешний процесс. Просто и надежно, но на каждый вызов тратится время на запуск процесса (10-50 мс), а вывод приходится парсить как текст.

Второй: общаться с adb-сервером напрямую по его TCP-протоколу на порту 5037 (мы трогали его в главах 4 и 19). Бинарник нужен один раз, чтобы поднять сервер командой

Код: Выделить всё

adb start-server
, дальше библиотека сама ходит в сокет. Так работают pure-python-adb и adbkit.

Третий: реализовать сам протокол ADB и говорить с adbd на устройстве напрямую, вообще без установленного adb. Нужны RSA-ключи для авторизации, зато ноль внешних зависимостей. Так работает adb-shell.

Python, обертки над subprocess:

Базовый и самый живучий вариант. Минимальная обертка, которую я таскаю из проекта в проект:

Код: Выделить всё

import subprocess

def adb(*args, serial=None, timeout=30):
    cmd = ["adb"]
    if serial:
        cmd += ["-s", serial]
    cmd += list(args)
    res = subprocess.run(cmd, capture_output=True, text=True,
                         encoding="utf-8", errors="replace",
                         timeout=timeout)
    if res.returncode != 0:
        raise RuntimeError(f"{' '.join(cmd)}: {res.stderr.strip()}")
    return res.stdout

print(adb("shell", "getprop", "ro.build.version.release",
          serial="R58M42ABCDE").strip())
Вывод:
Частые грабли. На русской Windows без явного

Код: Выделить всё

encoding="utf-8"
subprocess декодирует вывод в cp1251, и русские названия приложений превращаются в кракозябры: adb всегда отдает UTF-8. Всегда передавайте аргументы списком, а не строкой с

Код: Выделить всё

shell=True
, иначе получите проблемы с кавычками и дыру для инъекций. И помните: коды возврата команд внутри

Код: Выделить всё

adb shell
корректно пробрасываются только при протоколе shell v2 (adb 1.0.36+, устройство на Android 7+), на музейных девайсах adb вернет 0 даже при упавшей команде.

Бинарные данные тяните через exec-out, а не shell:

Код: Выделить всё

png = subprocess.run(["adb", "exec-out", "screencap", "-p"],
                     capture_output=True, timeout=30).stdout
open("screen.png", "wb").write(png)
Python, pure-python-adb:

Библиотека ppadb говорит с adb-сервером по сокету 5037:

Код: Выделить всё

pip install pure-python-adb

Код: Выделить всё

from ppadb.client import Client as AdbClient

client = AdbClient(host="127.0.0.1", port=5037)
print(client.version())   # 41, версия протокола сервера

for device in client.devices():
    print(device.serial, device.shell("getprop ro.product.model").strip())

device = client.device("R58M42ABCDE")
device.install("app-debug.apk")
with open("screen.png", "wb") as f:
    f.write(device.screencap())
Вывод:

Код: Выделить всё

41
R58M42ABCDE SM-A546E
emulator-5554 sdk_gphone64_x86_64
Если сервер не запущен, получите ConnectionRefusedError: библиотека сама его не поднимает, сначала

Код: Выделить всё

adb start-server
. Честное предупреждение: последний релиз ppadb вышел в 2020 году, проект заморожен. Работает он до сих пор, потому что протокол adb-сервера не менялся, но для нового кода я бы взял живой аналог adbutils (его развивает команда openatx, он же лежит под uiautomator2):

Код: Выделить всё

import adbutils
d = adbutils.adb.device()    # первое устройство в списке
print(d.shell("getprop ro.product.model"))
Python, adb-shell:

Эта библиотека реализует протокол ADB сама и подключается к adbd напрямую, без бинарника и сервера. На ней построена интеграция Android TV в Home Assistant. Для авторизации нужны те же RSA-ключи, что описаны в главе 3, проще всего переиспользовать ключ хоста:

Код: Выделить всё

pip install adb-shell

Код: Выделить всё

import os
from adb_shell.adb_device import AdbDeviceTcp
from adb_shell.auth.sign_pythonrsa import PythonRSASigner

key = os.path.expanduser("~/.android/adbkey")
with open(key) as f:
    priv = f.read()
with open(key + ".pub") as f:
    pub = f.read()
signer = PythonRSASigner(pub, priv)

device = AdbDeviceTcp("192.168.1.42", 5555, default_transport_timeout_s=9.0)
device.connect(rsa_keys=[signer], auth_timeout_s=10.0)
print(device.shell("getprop ro.build.version.sdk").strip())   # 35
device.push("config.json", "/sdcard/Download/config.json")
device.close()
Нюанс: режим сопряжения беспроводной отладки из Android 11+ (глава 20) библиотека не реализует, поэтому порт 5555 на устройстве включается классикой,

Код: Выделить всё

adb tcpip 5555
из USB-сессии. Для USB-подключения ставьте

Код: Выделить всё

pip install adb-shell[usb]
и класс AdbDeviceUsb, понадобится libusb.

JavaScript и Node.js, adbkit:

Оригинальный пакет adbkit от OpenSTF заброшен, живой форк поддерживает команда DeviceFarmer, ставьте именно его:

Код: Выделить всё

npm install @devicefarmer/adbkit

Код: Выделить всё

const { Adb } = require('@devicefarmer/adbkit');
const client = Adb.createClient();   // сервер на 127.0.0.1:5037

async function main() {
  const devices = await client.listDevices();
  for (const d of devices) {
    const device = client.getDevice(d.id);
    const stream = await device.shell('getprop ro.build.version.release');
    const out = await Adb.util.readAll(stream);
    console.log(`${d.id}: Android ${out.toString().trim()}`);
  }
}
main();
Вывод:

Код: Выделить всё

R58M42ABCDE: Android 15
emulator-5554: Android 14
Сильная сторона adbkit, событийная модель. Трекер устройств реагирует на подключения без поллинга:

Код: Выделить всё

const tracker = await client.trackDevices();
tracker.on('add',    d => console.log('подключено:', d.id));
tracker.on('remove', d => console.log('отвалилось:', d.id));
Установка APK и скриншот в файл:

Код: Выделить всё

const device = client.getDevice('R58M42ABCDE');
await device.install('app-debug.apk');
const png = await device.screencap();
png.pipe(require('fs').createWriteStream('screen.png'));
Electron:

Десктопные утилиты для Android (GUI-обертки над scrcpy, менеджеры прошивок) часто пишут на Electron. Правила простые. Весь код с adb живет в главном процессе, рендерер получает данные через IPC, включать nodeIntegration ради adb нельзя. Бинарники platform-tools под каждую ОС кладут в extraResources сборщика (electron-builder) и ищут относительно ресурсов приложения:

Код: Выделить всё

const { app } = require('electron');
const { execFile } = require('child_process');
const path = require('path');

const adbPath = app.isPackaged
  ? path.join(process.resourcesPath, 'platform-tools', 'adb')
  : 'adb';

function adbDevices() {
  return new Promise((resolve, reject) => {
    execFile(adbPath, ['devices', '-l'], (err, stdout) =>
      err ? reject(err) : resolve(stdout));
  });
}
Отдельная экзотика: библиотека @yume-chan/adb (проект Tango) реализует протокол ADB на TypeScript поверх WebUSB и работает в Chrome и Electron вообще без бинарника, на ней сделаны веб-клиенты scrcpy. Помните только, что USB-интерфейс эксклюзивен: пока локальный adb-сервер держит устройство, WebUSB его не получит, сначала

Код: Выделить всё

adb kill-server
.

Java и Kotlin, внешний процесс:

В JVM-мире штатный путь, ProcessBuilder. Главная ловушка: если не вычитывать stdout, процесс встанет насмерть, когда заполнится пайп (около 64 КБ). Поэтому сначала читаем поток до конца, потом waitFor:

Код: Выделить всё

import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

public class AdbRunner {
    public static String run(String... args) throws Exception {
        String[] cmd = new String[args.length + 1];
        cmd[0] = "adb";
        System.arraycopy(args, 0, cmd, 1, args.length);
        ProcessBuilder pb = new ProcessBuilder(cmd);
        pb.redirectErrorStream(true);
        Process p = pb.start();
        String out = new String(p.getInputStream().readAllBytes(),
                                StandardCharsets.UTF_8);
        if (!p.waitFor(30, TimeUnit.SECONDS)) {
            p.destroyForcibly();
            throw new RuntimeException("adb не ответил за 30 секунд");
        }
        return out;
    }
}
На Kotlin то же самое компактнее:

Код: Выделить всё

import java.util.concurrent.TimeUnit

fun adb(vararg args: String, timeoutSec: Long = 30): String {
    val proc = ProcessBuilder("adb", *args)
        .redirectErrorStream(true)
        .start()
    val out = proc.inputStream.bufferedReader(Charsets.UTF_8).readText()
    if (!proc.waitFor(timeoutSec, TimeUnit.SECONDS)) {
        proc.destroyForcibly()
        error("adb не ответил за $timeoutSec секунд")
    }
    return out.trim()
}

fun main() {
    println(adb("-s", "R58M42ABCDE", "shell", "dumpsys", "battery"))
}
В CI указывайте полный путь к adb или гарантируйте PATH для JVM-процесса: Gradle-демон может стартовать с другим окружением, чем ваш терминал, и "command not found" в логах CI при работающем локально коде, классика. Для серьезных инструментов есть ddmlib (артефакт com.android.tools.ddms:ddmlib), та самая библиотека, через которую с adb-сервером общается Android Studio: классы AndroidDebugBridge и IDevice, executeShellCommand с колбэком. Мощно, но API не считается стабильным и ломается между версиями.

REST-обертки и фермы устройств:

Когда устройства висят на отдельной машине (а в командах СНГ это обычно сервер в офисе с USB-хабом), удобно поднять над ними HTTP API. Самодельная обертка на Flask с жестким белым списком действий:

Код: Выделить всё

from flask import Flask, jsonify, abort
import subprocess

app = Flask(__name__)
ALLOWED = {
    "battery":  ["shell", "dumpsys", "battery"],
    "packages": ["shell", "pm", "list", "packages", "-3"],
}

@app.route("/device/<serial>/<action>")
def run_action(serial, action):
    if action not in ALLOWED:
        abort(404)
    res = subprocess.run(["adb", "-s", serial] + ALLOWED[action],
                         capture_output=True, text=True, timeout=30)
    return jsonify(stdout=res.stdout, code=res.returncode)
Белый список здесь не паранойя: эндпоинт вида "выполни произвольную команду" равен удаленному шеллу на всех подключенных телефонах. Наружу такое не выставляют никогда, подробнее в главе 25.

Для ферм посерьезнее есть STF (Smartphone Test Farm). Оригинальный OpenSTF заброшен, развивается форк DeviceFarmer/stf, разворачивается через Docker и дает веб-интерфейс с управлением устройством из браузера плюс REST API. Токен берется в настройках профиля (Keys, Access Tokens). Типовой цикл аренды устройства:

Код: Выделить всё

curl -s -H "Authorization: Bearer $STF_TOKEN" \
  https://stf.lan/api/v1/devices | jq '.devices[] | {serial, model, present}'

curl -s -X POST -H "Authorization: Bearer $STF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"serial": "R58M42ABCDE"}' \
  https://stf.lan/api/v1/user/devices

curl -s -X POST -H "Authorization: Bearer $STF_TOKEN" \
  https://stf.lan/api/v1/user/devices/R58M42ABCDE/remoteConnect
Последний запрос вернет что-то вроде:

Код: Выделить всё

{"success": true, "remoteConnectUrl": "stf.lan:7404"}
Дальше обычное сетевое подключение из главы 3:

Код: Выделить всё

adb connect stf.lan:7404
adb -s stf.lan:7404 shell getprop ro.product.model
После работы устройство освобождают:

Код: Выделить всё

curl -s -X DELETE -H "Authorization: Bearer $STF_TOKEN" \
  https://stf.lan/api/v1/user/devices/R58M42ABCDE
Этот же API отлично дергается из любого языка выше: Python-скрипт арендует девайс в STF, подключается через adb connect, гоняет тесты, отпускает. Ровно такую связку мы соберем в главе 24, где займемся автоматизацией тестирования.
👍3 ❤️3 🔥2 😄 🤔1
✔ Лучший ответ сформирован автоматически — madmaxim
android_roman писал(а):signer = PythonRSASigner(pub, priv) а если на машине вообще никогда не стоял adb и ~/.android/adbkey тупо нет? контейнер с голым питоном, ставить platform-tools ради одного ключа жирно. Покопался в исходниках adb-shell, там есть adb_shell.auth.keygen с функцией keygen, генерит пару сама. Может в урок добавить, я минут 20 потерял пока нашел
Перейти к ответу →
Аватара пользователя
madmaxim
Сообщения: 1
Зарегистрирован: 23 май 2026, 11:25

Re: ADB в языках программирования

Сообщение madmaxim »

✔ Лучший ответ — сформирован автоматически
android_roman писал(а):signer = PythonRSASigner(pub, priv)
а если на машине вообще никогда не стоял adb и ~/.android/adbkey тупо нет? контейнер с голым питоном, ставить platform-tools ради одного ключа жирно. Покопался в исходниках adb-shell, там есть adb_shell.auth.keygen с функцией keygen, генерит пару сама. Может в урок добавить, я минут 20 потерял пока нашел
👍1 ❤️ 🔥 😄 🤔
Аватара пользователя
sipher
Сообщения: 1
Зарегистрирован: 28 май 2026, 08:13

Re: ADB в языках программирования

Сообщение sipher »

поймал ровно те кракозябры на win11. прикол в том что в терминале pm list packages выводится нормально, а из питона мусор даже local в utf8. encoding=utf-8 в subprocess.run починил, но осадочек остался. кто-нибудь знает почему powershell сам по себе норм декодирует?
👍2 ❤️ 🔥 😄 🤔
Аватара пользователя
terraformsre
Сообщения: 1
Зарегистрирован: 30 май 2026, 04:47

Re: ADB в языках программирования

Сообщение terraformsre »

подтверждаю про adbkit, не ставьте старый пакет adbkit с npm, он годами без коммитов и типов нет. перешел на @devicefarmer/adbkit, апи почти один в один, только импорты поправить. еще заметил что trackDevices иногда молча умирает после adb kill-server, лечу пересозданием клиента по событию end
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
lastdead
Сообщения: 1
Зарегистрирован: 11 май 2026, 03:51

Re: ADB в языках программирования

Сообщение lastdead »

а у вас remoteConnectUrl из STF наружу нормально ходит? у нас ферма за nginx и adb connect на порты 7400+ не пролезал, проксируется ведь только веб. пришлось у админов отдельно просить диапазон портов провайдера, в доке деплоя это легко проглядеть
👍3 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Написание скриптов на Bash/CMD/PowerShell
Следующая глава →
Автоматизация тестирования с ADB

Все главы курса «ADB: Android Debug Bridge»

Поделиться темой: ✈ Telegram VK

Вернуться в «ADB: Android Debug Bridge»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость