ZennoPoster + Android Studio AVD: нативная автоматизация Android без посредников

  • Автор темы Автор темы kolina
  • Дата начала Дата начала

kolina

Client
Регистрация
05.10.2019
Сообщения
188
Реакции
94
Баллы
28
О чём статья

  • Как напрямую управлять Android-эмулятором Android Studio AVD из ZennoPoster (через adb).
  • Как нажимать, свайпить, печатать и снимать UI без Appium и сторонних «прокладок».
  • Как стабильно работать с «прожорливыми» мессенджерами (пример: Telegram).
  • Готовые сниппеты C# для вставки в ZennoPoster, чек-листы стабильности и отладки.

    Zenno.png

Почему AVD (Android Studio) + ZennoPoster

AVD — официальный эмулятор Android. Он:
  • максимально совместим с новыми API/SDK;
  • не тащит лишний софт/рекламу;
  • предсказуем по координатам и поведению ввода;
  • поддерживает «родной» инструментарий (ADB, UIAutomator).
ZennoPoster — мощный раннер с визуальными блоками и C#:
  • легко миксует HTTP/API-шаги и мобильную автоматизацию;
  • хранит состояния (Таблицы/Списки/Переменные);
  • позволяет писать «тонкие» C#-обработчики для adb/UI.

Итог: минимальная связка, высокая скорость, гибкость и полная управляемость.

Архитектура решения:
Развернуть Свернуть Копировать
ZennoPoster (C# блоки)
       │
       ├─ запускает adb.exe с нужными аргументами
       │
       ├─ отправляет команды ввода:
       │    input tap / text / swipe / keyevent
       │
       ├─ снимает разметку экрана:
       │    uiautomator dump → /sdcard/ui.xml → cat
       │
       └─ парсит XML (bounds, class, text, content-desc)

Быстрый старт (чек-лист)
  1. Поставьте Android StudioSDK Platform-Tools (там adb.exe).
    Пример пути:
    C:\Users\User\AppData\Local\Android\Sdk\platform-tools\adb.exe
  2. Создайте AVD (напр., Pixel 7 / Android 14–15). Запустите эмулятор.
    В adb devices -l увидите что-то вроде emulator-5554.
  3. Подготовьте ZennoPoster: блок C# + ваш путь к adb и serial AVD.
  4. Проверьте команду:
cmd:
Развернуть Свернуть Копировать
adb -s emulator-5554 shell getprop ro.product.model

Базовые операции: готовые сниппеты
1) Быстрый вызов adb из C# блока ZennoPoster

C#:
Развернуть Свернуть Копировать
string adb = @"C:\Users\User\AppData\Local\Android\Sdk\platform-tools\adb.exe";
string serial = "emulator-5554";
string RunAdb(string args, int timeoutMs = 1500)
{
    var psi = new System.Diagnostics.ProcessStartInfo {
        FileName = adb, Arguments = args,
        UseShellExecute = false, CreateNoWindow = true,
        RedirectStandardOutput = true, RedirectStandardError = true,
        StandardOutputEncoding = System.Text.Encoding.UTF8,
        StandardErrorEncoding  = System.Text.Encoding.UTF8
    };
    using var p = new System.Diagnostics.Process { StartInfo = psi };
    p.Start();
    if (!p.WaitForExit(timeoutMs)) { try { p.Kill(); } catch {} }
    var err = p.StandardError.ReadToEnd();
    if (!string.IsNullOrWhiteSpace(err) && !err.Contains("dumped to"))
        project.SendWarningToLog("[ADB] " + err);
    return p.StandardOutput.ReadToEnd();
}

2) Ввод: тап, текст, свайп, «назад»

C#:
Развернуть Свернуть Копировать
void Tap(int x, int y) =>
    RunAdb($"-s {serial} shell input tap {x} {y}");

void TypeText(string text) =>
    RunAdb($"-s {serial} shell input text {text}");

void Swipe(int x1,int y1,int x2,int y2,int ms=150) =>
    RunAdb($"-s {serial} shell input swipe {x1} {y1} {x2} {y2} {ms}");

void Back() => RunAdb($"-s {serial} shell input keyevent KEYCODE_BACK");

3) Снятие UI без зависаний

C#:
Развернуть Свернуть Копировать
string DumpUI()
{
    RunAdb($"-s {serial} shell uiautomator dump /sdcard/ui.xml");
    return RunAdb($"-s {serial} shell cat /sdcard/ui.xml");
}

Как «читать» экран: парсим UIAutomator XML

В DumpUI() приходит XML со структурой элементов. Можно искать:
  • @class (например, androidx.recyclerview.widget.RecyclerView);
  • @text — текст у виджета (часто у «листовых» сообщений);
  • @Content-desc — подпись для доступности (иконки, FAB);
  • @bounds — координаты [x1,y1][x2,y2].
Пример извлечения текстов сообщений из ленты (RecyclerView):

C#:
Развернуть Свернуть Копировать
List<string> ExtractMessages(string xml)
{
    var res = new List<string>();
    var m = System.Text.RegularExpressions.Regex.Match(xml ?? "", "(<hierarchy[\\s\\S]*?</hierarchy>)");
    if (!m.Success) return res;

    var doc = new System.Xml.XmlDocument();
    doc.LoadXml(m.Groups[1].Value);

    // Многие мессенджеры кладут «текст сообщения» прямо в text узлов ViewGroup
    var nodes = doc.SelectNodes("//node[@class='androidx.recyclerview.widget.RecyclerView']/node[@class='android.view.ViewGroup' and @text!='']");
    if (nodes != null)
        foreach (System.Xml.XmlNode n in nodes)
            res.Add(n.Attributes?["text"]?.Value ?? "");

    return res;
}
Примечание: структура у разных приложений может отличаться — смотрите реальный XML (project.Variables["test"] = DumpUI();-) и корректируйте XPath.

Практика на примере Telegram
Переход в канал/группу по схеме «deeplink + intent»
C#:
Развернуть Свернуть Копировать
// Жёстко: Telegram уже установлен в профиле AVD с Play Services.
string tgGroup = "@gruz"; // или t.me/...
RunAdb($"-s {serial} shell am start -a android.intent.action.VIEW -d https://t.me/{tgGroup.TrimStart('@')}");

Если Telegram уже открыт поверх — система часто пишет:
Activity not started, intent has been delivered to currently running top-most instance.
Это норм: приложение получило интент и само переключило экран.

«Прыгнуть к последним» (если есть FAB)
  • снимаем XML → ищем content-desc/text с ключами типа «в конец», «последним», «jump», «new message»;
  • тапаем в центр найденной кнопки (по bounds);
  • если не нашли — делаем пару свайпов вверх по левому краю (чтобы избежать случайного «поделиться/реакции»).
Сбор сообщений
Подходы:
  1. Скроллить и парсить каждую «порцию» UI (быстро, но зависит от верстки).
  2. Не скроллить, а ждать 30 сек и каждые N секунд снимать UI (полезно для «живых» каналов).
Оба подхода мы успешно использовали. В «скроллинговом» — помогает:
  • свайпать по левому краю,
  • не делать слишком короткие свайпы (иначе эмулятор считает их tap),
  • избегать долгих свайпов (>500 мс), чтобы не сработал long-press.

«Анти-ловушки» и лайфхаки стабильности
  • Шторка «Поделиться». При слишком длинном/правом свайпе может выпадать системный «chooser».
    Решение: свайпать по левому краю, при детекте «resolver/chooser/share» делать KEYCODE_BACK.
  • Залипания uiautomator. Команда exec-out uiautomator dump иногда «виснет».
    Решение: использовать «надёжную» схему
    uiautomator dump /sdcard/ui.xml → cat /sdcard/ui.xml.
  • Тайминги. Избыточные Thread.Sleep(1000+) убивают скорость.
    На практике хватает 30–120 мс между жестами, 20–50 мс перед дампом.
  • Координаты. Нормируйте от размеров экрана (wm size), а не хардкод.
  • Кодировка. Если видите «кракозябры», попробуйте «перекинуть» как 1251/1252→UTF-8 (простые фиксы в коде).

Когда нужен Appium / UIAutomator2
  • Нужны локаторы по ресурсу/иерархии и стабильные клики без координат.
  • Сложные сценарии с ожиданиями и взаимодействием изнутри Android (Accessibility).
  • Мульти-эмуляторы и тонкий контроль времени анимаций.
Но для массы задач (чтение ленты, «снять последние X», базовые клики/свайпы) ADB-подход проще, быстрее и не требует лишних слоёв.

Готовый «скелет» сценария (свайп + сбор)

C#:
Развернуть Свернуть Копировать
// 1) уже перешли в группу (интентом) и «провалились в самый низ» другой подзадачей
// 2) здесь: делаем 10 свайпов «в историю» и собираем тексты как есть

IZennoList outList = project.Lists["Список 1"];
outList.Clear();

string adb = @"C:\Users\User\AppData\Local\Android\Sdk\platform-tools\adb.exe";
string serial = "emulator-5554";

string RunAdb(string args, int t=900) { /* из блока выше */ return ""; }
void Swipe(int x1,int y1,int x2,int y2,int ms=160) => RunAdb($"-s {serial} shell input swipe {x1} {y1} {x2} {y2} {ms}");
string DumpUI(){ RunAdb($"-s {serial} shell uiautomator dump /sdcard/ui.xml"); return RunAdb($"-s {serial} shell cat /sdcard/ui.xml"); }

(int W,int H) GetWH(){
  var m = System.Text.RegularExpressions.Regex.Match(RunAdb($"-s {serial} shell wm size"), @"(\d+)\s*x\s*(\d+)");
  return m.Success ? (int.Parse(m.Groups[1].Value), int.Parse(m.Groups[2].Value)) : (1080,2400);
}
var (W,H) = GetWH();
int xL = (int)(W*0.08);
int y1 = (int)(H*0.12), y2 = (int)(H*0.90);

System.Collections.Generic.List<string> Extract(string xml){
  var res = new System.Collections.Generic.List<string>();
  var mm = System.Text.RegularExpressions.Regex.Match(xml ?? "", "(<hierarchy[\\s\\S]*?</hierarchy>)");
  if (!mm.Success) return res;
  var doc = new System.Xml.XmlDocument(); doc.LoadXml(mm.Groups[1].Value);
  var nodes = doc.SelectNodes("//node[@class='androidx.recyclerview.widget.RecyclerView']/node[@class='android.view.ViewGroup' and @text!='']");
  if (nodes!=null) foreach(System.Xml.XmlNode n in nodes) res.Add(n.Attributes?["text"]?.Value ?? "");
  return res;
}

// текущий кадр + 10 свайпов
var seen = new System.Collections.Generic.HashSet<string>();
for (int i=0;i<11;i++){
  var xml = DumpUI();
  foreach (var t in Extract(xml))
    if (!string.IsNullOrWhiteSpace(t) && seen.Add(t)) outList.Add(t);

  if (i==10) break;       // дефолт: 10 свайпов
  Swipe(xL, y1, xL, y2);  // вниз (в историю)
}

project.SendInfoToLog($"[OK] Собрано: {outList.Count}");
return 0;

Диагностика и логирование (чек-лист)
  • Писать в лог путь к adb, serial, размеры экрана.
  • Хранить сырой XML последнего DumpUI() в переменной test для быстрой отладки XPath.
  • Фиксировать «подозрительные» stderr из adb (но не ругаться на «dumped to /sdcard/ui.xml»).
  • Если что-то «случайно нажимается» — перенести жесты ещё левее и чуть удлинить ms.

Безопасность и этика
  • Уважайте правила площадок и приватность пользователей.
  • Не автоматизируйте действий, запрещённых ToS/законами.
  • Работайте в тестовых/демо-окружениях и своих аккаунтах.
FAQ
Почему иногда пусто в списке сообщений?
Чаще всего — из-за XPath (у вашего приложения другой класс/расположение текста). Сохраните DumpUI() в переменную, откройте XML и подстройте SelectNodes().
Почему срабатывает «Поделиться/реакция»?
Короткий/правый свайп похож на tap/long-press. Свайпайте слева и держите длительность ~140–200 мс.
uiautomator dump зависает.
Используйте «через файл»: dump /sdcard/ui.xml → cat /sdcard/ui.xml.
Нужно ли Appium?
Если нужен надёжный поиск по resource-id, сложные ожидания внутри активности или кросс-девайс сценарии — да. Для «читать/листать/нажимать» чаще достаточно ADB.

Итоги
  • ZennoPoster + AVD даёт нативный, быстрый и прозрачный способ автоматизировать Android без посредников.
  • Базовых кирпичиков (tap/text/swipe/dump + XPath) достаточно для 80% задач: от чтения лент до аккуратных действий в приложениях.
  • Стабильность обеспечивают левый край свайпов, корректные тайминги и «быстрый» дамп UI.

    Приложил видео работы проекта
 
Последнее редактирование:
Пошла жара!!!
 
А что с подменой отпечатков?)
 
Это для эмуляторов или реальные трубки тоже?
 
Отлично. Вот на ZennoDroid жду таких примеров.
 
GPT утверждает что реальными трубками управлять можно тоже. Для моих задач достаточно эмулятора. Потребляет 700мб оперативки
С подменой отпечатков не знаю, но можно создавать множество устройств в Android Studio AVD
 
Отлично. Вот на ZennoDroid жду таких примеров.
Нет у меня ZennoDroid))) Стояла задача автоматизировать андроид, думал что делать покупать или найти решение и прикрутить к Zennoposter. Сейчас понимаю что наверно все вопросы закрою Zennoposter.
 
а база отпечатков реальных телефонов есть?)
 
Нет у меня ZennoDroid))) Стояла задача автоматизировать андроид, думал что делать покупать или найти решение и прикрутить к Zennoposter. Сейчас понимаю что наверно все вопросы закрою Zennoposter.
все зависит от задач и сервисов которые автоматизировать нужно.
 

Кто просматривает тему: (Всего: 0, Пользователи: 0, Гости: 0)