Android 作为 Linux 的分支之一,同时也支持 Linux 的一些基本终端命令,并且在 Andriod 应用中使用终端命令可以实现一些 Android API 中没有提供的功能。
使用 Java 接口执行终端命令
Android 本身并没有提供执行终端命令的接口,若要在应用中执行终端命令,需要用到 Java 提供的与系统进行交互的接口。
Java 提供了 ProcessBuilder().command().start()
和 Runtime.getRuntime().exec()
方法来执行终端命令,这两个方法都会创建一个用来执行命令的进程并返回一个 Process
子类的实例。Process
类用来控制该进程或从其中获取信息。
Process 类的方法介绍
getOutputStream()
获得与进程标准输入相连的 OutputStream
,用来向进程输入命令。
getInputStream()
获得与进程标准输出相连的 InputStream
,用来读取进程的输出信息。
getErrorStream()
获得与进程错误输出相连的 InputStream
,用来读取进程的错误信息。
waitFor()
等待进程结束,并返回进程的退出状态值,正常状态返回 0 。
destroy()
强制销毁该进程。
执行命令的具体过程
通常以 Runtime.getRuntime().exec(command)
方法来开启进程并执行第一个命令。
如果需要继续输入命令,可以用 getOutputStream()
获取 OutputStream 向进程写入信息。
若要读取命令返回的信息,可以调用 getInputStream()
获取 InputStream 读取。
执行完命令调用 waitFor()
获得返回状态。
具体代码如下:
public static void executeShellCommand(String command) {
Process process = null;
DataOutputStream os = null;
BufferedReader osReader = null;
BufferedReader osErrorReader = null;
try {
//执行命令
process = Runtime.getRuntime().exec(command);
//获得进程的输入输出流
os = new DataOutputStream(process.getOutputStream());
osReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
osErrorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
//输入 exit 命令以确保进程退出
os.writeBytes("exit\n");
os.flush();
int processResult;
String shellMessage;
String errorMessage;
//获取命令执行信息
shellMessage = readOSMessage(osReader);
errorMessage = readOSMessage(osErrorReader);
//获得退出状态
processResult = process.waitFor();
System.out.println("processResult : " + processResult);
System.out.println("shellMessage : " + shellMessage);
System.out.println("errorMessage : " + errorMessage);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (osReader != null) {
try {
osReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (osErrorReader != null) {
try {
osErrorReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (process != null) {
process.destroy();
}
}
}
//读取执行命令后返回的信息
private static String readOSMessage(BufferedReader messageReader) throws IOException {
StringBuilder content = new StringBuilder();
String lineString;
while ((lineString = messageReader.readLine()) != null) {
System.out.println("lineString : " + lineString);
content.append(lineString).append("\n");
}
return content.toString();
}
常用 Linux 命令
记录一些常用命令。
ls
列出当前目录下的所有文件
cat 文件
查看文件内容
mv 文件A 文件B
改名或移动文件 A 为文件
cp 文件A 文件B
拷贝文件 A 到文件 B
chmod {r|w|x} 文件名
修改文件的权限
echo 字符串
将字符串输出到标准输出
指令 > 文件名
输出重定向,将指令的输出信息输入到文件中,会覆盖文件
配合 echo
命令可以达到写入文件的效果
指令 >> 文件名
将指令输出添加到文件尾
以 root 权限执行命令
在 Android 中执行终端命令的常见场景就是获取 root 权限。Android API 中并没有提供在非开发机上获取 root 权限的功能,所以要使用获取 root 权限的命令 su
。
Android 默认不开放 su
命令,需要通过刷机破解才能得到使用 su
命令的权限。su
命令本质是在系统目录下的一个文件,刷机破解之后使该文件拥有最高权限且可以被任何用户访问。为了安全性考虑,在破解之后还会在系统中安装一个用来管理访问 su
命令的应用,通常为 SuperUser 应用。SuperUser 会在有人调用 su
命令时进行拦截,并在手机上显示窗口询问是否给予本次访问权限,在点击给予权限后就可以由 su
命令获得 root 权限。
以 root 权限执行命令的过程为在应用中调用 Runtime.getRuntime().exec("su")
来获得一个带有 root 权限的进程,在 su
命令成功后该进程中之后执行的所有命令都具有 root 权限。需要注意的是,执行 su
只是使新创建的进程具有 root 权限,应用本身并不具备 root 权限。
检查是否获取到 root 权限
鉴于执行 su
命令有可能会被 SuperUser 等应用拒绝,有时需要检查获取权限是否被拒绝。检查的方式有两种:
- 在进程中只执行
su
命令,然后调用waitFor()
退出获得退出状态,如果为退出状态 0 则获取成功。 - 在执行
su
命令后执行id
命令,如果返回的信息中包含uid=0
则确认已获得 root 权限。
执行 root 命令的代码
public static void executeCommand(String command, boolean isRoot, boolean checkPermission) {
Process process = null;
DataOutputStream os = null;
BufferedReader osReader = null;
BufferedReader osErrorReader = null;
try {
//如果需要 root 权限则执行 su 命令,否则执行 sh 命令
process = Runtime.getRuntime().exec(isRoot ? "su" : "sh");
os = new DataOutputStream(process.getOutputStream());
osReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
osErrorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
//检查是否获取到 root 权限
if (checkPermission && isRoot && !checkRootPermissionInProcess(os, process.getInputStream())) {
return;
}
os.writeBytes(command + "\n");
os.flush();
System.out.println("command : " + command);
os.writeBytes("exit\n");
os.flush();
String shellMessage;
int processResult;
String errorMessage;
shellMessage = readOSMessage(osReader);
errorMessage = readOSMessage(osErrorReader);
processResult = process.waitFor();
System.out.println("processResult : " + processResult);
System.out.println("shellMessage : " + shellMessage);
System.out.println("errorMessage : " + errorMessage);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (osReader != null) {
try {
osReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (osErrorReader != null) {
try {
osErrorReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (process != null) {
process.destroy();
}
}
}
//读取执行命令后返回的信息
private static String readOSMessage(BufferedReader messageReader) throws IOException {
StringBuilder content = new StringBuilder();
String lineString;
while ((lineString = messageReader.readLine()) != null) {
System.out.println("lineString : " + lineString);
content.append(lineString).append("\n");
}
return content.toString();
}
//用 id 命令检查是否获取到 root 权限
private static boolean checkRootPermissionInProcess(DataOutputStream os, InputStream osReader) throws IOException {
String currentUid = readCommandResult(os, osReader, "id");
System.out.println(currentUid);
if (currentUid.contains("uid=0")) {
System.out.println("ROOT: Root access granted");
return true;
} else {
System.out.println("ROOT: Root access rejected");
return false;
}
}
//执行一个命令,并获得该命令的返回信息
private static String readCommandResult(DataOutputStream os, InputStream in, String command) throws IOException {
os.writeBytes(command + "\n");
os.flush();
return readCommandResult(in);
}
//读取命令返回信息
private static String readCommandResult(InputStream in) throws IOException {
StringBuilder result = new StringBuilder();
// System.out.println("Before : " + in.available());
int available = 1;
while (available > 0) {
// System.out.println("In : " + in.available());
int b = in.read();
result.append((char) b);
// System.out.println((char) b + " " + b);
available = in.available();
}
// System.out.println("After : " + in.available());
return result.toString();
}
踩过的一些坑
在执行 cat
命令读取比较大的文件时,由于返回的信息过多,可能会使读取线程阻塞。造成这种状况的原因是由于
Process
类使用了标准输入输出流,而在某些平台系统中对标准输入输出流只提供了有限的缓存,所以如果你没有及时输入或输出而导致流里的信息超
过这个缓存,就会造成阻塞。解决办法是尽可能立即输入输出信息。
另一个问题是 Process
并没有提供执行一个命令然后获取该命令返回信息的接口。一个不是很好的解决方法是,执行一个命令,然后调用 InputStream.read()
读取一个字符,之后再调用 InputStream.available()
获得该命令返回的信息长度(该长度并不一定准确),最后在读取该长度的信息。比较奇怪的是不读取一个字符 InputStream.available()
返回值会一直为 0。
在日常使用时,如果没有必要,最好是在 Process
中只执行一个命令,不需要返回信息则不去读取信息。