您的位置:首页 > 运维架构 > Linux

在 Android 中执行 Linux 终端命令

2016-03-23 16:59 537 查看
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
中只执行一个命令,不需要返回信息则不去读取信息。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: