- Регистрация
- 10.09.2021
- Сообщения
- 1 666
- Реакции
- 961
- Баллы
- 113
Введение
Приветствую всех на 20-ом конкурсе статей форума ZennoLab!
В статье я поделюсь своим опытом в создании шаблонов на ZennoDroid, с учетом использования кубиков, C# и VisualStudio. Мой материал окажется полезным в первую очередь для тех кто только начинает знакомство с ZennoDroid. В статье я расскажу не только про технические аспекты, но и поделюсь собственными наработками.
Основы ZennoDroid для новичков с кубиками
В целом по кубикам отличий от зенопостера не так много, большая часть из них скопирована, появилась дополнительная вкладка под андроид, там и осуществляется основная работа с устройством.
Быстрый старт осуществляется с помощью 4х кубиков: Создаем устройство, выбираем его, проксируем, запускаем.
Создание, выбор и запуск находятся во вкладке “Действия с устройством”, проксирование во вкладке “Настройки устройства”.
В кубике с названием надо обязательно указать переменную для индекса создаваемого устройства (индекс генерируется автоматически), а в кубике выбора указать эту переменную что бы он по индексу выбрал нужное устройство.
Всё, машина запущена, можно начинать с ней работать.
Теперь немного нырнем в параметры устройства, вкладка “Настройки устройства”.
В целом, сам ZennoDroid генерирует вполне сносные параметры, лично я в обязательном порядке меняю разрешение экрана, потому что по умолчанию там планшет получается и докидываю ядер с оперативкой, что бы устройство пошустрее было. Все настройки которые вы тут задаете они сохраняются в профиль.
Что бы они применились, надо запускать устройство с параметром применения настроек профиля.
Теперь попробуем мальца изменить параметры .
Если с настройкой IMEI, Android ID все понятно, то с оператором нужно знать некоторые моменты, они прописаны в документации ZennoDroid.
Вот сайт с требуемыми данными https://www.mcc-mnc.com/
По поводу смены модели устройства, если с производителем всё понятно, то модель надо указывать (на пример для самсунга) не Galaxy s10+, а SM-G975X, иначе вы даже проверку устройства не пройдете при попытке авторизации в гугл плей.
Поля ro.product.brand и ro.product.board надо искать в файле build.prop для требуемой модели, файл этот находится в гугле за 5 минут.
Теперь после того как мы настроили устройство и запустили его, можно на него что-нибудь установить, для этого идем на вкладку “Действия с приложениями”. Установим яндекс браузер, для этого надо просто указать путь к апк файлу.
Вот мы накатили наше первое приложение, теперь его надо запустить, для этого выберем в этом же кубике пункт “Открыть приложение”, нам надо указать имя приложения, для того что бы узнать его имя мы идем на вкладку “Установленные приложения” и там находим наш яндекс браузер и копируем его название, затем подставляем его в кубик и пробуем запустить.
По такому же принципу (через название приложение) осуществляется работа с остальными опциями в кубике “Действия с приложениями”.
Управление через AdbShell
Ещё хотелось бы рассказать про Adb Shell команды которые упрощают некоторые действия. Кубик “Утилиты”, опция “Консольная команда”. Пробегусь по паре основных команд, в целом их создавать не так сложно, заходите в ChatGPT и он их очень ловко генерирует. Обязательно надо указывать переменную в которую положится результат выполнения кубика, а то он с ошибкой будет выполняться.
Команда которая открывает браузер по умолчанию и переходит на нужный нам URL.
adb shell am start -a android.intent.action.VIEW -d http://www.yahoo.comКоманда что бы открыть настройки приложения, в данном случая яндекс браузера, тут так же используется название приложения.
adb shell am start -a android.settings.APPLICATION_DETAILS_SETTINGS -d package:com.yandex.searchappКоманда для открытия настроек разработчика.
adb shell am start -a android.settings.APPLICATION_DEVELOPMENT_SETTINGSКоманда что бы сделать скриншот.
adb shell screencap -p /storage/emulated/0/Download/screenshot.pngСкриншот сохраняется в папку Download (это основная папка для обмена файлами между эмулятором и пк) на пк это папка находится по пути C:\Users\UserName\Downloads\MEmu Download
В целом через adb shell команды очень удобно производить настройки самого устройства без создания лишних команд для тачей и свайпов.
Про свайпы и тачи будет дальше в статье, мне стандартное решение не нравится, предложу вам свое.
Подводные камни
За время работы с ZennoDroid заметил следующие неприятности.
1) Вес одной машины
Одно устройство весит довольно не мало 2 гб, чем больше вы с ним работаете тем больше оно будет весить. В итоге небольшая ферма на 500 устройств будет весить терабайт (как минимум).
2) Скорость работы устройства
В зависимости от сгенерированного устройства и используемых приложений, скорости интернета и мощности вашего пк будет зависеть успешность выполнения вашего шаблона, на пример, вы произвели запуск устройства, кубик завершился удачно, следующим у вас идет кубик запуска приложения, и он у вас выполняется не удачно, потому что устройство хоть и запустилось но оно не прогрузилось как следует, а ZennoDroid этот момент не ловит, в итоге вы приложение не запустите, так же произойдет ошибка если вы попробуете выполнить adb shell команду пока устройство не прогрузилось.
3) Создание большого количества машин
Если вы создаете в MEmu очень много машин, ну хотя бы 1000, у вас появится 2 очень неприятных проблемы. Первая, очень часто начнет вылетать ошибка при попытке создать новое устройство, иногда даже раз по 5 подряд. Вторая, после неудачной попытки создания, список устройств в Memu будет автоматически пролистывать до самого конца. Можете это увидеть вот тут https://zennolab.com/discussion/threads/prolistyvanie-spiska-ustrojstv.115103/
4) Одновременный запуск машин
Запуск большого количества потоков подтягивает проблему одновременного запуска машин, ресурсы на запуск как я понял делятся между машинами и чем больше вы машин одновременно запускаете, тем дольше будет производится запуск, тем больше шансов что шаблон упадет в ошибку из за таймаута на запуск машины.
А теперь расскажу вам как каждую из этих “Фичей” я решал.
1) По уменьшению веса машины я писал целую статью, можете с ней по ссылке ознакомиться. https://zennolab.com/discussion/threads/umenshenie-razmera-mashiny-zennodroid-dlja-xranenija.114499/
2) Тут всё конечно посложнее, универсального решения у меня нету, есть только советы, помнить о том что такая проблема есть и руками выставлять паузы перед действиями. У меня есть свой метод для ожидания элементов который частично решает проблему. Его я напишу в части про c#
3) Этот момент связан с первым пунктом, в той статье я допустил не большую ошибку (о которой я узнал позже), получается мы оставляли папку с машиной в мему и количество машин которые отображаются в memu постоянно росло. Сейчас делаю иначе. Алгоритм такой. Создаю новую машину, работаю с ней, потом файл disk2 перемещаю на хранение, а саму машину удаляю. Когда мне вновь надо поработать с этой машиной, я создаю новую машину, удаляю в ней файл disk2 и подкидываю туда свой disk2 который на хранение был, после работы убираю этот диск обратно на хранение а машину удаляю. В итоге у меня количество созданных эмуляторов в мему равно количеству потоков с которыми я работаю. Ну и ещё есть не большой момент. После обновления версии MEmu там меняются цифры в название файла disk2 и disk1, в итоге когда вы подкидываете свой файл disk2 надо будет ему сменить название на актуальные цифры которые используются в названии disk1.
4) Тут всё просто, если комп слабый, делаем лок, что бы у нас одновременно мог производить запуск только один эмулятор, если комп помощнее, делаем семафор и указываем сколько эмуляторов можно запускать одновременно. Этими двумя методами я с вами поделюсь в части про c#.
С#
Пробежимся по основным методам, находятся они в классе
instance.DroidInstanceВ комментариях к методам разработчики решили себя не утруждать, поэтому догадываться придется самим. Я напишу основные, остальное думаю вам не составит труда по названиям определить, имена большей части методов присвоены адекватные.
C#:
instance.DroidInstance.Action – выбор запуск, создание и остановка устройства
instance.DroidInstance.App – действия с приложениями
instance.DroidInstance.AppiumDriver – получение объекта элемента
instance.DroidInstance.Input – свайпы, тачи, ввод текста
По поводу поиска элементов на экране, для этого есть 4 метода
instance.DroidInstance.AppiumDriver.FindElementById(); - по атрибуту resource-idinstance.DroidInstance.AppiumDriver.FindElementByAccessibilityId();- по атрибуту content-descinstance.DroidInstance.AppiumDriver.FindElementsByClassName(); - по атрибуту classС поиском через xPath думаю вопросов быть не должно.
C# Кубики
А теперь напишем несколько кубиков. Для свайпа, тача, ввода текста и ожидания элемента.
C#:
public IAndroidElementAPI WaitElementByXPath(string xPath)
{
IAndroidElementAPI element = null;
try
{
element = instance.DroidInstance.AppiumDriver.FindElementByXPath(xPath);
}
catch (Exception ex)
{
throw new Exception("Не удалось осуществить инициализацию элемента для ожидания, проверьте xPath: " + ex.Message);
}
int counter = 0;
while (element == null)
{
if (counter == 10) //Количество попыток для инициализации
{
throw new Exception("Элемент не появился: " + xPath);
}
project.SendInfoToLog("Ожидание элемента: " + xPath);
element = instance.DroidInstance.AppiumDriver.FindElementByXPath(xPath);
Thread.Sleep(3000); //Пауза между попытками в МС
counter++;
}
Thread.Sleep(1000); //Пауза после нахождения элемента в МС, нужна для того что иногда элемент находится в дереве быстрее того как он появится на экране.
return element; //Возвращаем инициализированный элемент.
}
C#:
public void ClickToElementByXpath(string xPath, int PauseAfterClickInSec) // В качестве параметров принимает xPath и паузу после клика в секундах
{
Random rnd = new Random();
IAndroidElementAPI Element = WaitElementByXPath(xPath); //Принимаем элемент из прошлого метода
var elementLocation = Element.GetAttribute("bounds").Replace("][", "|").Replace("]", "").Replace("[", ""); //Получаем его координаты из свойства bounds
//Раскладываем по переменным его координаты
int x1 = Convert.ToInt32(elementLocation.Split('|')[0].Split(',')[0]);
int x2 = Convert.ToInt32(elementLocation.Split('|')[1].Split(',')[0]);
int y1 = Convert.ToInt32(elementLocation.Split('|')[0].Split(',')[1]);
int y2 = Convert.ToInt32(elementLocation.Split('|')[1].Split(',')[1]);
int randomX = rnd.Next(x1,x2); // Получаем рандомную точку между координатами x
int randomY = rnd.Next(y1, y2); // Получаем рандомную точку между координатами y
instance.DroidInstance.Input.Tap (randomX, randomY); //деламем клик
project.SendInfoToLog("Сделали клик по элементу: " + xPath, true);
Thread.Sleep(PauseAfterClickInSec * 1000);
}
C#:
public void SwipeUp()
{
Random random = new Random();
int height = Convert.ToInt32(Convert.ToDouble(project.Profile.DisplayHeight) * 0.87); //получаем и уменьшаем высоту экрана на 13% что бы не цеплять элементы интерфейса
int width = project.Profile.DisplayWidth; // получаем ширину экрана
int randomHeight = (height * random.Next(30, 40)) / 100; //Определяем размер поля для свайпа по y
int randomWidth = (width * random.Next(10, 17)) / 100; Определяем размер поля для свайпа по x
instance.DroidInstance.Input.Swipe
(random.Next(width / 2 - randomWidth, width / 2 + randomWidth), random.Next(height / 2 + randomHeight, height / 2 + randomHeight),
random.Next(width / 2 - randomWidth, width / 2 + randomWidth), random.Next(height / 2 - randomHeight, height / 2 - randomHeight),
random.Next(1000, 2000));
}
C#:
public void SwipeDown()
{
Random random = new Random();
int height = Convert.ToInt32(Convert.ToDouble(project.Profile.DisplayHeight) * 0.87);
int width = project.Profile.DisplayWidth;
int randomHeight = (height * random.Next(30, 40)) / 100;
int randomwidth = (width * random.Next(10, 17)) / 100;
instance.DroidInstance.Input.Swipe
(random.Next(width / 2 - randomwidth, width / 2 + randomwidth), random.Next(height / 2 - randomHeight, height / 2 - randomHeight),
random.Next(width / 2 - randomwidth, width / 2 + randomwidth), random.Next(height / 2 + randomHeight, height / 2 + randomHeight), random.Next(1000, 2000));
}
C#:
public void SwipeSearchElementByXpath(string xpath)
{
var element = instance.DroidInstance.AppiumDriver.FindElementByXPath(xpath); // инициализируем элемент
Random random = new Random();
int counter = 0;
while (element == null) //Проверяем наличие элемента, если его нету то заходим в while
{
if (counter == 20) //Количество попыток свайпа
{
throw new Exception("Сделали 20 свайпов а элемент так и не появился");
}
SwipeUp(); // Делаем свайп
element = instance.DroidInstance.AppiumDriver.FindElementByXPath(xpath); // Пробуем найти элемент
Thread.Sleep(random.Next(500, 1000)); //Пауза между свайпами
counter++;
}
}
C#:
public void SendTextByXpath(string xPath, string text)
{
Random random = new Random();
SwipeSearchElementByXpath(xPath);//Ищем элемент
Thread.Sleep(1000);
ClickToElementByXpath(xPath,2);//Кликаем по нему
instance.DroidInstance.Input.SendText(text, random.Next(100, 250)); // Вводим текст
instance.DroidInstance.Input.SendKeyCode(KeyCode.KEYCODE_ENTER); //Нажимаем enter после ввода текста
}
Класс и методы для запуска машины через лок и семафор
C#:
public class StartMachine
{
public static Semaphore SinhronizationStartingMachineSemaphore = new Semaphore(2, 2);//Статичный объект семафора 2,2 это то , сколько машин могут одновременно запускаться
public static object SinhronizationStartingMachineObj = new object();//Статичный объект лока
Instance instance;
IZennoPosterProjectModel project;
public StartMachine(Instance instance, IZennoPosterProjectModel project)
{
this.instance = instance;
this.project = project;
}
public void StartLock()
{
lock (SinhronizationStartingMachineObj)
{
instance.DroidInstance.Action.Stop();
instance.DroidInstance.Action.Start(true); // true значит что запускаем машину с применением настроек профиля, если не хотите их применять пропишите туда false
}
}
public void StartSemaphore()
{
SinhronizationStartingMachineSemaphore.WaitOne(); // Запрашиваем разрешение на доступ.
try
{
instance.DroidInstance.Action.Stop();
instance.DroidInstance.Action.Start(true);
}
finally
{
SinhronizationStartingMachineSemaphore.Release(); // Освобождаем разрешение после выполнения работы.
}
}
}
Все эти методы добавлены в общий код шаблона который я прикрепил к статье.
ZennoDroid + VisualStudio
Ну это прям для тертых калачей)
Прямого подруба vs к zd как vs к zp нету, придется мальца повозиться.
Сначала нам надо создать проект либы в студии.
Затем в папке проекта вам надо найти файл ProjectName.csproj (лежит рядом с файлом sln)
XML:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net4.8</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<Reference Include="ZennoLab.CommandCenter">
<HintPath>$(ZennoDroidDllPath)\ZennoLab.CommandCenter.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ZennoLab.Emulation">
<HintPath>$(ZennoDroidDllPath)\ZennoLab.Emulation.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ZennoLab.InterfacesLibrary">
<HintPath>$(ZennoDroidDllPath)\ZennoLab.InterfacesLibrary.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ZennoDroid.Interface">
<HintPath>$(ZennoDroidDllPath)\ZennoDroid.Interface.dll</HintPath>
</Reference>
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<StartAction>Program</StartAction>
<StartProgram>$(ZennoDroidDllPath)\ZennoLab.CodeRunner.exe</StartProgram>
<StartArguments>50606 "$(MSBuildThisFileDirectory)bin\Debug\net4.8\$(MSBuildProjectName).dll" -sp "$(USER_HOME)\AppData\Roaming\ZennoLab\ZennoPoster\7" --run-external-dll</StartArguments>
</PropertyGroup>
</Project>
Создать класс Program, унаследовать им интерфейс IZennoExternalCode и реализовать его.
Метод Execute является у нас точкой входа. Тут мы уже можем развернуться как следует, подключить другие проекты, насоздавать пространств имен, классов, методов и прочей бесовщины). Крупный проект на пример может вот так выглядеть.
А теперь расскажу как это в проект подкинуть. Кубика VisualStudio как в зенопостере у нас нету. Но я как то копался в документации Zennodroid и нашел там один пример проекта в котором этот кубик откуда то был, в итоге я его просто методом копирования переношу в свои проекты, с вами я им тоже поделюсь в файлах к статье.
В кубике мы уже просто указываем путь к dll нашей либы и запускаем его.
Практика
Ну а теперь давайте используя все полученные знания и напишем простенький шаблон для того что бы заработать нашу первую копеечку.
Для этого мы зарегаем кошелек монетки Neurai (XNA) и нападем с ним на кран.
Шаблон будет прикреплен к статье, код и кубики будут максимально прокомментированы.
Папка с шабом оказалась слишком большой, так что пришлось заливать в облако https://disk.yandex.ru/d/SkzBRgTfh9W9Ew
БОНУС
Решение Yandex Smart Captcha через клики.
Решение Yandex Smart Captcha через клики.
- Номер конкурса статей
- Двадцатый конкурс статей
- Тема статьи
- Другое
Последнее редактирование модератором:



