当你没跨过一个问题的时候,这个问题就会一直跟着你。
在安卓中我们可以使用Process来执行名命令行命令,我们来看看如何使用。
首先是实例化,可以通过Runtime.getRuntime().exec(cmd)获取到Process,exec中写入我们需要执行的命令行即可。最简单的示例如下:
public static int execCmd(String cmd) {
int result = -1;
DataOutputStream dos = null;
try {
Process p = Runtime.getRuntime().exec("su");
dos = new DataOutputStream(p.getOutputStream());
dos.writeBytes(cmd + "\n");
dos.flush();
dos.writeBytes("exit\n");
dos.flush();
p.waitFor();
result = p.exitValue();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (dos != null) {
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
}
这里的su表示切换用户身份,以达到root的权限,后面调用dos.writeBytes(cmd + "\n"); 执行命令行,这里我们需要注意的方法是dos.flush(); 和p.waitFor(); 和p.exitValue(); 。
dos.flush();的作用是刷新缓冲区的信息
p.waitFor();的作用是等待子进程完成
p.exitValue();是获取命令行执行的结果,0 表示正确执行了。
尤其的是p.waitFor();会导致线程挂起,所以我们执行命令行的代码一般要写在子线程中,这里看下线程挂起是什么一个过程。
主进程中调用Runtime.exec会创建一个子进程,用于执行shell脚本。子进程创建后会和主进程分别独立运行。
因为主进程需要等待脚本执行完成,然后对脚本返回值或输出进行处理,所以这里主进程调用Process.waitfor等待子进程完成。
通过shell脚本可以看出:子进程执行过程就是不断的打印信息。主进程中可以通过Process.getInputStream和Process.getErrorStream获取并处理。
这时候子进程不断向主进程发生数据,而主进程调用Process.waitfor后已挂起。当前子进程和主进程之间的缓冲区塞满后,子进程不能继续写数据,然后也会挂起。
这样子进程等待主进程读取数据,主进程等待子进程结束,两个进程相互等待,最终导致死锁。
还有一点就是result = p.exitValue(); 必须要写在p.waitFor(); 的后面,因为二者并不是线性执行的,如果放在之前调用,没有等命令行执行完成就调用,可能会报错:process has not exited。
为了解决线程缓冲区阻塞导致线程挂起,我们可以不断处理缓冲区中的数据,避免阻塞,比如我们看一下在root权限下实现静默升级功能的代码,如下:
public void installAppWithRoot(String path) throws Exception {
Process process = Runtime.getRuntime().exec("su");
DataOutputStream dos = new DataOutputStream(process.getOutputStream());
dos.writeBytes("pm install -r " + path + "\n");
dos.flush();
dos.writeBytes("exit" + "\n");
dos.flush();
BufferedReader errorStream = null;
errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream()));
StringBuilder errorMsg = new StringBuilder();
String line;
while ((line = errorStream.readLine()) != null) {
errorMsg.append(line);
}
Log.d("apk安装错误信息", String.valueOf(errorMsg));
BufferedReader successStream = null;
StringBuilder successMsg = new StringBuilder();
successStream = new BufferedReader(new InputStreamReader(process.getInputStream()));
// 读取命令执行结果
while ((line = successStream.readLine()) != null) {
successMsg.append(line);
}
Log.d("apk安装正确信息", String.valueOf(successMsg));
process.waitFor();
int result = process.exitValue();
Log.d("apk安装返回信息", result + "");
}
及时的处理缓冲区中的信息,打印出命令的输出流和错误信息,并保证了线程的正常执行。