本篇仍旧是源于最近的工作,总结一下纪念那些年埋下的坑...
背景故事
- 需求:“使用进程方式启动另一个程序!”
- 开发:“OK! Runtime.getRuntime().exec("xxxx")”
- 需求:“启动以后能看到输出消息不!”
- 开发:“OK!”
Process process = null;
try {
process = Runtime.getRuntime().exec("ipconfig /all");
} catch (IOException e) {
e.printStackTrace();
}
try {
String line;
InputStream is = process.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is,"GBK"));
while(null != (line = br.readLine())){
System.out.println(line);
}
System.out.println("---------------------------------------------------------------------------------------");
InputStream is_error = process.getErrorStream();
BufferedReader br_error = new BufferedReader(new InputStreamReader(is_error,"GBK"));
while(null != (line = br_error.readLine())){
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
于是,神坑挖好了!
遇到的问题
由于运行的程序比较复杂,有可能出现错误输出。这时就不好保证是错误输出还是标准输出哪个先到。但是上面的程序中,使用了同步的方式输出子进程的消息,结果就导致了子进程阻塞。
解决方案1:使用缓冲区缓存消息
解决方案2:使用ProcessBuilder合并标准输出和错误
仍然源自于上面的博客:
try{
String[] cmds = {"ipconfig","/all"};
ProcessBuilder builder = new ProcessBuilder(cmds);
//合并输出流和错误流
builder.redirectErrorStream(true);
//启动进程
Process process = builder.start();
//获得输出流
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream(),"GBK"));
String line = null;
while (null != (line = br.readLine())){
System.out.println(line);
}
}catch(IOException e){
e.printStackTrace();
}
上面的代码中builder.redirectErrorStream(true);
就可以帮助你把错误合并到标准输出里面。
于是,很好奇这个ProcessBuilder到底什么东东。
阅读API —— 什么是ProcessBuilder
ProcessBuilder用于创建操作系统进程,每个ProcessBuilder实例都管理一个进程属性集合。通过调用start()方法,可以通过这些属性创建出一个进程。start()方法可以被多次调用,来创建多个独立的进程。
每个builder管理着下面的进程属性:
cmmand
命令,比如{“ipcofig”,"/all"}
environment
环境变量,子进程会直接使用当前进程的环境变量。环境变量是独立的,因此可以被修改,但是不会影响其他的进程。
directory
工作目录,如果返回的是Null,说明当前目录使用的是系统变量user.dir所在的目录。
redirectErrorStream属性
默认是false。Flase意味着标准输出和标准错误是两个独立的流,可以通过Process.getInputStream()和Process.getErrorStream()方法获得。
如果这个值设置为true,那么标准错误将会合并到标准输出中,并且发往同一个目标地址(这种特性使得错误消息可以很方便的和输出消息一起管理),此时,如果你再想要单独获取错误输出流,就会得到null。
线程安全
注意这个类不是线程安全的,因此如果多个线程使用ProcessBuilder实例,并且修改属性,那么可能会造成冲突。因此需要在外面进行线程同步。
启动
可以简单的向下面这样启动一个进程:
Process p = new ProcessBuilder("myCommand", "myArg").start();
样例
下面是官方文档中给出的样例,样例中修改了工作目录以及环境变量,并且把标准错误和标准输出合并输出到日志文件中:
ProcessBuilder pb = new ProcessBuilder("myCommand", "myArg1", "myArg2");
Map<String, String> env = pb.environment();
env.put("VAR1", "myValue");
env.remove("OTHERVAR");
env.put("VAR2", env.get("VAR1") + "suffix");
pb.directory(new File("myDir"));
File log = new File("log");
pb.redirectErrorStream(true);
pb.redirectOutput(Redirect.appendTo(log));
Process p = pb.start();
assert pb.redirectInput() == Redirect.PIPE;
assert pb.redirectOutput().file() == log;
assert p.getInputStream().read() == -1;