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