shell команды на Android за ~100 строк кода
Мой канал: @VolfsChannel
Сегодня мы попробуем создать мини-приложение терминал, рассмотрим основные методы работы с shell-командами на Android.
Android основан на ядре Linux. Также как и на Linux в нем можно выполнять команды через терминал. Терминал в Android по своей сути просто является нескучной обоиной для выполнения функций рантайма. Чтож, заглянем глубже. Создадим простейшее приложение которое будет отдавать shell-команды и получать результат в виде строки. Ко всему прочему наше приложение сможет использовать Root.
100 строчек кода...
Создадим новый класс, я назвал его CommandExecutor.java и напишите следующий код:
import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; public class CommandExecutor { // Модель для получения результата public static class ResultData { // Код результата private int resultCode; // Результат отработки команды private String resultData; // Поле для записи ошибок private String resultError; // Конструктор ResultData(int code, String info, String err) { resultCode = code; resultData = info == null ? "" : info; resultError = err == null ? "" : err; } // Геттеры полей public int getResultCode() { return resultCode; } public String getResultData() { return resultData; } public String getResultError() { return resultError; } public String getResult() { return toString(); } public String toString() { return resultData + "\n" + resultError; } } // Выполнение простой sh-команды public static ResultData execute(String command) { return execute(false, command); } // То же что и выше, но из под рута public static ResultData executeSu(String command) { return execute(true, command); } // Если нужно изменять значение su динамически public static ResultData execute(boolean su, String command) { return execPool(su, rmSlashN(command).split("\n")); } // Выполняем массив команд private static ResultData execPool(boolean su, String[] commands) { // Обьявление переменных Process exec = null; InputStream execIn = null; InputStream execErr = null; OutputStream execOs = null; ResultData resultData; try { // В зависимости от значения su меняем параметр запуска команды // От рута или же от юзера exec = Runtime.getRuntime().exec(su ? "su" : "sh"); execIn = exec.getInputStream(); execErr = exec.getErrorStream(); execOs = exec.getOutputStream(); DataOutputStream dos = new DataOutputStream(execOs); // В цикле проходимся по всем отосланным командам for (String com : commands) if (!com.isEmpty()) { dos.writeBytes(com + "\n"); dos.flush(); } dos.close(); // Посылаем команду и ждём ответа от оболочки resultData = new ResultData(exec.waitFor(), inputStream2String(execIn, "utf-8"), inputStream2String(execErr, "utf-8")); } catch (Exception e) { resultData = new ResultData(-1, "", e.toString()); } finally { try { // Не забываем закрыть все стримы if (execIn != null) execIn.close(); if (execErr != null) execErr.close(); if (execOs != null) execOs.close(); // Завершаем процесс выполнения if (exec != null) exec.destroy(); } catch (Exception ignored) {} } return resultData; } // Преобразование стрима в строку public static String inputStream2String(InputStream in, String encoding) throws Exception { StringBuilder out = new StringBuilder(); InputStreamReader inread = new InputStreamReader(in, encoding); char[] b = new char[1024]; int n; while ((n = inread.read(b)) != -1) { String s = new String(b, 0, n); out.append(s); } return out.toString(); } // Чтение стрима в строку public static String read(InputStream is) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int length; while ((length = is.read(buffer)) != -1) baos.write(buffer, 0, length); return baos.toString("UTF-8"); } catch (Exception e) { e.printStackTrace(); return e.toString(); } } // Удаляем лишние переносы строки public static String rmSlashN(String text) { while (text.contains("\n\n")) text = text.replace("\n\n", "\n"); if (text.startsWith("\n")) text = text.substring(1); if (text.endsWith("\n")) text = text.substring(0, text.length() - 1); return text; } }
Я добавил комментарии к коду. Если у вас возникнут вопросы - пишите их в бота.
Грубо говоря: мы шлём команду, ждём пока система ответит, и выполняем другие команды дальше.
В примере приложения я использовал метод execute(boolean su, String command)
т.к. значение su
у меня зависит от чекбокса.
Пример вызова
String out = CommandExecutor.execute(true, "magiskhide --ls").getResult();
Выведет список приложений от которых скрывается рут в MagiskHide
.
Log.d("SU-TEST", out);
Создаем приложение
Создайте новый проект в IDE. Откройте MainActivity.java и напишите следующий код:
import android.app.Activity; import android.os.Bundle; import android.widget.Button; import android.widget.EditText; import android.widget.CheckBox; import android.widget.TextView; import android.view.View.OnClickListener; import android.view.View; public class MainActivity extends Activity { private Button btnExec; private EditText fieldCommand; private CheckBox boxSu; private TextView textOut; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); fieldCommand = findViewById(R.id.field_exec); boxSu = findViewById(R.id.checbox_su); btnExec = findViewById(R.id.btn_execute); textOut = findViewById(R.id.command_output); btnExec.setOnClickListener(new OnClickListener(){ @Override public void onClick(View p1) { String out = CommandExecutor.execute(boxSu.isChecked(), fieldCommand.getText().toString()).getResult(); textOut.setText(out); } }); } }
Измените разметку главного экрана (в моем случае main.xml)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <EditText android:layout_width="match_parent" android:maxLines="2" android:layout_height="wrap_content" android:hint="command" android:id="@+id/field_exec"/> <CheckBox android:layout_marginTop="16dp" android:text="Super User" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/checbox_su"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Execute" android:textAllCaps="false" android:layout_gravity="end" android:id="@+id/btn_execute"/> <ScrollView android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:typeface="monospace" android:textSize="12sp" android:id="@+id/command_output"/> </ScrollView> </LinearLayout>
Скопируйте класс CommandExecutor.java к себе в проект.
Все ошибки должны пропасть. Запустите приложение и попробуйте выполнить любую shell-команду.
Результат
Выполнение команды без рута
То же самое с рутом
Приложение и его исходный код можно найти на канале: @VolfsChannel