本篇仍舊是源於最近的工作,總結一下紀念那些年埋下的坑...
背景故事
-
需求:“使用進程方式啟動另一個程序!”
-
開發:“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;