在 Android 中执行 Linux 终端命令

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 等应用拒绝,有时需要检查获取权限是否被拒绝。检查的方式有两种:

  1. 在进程中只执行 su 命令,然后调用 waitFor() 退出获得退出状态,如果为退出状态 0 则获取成功。
  2. 在执行 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 中只执行一个命令,不需要返回信息则不去读取信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值