
В связи с тем, что мобильные устройства уже давно имеют обширный функционал, то задачи автоматизации можно смело переносить и на них. И, как нельзя лучше, здесь так же хорошо подходит cron для их выполнения. Но если в «обычных» Linux системах настройка cron занимает мало времени, то Android устройство требует более сложной работы по его настройке.
Если тебе интересна тема автоматизации и ты хочешь, чтобы твои shell-скрипты запускались сразу же после загрузки устройства, да еще и могли бы запускаться по таймеру — добро пожаловать под кат!
Предисловие
Я занимаюсь автоматизацией мобильных устройств под Android. И во время выполнения автоматических скриптов происходит множество непредвиденных ситуаций, даже если для тестирования используются одинаковые устройства.
Самые популярные проблемы:
0. Скрипт автоматизации выполняет не то, что ты хотел
1. Мобильное приложение автоматически выгружается
2. Автоматическая перезагрузка телефона
3. Мобильное приложение автоматически не запускается после перезапуска
4. Wi-Fi модуль произвольно отключается, не находит сеть, не подключается к сети
5. Мобильная сеть неожиданно пропала
6. Телефон ушел в спящий режим
7. Отпал прокси или сам сервер или сервер вернул странный ответ
Из-за этого приходится постоянно следить за устройством и отлавливать эти непредвиденные ситуации.

Таким образом, я пришел к тому, что cron с «правильными» скриптами позволит отследить программные сбои и восстановить скрипт автоматизации или запустить его заново. Но как оказалось, хотя Android содержит ядро Linux, но есть особые нюансы, с которыми пришлось разбираться. Итак, давайте приступим к настройке!
Настройка Cron
Настраиваем окружение
- Устанавливаем adb для доступа к устройству через shell используя usb-провод.
- Открываем раздел Для разработчиков. Для этого заходим в раздел О телефоне и делаем несколько нажатий на Номер сборки или на что-то похожее.
- Заходим в раздел Для разработчиков и включаем его. Подключаем устройство к компьютеру и разрешаем доступ на этом компьютере к данному устройству.
- Добавляем root для вашего устройства. Самые распространенные варианты — это SuperSu, Magisk и Kingroot. Зайдите на 4pda и найдите вариант root'а под ваше устройство. К сожалению, универсального root'а не существует.
- Устанавливаем BusyBox (так же есть на 4pda, например), так как он как раз содержит в себе cron-программу.
Настройка ручного запуска
- Подключаемся к телефону с помощью adb shell (если adb у вас не прописана в переменной среды окружения, то добавьте полный путь.
- Переходим в режим root с помощью команды su
- Проверяем наличие cron-программы и посмотрим настройки с помощью команды crond -h
результат выполнения
crond: invalid option -- h BusyBox v1.29.2-Stericson (2018-08-12 11:19:12 EDT) multi-call binary. Usage: crond -fbS -l N -d N -L LOGFILE -c DIR -f Foreground -b Background (default) -S Log to syslog (default) -l N Set log level. Most verbose 0, default 8 -d N Set log level, log to stderr -L FILE Log to FILE -c DIR Cron dir. Default:/var/spool/cron/crontabs
Как видно из последней строки, инструкции по умолчанию должны храниться в директории /var/spool/cron/crontabs, которая автоматически не создается и если мы запустим команду
и потом проверим запустился ли процесс черезcrond -b
, то его там может и не быть, т.к. он не смог получить какие-либо инструкции. Поэтому давайте выполним командуps | grep crond
и посмотрим в чем причина. Скорее всего у вас будет подобная ошибка:crond -b -fd0
crond: can't change directory to '/var/spool/cron/crontabs': No such file or directory. В данном случае это нормально, т.к. в будущем мы сами укажем путь до исолняемого crontab файла.4. Создадим простой crontab файл:
mkdir /data/crontab echo "*/1 * * * * echo 'text' >> /sdcard/test.txt" > /data/crontab/root
Теперь у нас есть задание, которое каждую минуту будет добавлять слово text в файл /sdcard/test.txt
Запускаем:
и получаем следующий лог:crond -b -fd0 -c /data/crontab
crond: crond (busybox 1.29.2-Stericson) started, log level 0 crond: ignoring file 'root' (no such user) ...
Конечно, немного удивляет, ведь если мы выполним команду whoami то в результате она вернёт root.
5. Добавим пользователя root, раз crond просит:
mount -o remount,rw /system; echo "root:x:0:0::/system/etc/crontabs:/system/bin/sh" >> /system/etc/passwd; mount -o remount,ro /system;
Из-за отсутствия данного файла, я понял, что в Android системе он совсем не задействован. Если вы уверены в том, где вы будете хранить свои crontab файлы, то вы можете заменить строку /system/etc/crontabs на нужную вам. Снова выполняем команду
crond -b -fd0 -c /data/crontab
И получаем следующее:
crond: user:root entry:*/1 * * * * echo 'text' >> /sdcard/test.txt 111111111111111111111111111111111111111111111111111111111111 111111111111111111111111 11111111111111111111111111111111 111111111111 1111111 crond: wakeup dt=16 crond: file root: crond: line echo 'text' >> /sdcard/test.txt crond: job: 0 echo 'text' >> /sdcard/test.txt crond: can't change directory to '/system/etc/crontabs' crond: can't change directory to '/var/spool/cron': No such file or directory crond: USER root pid 12849 cmd echo 'text' >> /sdcard/test.txt
Хотя, если верить логу задача в crond прописалась, но в моем случае файл не создался. Решить проблему можно очень просто:
mkdir -p /system/etc/crontabs
Ну хочет он, чтобы существовала там директория, кто мы такие, чтобы ему запрещать! Запускаем снова и видим:
crond: user:root entry:*/1 * * * * echo 'text' >> /sdcard/test.txt 111111111111111111111111111111111111111111111111111111111111 111111111111111111111111 11111111111111111111111111111111 111111111111 1111111 crond: wakeup dt=12 crond: file root: crond: line echo 'text' >> /sdcard/test.txt crond: job: 0 echo 'text' >> /sdcard/test.txt crond: child running /system/bin/sh crond: USER root pid 13033 cmd echo 'text' >> /sdcard/test.txt
Ошибки ушли, и появилась строка crond: child running /system/bin/sh. Наконец-то cron у нас успешно завелся, и можно переходить ко второй части!
Автоматическая загрузка shell-скрипта
В Linux системе есть директория init.d, которая отвечает за автозапуск сразу же после загрузки системы, поэтому попробуем идти по этому пути!
1. Проверяем, существует ли данная директория у вас на устройстве (это /etc/init.d либо /system/etc/init.d — это тот же смонтированный раздел etc ). В моем случае её нет. Ну что, тогда создаем:
mount -o remount,rw /system mkdir /system/etc/init.d chmod 0755 /system/etc/init.d mount -o remount,ro /system
Теперь добавим туда какой-нибудь простенький скрипт, например:
echo "echo 'Init.d is working !!!' >> /sdcard/init_test.log" > /system/etc/init.d/90my_script chmod 777 /system/etc/init.d/90my_script
Перезагружаем устройство и смотрим, случилось ли чудо… К сожалению, у меня файл не появился.
Исследуем систему дальше и ищем какой-нибудь init файл, который может запускать скрипты после запуска. У меня на устройстве оказался файл в /init.rc. Ну что, попробуем его изменить и перезагрузим устройство:
mount -o remount,rw / echo "echo 'Init.d is working !!!' >> /sdcard/init_test.log" >> /init.rc mount -o remount,ro / reboot
Но файл опять не создался. Идем смотреть на файл /init.rc и наша запись пропала и файл как бы и не менялся, т.к. дата создания стоит совсем какая-то странная (в моем случае 01 янв. 70 05:00:00).
Продолжаем разбираться, и оказывается что данный файл храниться в boot.img, и каждый раз достается из него. И для того, чтобы изменить функционал файла init.rc нужно выполнить все это.
Но есть более простой способ, который поможет решить данную задачу. Для этого способа мы можем использовать следующий shell-скрипт (скажем спасибо Ryuinferno):
Shell-скрипт
#!/system/bin/sh #Script to enable init.d by Ryuinferno @ XDA error_msg(){ echo "You do not need this mod..." sleep 1 echo "If you are reapplying, please delete these files if present:" echo "/system/bin/sysinit" sleep 1 echo "/system/etc/install-recovery.sh" sleep 1 echo "/system/etc/install-recovery-2.sh" sleep 1 echo "And run again..." sleep 1 echo "If init.d is still not working, read the FAQ part in my thread..." sleep 1 echo "Aborting..." mount -o remount,ro -t auto /system echo "" echo "Ryuinferno @ XDA" exit 1 } echo "Init.d Enabler by Ryuinferno @ XDA" echo "" sleep 1 id=`id`; id=`echo ${id#*=}`; id=`echo ${id%%\(*}`; id=`echo ${id%% *}` if [ "$id" != "0" ] && [ "$id" != "root" ]; then echo "Script NOT running as root!" sleep 1 echo "Superuser access not granted!" sleep 1 echo "Please type 'su' first before running this script..." exit 1 else echo "Hello Supaa User! :P" echo "" sleep 1 fi if [ ! "'which busybox'" ]; then echo "busybox NOT INSTALLED!" sleep 1 echo "Please install busybox first!" exit 1 else echo "busybox found!" sleep 1 fi bbb=0 if [ ! "`which grep`" ]; then bbb=1 echo "grep applet NOT FOUND!" sleep 1 else echo "Awesome! grep found! :D" sleep 1 fi if [ ! "`which run-parts`" ]; then bbb=1 echo "run-parts applet NOT FOUND!" sleep 1 else echo "Good! run-parts found! :)" echo "" sleep 1 fi if [ $bbb -eq 1 ] ; then echo "" echo "Required applets are NOT FOUND!" echo "" sleep 1 echo "Please reinstall busybox!" exit 1 fi echo "Great! Let's proceed..." echo "" sleep 1 echo "Press enter to continue..." read enterKey clear sleep 1 echo "Mounting system as rewritable..." mount -o remount,rw -t auto /system sleep 1 echo "Removing old sysinit file" rm /system/bin/sysinit sleep 1 echo "" echo "Checking for the presence of sysinit in /system/bin..." sleep 1 if [ -e /system/bin/sysinit ]; then echo "sysinit found..." if [ -z "`cat /system/bin/sysinit | grep "init.d"`" ]; then echo "Adding lines to sysinit..." echo "" >> /system/bin/sysinit echo "# init.d support" >> /system/bin/sysinit echo "" >> /system/bin/sysinit echo "export PATH=/sbin:/system/sbin:/system/bin:/system/xbin" >> /system/bin/sysinit echo "run-parts /system/etc/init.d" >> /system/bin/sysinit echo "" >> /system/bin/sysinit else echo "" echo "Your sysinit should already be running the scripts in init.d folder at boot..." error_msg fi else echo "sysinit not found, creating file..." echo "#!/system/bin/sh" > /system/bin/sysinit echo "# init.d support" >> /system/bin/sysinit echo "" >> /system/bin/sysinit echo "export PATH=/sbin:/system/sbin:/system/bin:/system/xbin" >> /system/bin/sysinit echo "run-parts /system/etc/init.d" >> /system/bin/sysinit echo "" >> /system/bin/sysinit fi sleep 1 echo "Setting correct permissions and ownership for sysinit..." chmod 755 /system/bin/sysinit chown 0.2000 /system/bin/sysinit sleep 1 echo "" echo "Checking for the presence of install-recovery.sh..." sleep 1 if [ -f /system/etc/install-recovery.sh ] && [ -z "`cat /system/etc/install-recovery.sh | grep "daemon"`" ]; then if [ ! -z "`cat /system/etc/install-recovery.sh | grep "init.d"`" ];then echo "Your install-recovery.sh seems to be already modified for init.d..." error_msg fi echo "install-recovery.sh found, renaming it as install-recovery-2.sh..." mv /system/etc/install-recovery.sh /system/etc/install-recovery-2.sh echo "Recreating install-recovery.sh..." echo "#!/system/bin/sh" > /system/etc/install-recovery.sh echo "# init.d support" >> /system/etc/install-recovery.sh echo "" >> /system/etc/install-recovery.sh echo "/system/bin/sysinit" >> /system/etc/install-recovery.sh echo "" >> /system/etc/install-recovery.sh echo "# excecuting extra commands" >> /system/etc/install-recovery.sh echo "/system/etc/install-recovery-2.sh" >> /system/etc/install-recovery.sh echo "" >> /system/etc/install-recovery.sh elif [ -f /system/etc/install-recovery.sh ] && [ ! -z "`cat /system/etc/install-recovery.sh | grep "daemon"`" ]; then if [ -f /system/etc/install-recovery-2.sh ] && [ ! -z "`cat /system/etc/install-recovery-2.sh | grep "init.d"`" ];then echo "Your install-recovery-2.sh seems to be already modified for init.d..." error_msg fi echo "install-recovery.sh is used for superuser, using install-recovery-2.sh instead..." if [ -f /system/etc/install-recovery-2.sh ]; then echo "" >> /system/etc/install-recovery-2.sh echo "# init.d support" >> /system/etc/install-recovery-2.sh echo "/system/bin/sysinit" >> /system/etc/install-recovery-2.sh echo "" >> /system/etc/install-recovery-2.sh else echo "#!/system/bin/sh" > /system/etc/install-recovery-2.sh echo "# init.d support" >> /system/etc/install-recovery-2.sh echo "" >> /system/etc/install-recovery-2.sh echo "/system/bin/sysinit" >> /system/etc/install-recovery-2.sh echo "" >> /system/etc/install-recovery-2.sh fi if [ -z "`cat /system/etc/install-recovery.sh | grep "install-recovery-2.sh"`" ]; then echo "" >> /system/etc/install-recovery.sh echo "# extra commands" >> /system/etc/install-recovery.sh echo "/system/etc/install-recovery-2.sh" >> /system/etc/install-recovery.sh echo "" >> /system/etc/install-recovery.sh fi else echo "install-recovery.sh not found, creating it..." echo "#!/system/bin/sh" > /system/etc/install-recovery.sh echo "# init.d support" >> /system/etc/install-recovery.sh echo "" >> /system/etc/install-recovery.sh echo "/system/bin/sysinit" >> /system/etc/install-recovery.sh echo "" >> /system/etc/install-recovery.sh fi sleep 1 echo "Setting the correct permissions and ownership for install-recovery.sh..." echo "Also for install-recovery-2.sh if it exists..." chmod 755 /system/etc/install-recovery.sh chown 0.0 /system/etc/install-recovery.sh if [ -f /system/etc/install-recovery-2.sh ]; then chmod 755 /system/etc/install-recovery-2.sh chown 0.0 /system/etc/install-recovery-2.sh fi sleep 1 echo "" echo "Checking for the presence of the init.d folder..." sleep 1 if [ -d /system/etc/init.d ]; then echo "init.d folder found..." else echo "init.d folder not found, creating the folder..." mkdir /system/etc/init.d fi sleep 1 echo "" echo "Creating basic init.d scripts..." echo "#!/system/bin/sh" > /system/etc/init.d/08setperm echo "#set correct permissions to /system/etc/init.d folder" >> /system/etc/init.d/08setperm echo "" >> /system/etc/init.d/08setperm echo "mount -o remount,rw -t auto /system" >> /system/etc/init.d/08setperm echo "chmod -R 777 /system/etc/init.d" >> /system/etc/init.d/08setperm echo "mount -o remount,ro -t auto /system" >> /system/etc/init.d/08setperm echo "" >> /system/etc/init.d/08setperm echo "#!/system/bin/sh" > /system/etc/init.d/00test echo "#init.d test" >> /system/etc/init.d/00test echo "" >> /system/etc/init.d/00test echo "if [ -f /data/Test.log ]; then" >> /system/etc/init.d/00test echo "rm /data/Test.log" >> /system/etc/init.d/00test echo "fi" >> /system/etc/init.d/00test echo "" >> /system/etc/init.d/00test echo 'echo "Init.d is working !!!" >> /data/Test.log' >> /system/etc/init.d/00test echo 'echo "excecuted on $(date +"%d-%m-%Y %r" )" >> /data/Test.log' >> /system/etc/init.d/00test echo "" >> /system/etc/init.d/00test sleep 1 echo "Creating permissive SELinux script..." sleep 1 echo "#!/system/bin/sh" >> /system/etc/init.d/01permissive echo "#Init.d Permissive SELinux" >> /system/etc/init.d/01permissive echo "" >> /system/etc/init.d/01permissive echo "busybox mount -o remount,rw -t auto /system" >> /system/etc/init.d/01permissive echo "" >> /system/etc/init.d/01permissive echo "setenforce 0" >> /system/etc/init.d/01permissive echo "SELINUX=permissive" >> /system/etc/init.d/01permissive echo "" >> /system/etc/init.d/01permissive sleep 1 echo "Setting correct permissions and ownership for init.d folder and scipts..." chmod 777 /system/etc/init.d chmod 777 /system/etc/init.d/08setperm chmod 777 /system/etc/init.d/00test chmod 777 /system/etc/init.d/01permissive chown 0.0 /system/etc/init.d chown 0.0 /system/etc/init.d/08setperm chown 0.0 /system/etc/init.d/00test chown 0.0 /system/etc/init.d/01permissive sleep 1 echo "" echo "Mounting system as read-only..." mount -o remount,ro -t auto /system sleep 1 echo "" echo "Done!!!" sleep 1 echo "Please reboot at least twice before checking /data..." sleep 1 echo "If init.d is working, you will see a Test.log in /data..." sleep 1 echo "" echo "Enjoy!!! =)" echo "Ryuinferno @ XDA 2013" exit
Приступаем к внедрению скрипта! В моем случае он будет называться init.sh.
1. Загружаем файл на sdcard мобильного устройства:
adb push /tmp/init.sh /sdcard
2. Копируем в память мобильного устройства и устанавливаем нужные права:
adb shell su cp /sdcard/init.sh /data/init.sh chmod 777 /data/init.sh
3. Запускаем на выполнение:
/data/init.sh
И обращаем внимание на лог, который выводится. Вот мой лог:
Лог выполнения
Init.d Enabler by Ryuinferno @ XDA Hello Supaa User! :P busybox found! Awesome! grep found! :D Good! run-parts found! :) Great! Let's proceed... Press enter to continue... Mounting system as rewritable... Removing old sysinit file rm: /system/bin/sysinit: No such file or directory Checking for the presence of sysinit in /system/bin... sysinit not found, creating file... Setting correct permissions and ownership for sysinit... Checking for the presence of install-recovery.sh... install-recovery.sh not found, creating it... Setting the correct permissions and ownership for install-recovery.sh... Also for install-recovery-2.sh if it exists... Checking for the presence of the init.d folder... init.d folder found... Creating basic init.d scripts... Creating permissive SELinux script... Setting correct permissions and ownership for init.d folder and scipts... Mounting system as read-only... Done!!! Please reboot at least twice before checking /data... If init.d is working, you will see a Test.log in /data... Enjoy!!! =) Ryuinferno @ XDA 2013
Как видим из лога, ошибок нет, поэтому смело перезагружаем устройство! Возможно у кого-то уже все заработало и вы смогли найти файл /data/Test.log, но у меня его нет. Проверим директорию /system/etc/init.d используя команду ls:
00test 01permissive 08setperm
Как видим, задачи успешно созданы. Возможно все же придется менять boot.img, но давайте в начале проверим, а где у нас файл install-recovery.sh с помощью команды
find / -name "install-recovery.sh" ... /system/bin/install-recovery.sh /system/etc/install-recovery.sh ...
Как можем заметить, у нас 2 файла, которые лежат в разных местах. По дате создания мы можем заметить, что скрипт создал файл в директории /system/etc/install-recovery.sh, хотя, возможно, в некоторых случаях он должен создавать его в /system/etc. Давайте переименуем файл в bin и скопируем файл из etc:
mount -o remount,rw /system mv /install-recovery.sh /system/bin/install-recovery2.sh cp /install-recovery.sh /system/bin/
UPD: Обратите внимание, что контекст безопасности у обоих файлов должен совпадать. И если вдруг при копировании у вас он сбился (хотя по идее, такого быть не должно), вам необходимо его будет восстановить (например, через утилиту chcon). Посмотреть полную информацию по файлу можно с помощью ls -lZ:
ls -lZ /system/etc/install-recovery.sh #результат -rwxr-xr-x root root u:object_r:system_file:s0 install-recovery.sh
Тут u:object_r:system_file:s0 и является контекстов безопасности.
И снова перезагружаем устройство… И вот, наконец-то долгожданный УСПЕХ! Файл /data/Test.log появился!
Раз все работает, идем в /system/etc/init.d и создаем shell-скрипт. А в нем как-раз запустим наш crond на выполнение:
echo "#!/system/bin/sh crond -b -L /sdcard/error.log -c /data/crontab" > /system/etc/init.d/99cronstart chmod 777 /system/etc/init.d/99cronstart reboot
После загрузки проверяем, запустился ли crond:
ps | grep crond root 414 1 9532 236 hrtimer_na 000dcf90 S crond
И на этом могли бы мы уже закончить, но давайте подождем минуту и посмотрим, произошла ли запись в наш файл… Ну как вы уже поняли, опять ни чего не сработало. Дело в том, что данный процесс нужно запустить от супер пользователя. Изменим скрипт в файле 99cronstart:
echo "#!/system/bin/sh /su/bin/su -c crond -b -L /sdcard/error.log -c /data/crontab" > /system/etc/init.d/99cronstart reboot
UPD: Возможно в вашем случае su будет иметь другой путь, тогда воспользуйтесь командой which su и замените путь, на ваш.
Теперь наше Android устройство поддерживает и задачи cron и может содержать shell-скрипты для автоматического запуска!
Ну и напоследок, скрипт, который будет запускать наше приложение, если его нет в процессах и сохранять информацию о том, что находилось на главном экране до запуска нашего приложения:
proc=$(ps | grep "com.test.app") if [ "$proc" == "" ]; then dumpsys window | grep CurrentFocus > /sdcard/current_focus.dump sleep 1 am start -n com.test.app/com.test.app.activities.MainActivity fi
