Process 简述
- public abstract class Processextends Object
- ProcessBuilder.start 和 Runtime.exec 方法都可以开启一个本机进程,并返回 Process 子类的一个实例,Process实例可控制进程并获得相关信息。
- Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法。
- 从JDK 1.5开始, ProcessBuilder.start()是创建Process的首选方式。
获取子进程输入流
- public abstract InputStream getInputStream() :获取子进程的输入流。
- 输入流获得由该 Process 对象表示的进程的标准输出流。
- 实现注意事项:对输入流进行缓冲是一个好主意。
- 返回连接到子进程正常输出的输入流。
public static void main(String[] args) { try { Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec("cmd /c dir %JAVA_HOME%",null); InputStream inputStream = process.getInputStream(); InputStreamReader streamReader = new InputStreamReader(inputStream,"gbk"); BufferedReader bufferedReader = new BufferedReader(streamReader); String readLine ; while ((readLine = bufferedReader.readLine())!=null){ System.out.println(readLine); } } catch (IOException e) { e.printStackTrace(); } }
- runtime.exec("cmd /c dir %JAVA_HOME%",null),可以直接获取windows系统的环境变量值,输出结果如下:
卷的序列号是 000C-81F5
D:\Java\jdk1.8.0_101 的目录
2017/07/29 周六 10:42 <DIR> .
2017/07/29 周六 10:42 <DIR> ..
2017/07/29 周六 10:40 <DIR> bin
2016/06/22 周三 01:42 3,244 COPYRIGHT
2017/07/29 周六 10:40 <DIR> db
2017/07/29 周六 10:40 <DIR> include
2017/07/29 周六 10:40 5,090,294 javafx-src.zip
2017/07/29 周六 10:40 <DIR> jre
2017/07/29 周六 10:40 <DIR> lib
2017/07/29 周六 10:40 40 LICENSE
2017/07/29 周六 10:40 159 README.html
2017/07/29 周六 10:40 528 release
2016/06/22 周三 01:42 21,251,669 src.zip
2017/07/29 周六 10:40 110,114 THIRDPARTYLICENSEREADME-JAVAFX.txt
2017/07/29 周六 10:40 177,094 THIRDPARTYLICENSEREADME.txt
8 个文件 26,633,142 字节
7 个目录 296,898,805,760 可用字节
Process finished with exit code 0
杀死子进程
- public abstract void destroy() :杀掉子进程,强制终止此 Process 对象表示的子进程。
- public Process destroyForcibly():杀死子进程,此Process对象表示的子进程被强制终止。 此方法的默认实现调用destroy() ,因此可能不会强制终止进程。 强烈建议使用此类的具体实现来使用兼容实现覆盖此方法。这是1.8新增的方法,也是推荐方式
- public boolean isAlive():测试这个Process代表的子 进程是否存活。 为true则表示此 Process对象表示的子进程尚未终止。
不加cmd直接打开
- 如下所示,Runtime 调用本地的PotPlayer程序播放了一个视频,返回Process对象,Process就代表着打开的PotPlayer子进程
- 休眠5秒之后判断PotPlayer子进程是否活着,如果活着则杀死它,即关闭PotPlayer程序
- 对于cmdStr这种没有加前缀"cmd /c"的写法,在杀死子进程时,是没有问题的,因为打开的时候,Process就直接记录的是PotPlayer.exe程序
import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Created by Administrator on 2018/6/27 0027. * 杀进程测试类 */ public class ProcessTest { public static void main(String[] args) { try { String cmdStr = "D:\\PotPlayer\\PotPlayerMini.exe E:/wmx/zl2.mp4"; Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec(cmdStr); Thread.sleep(5000); if (process.isAlive()){ process.destroy(); } } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }
加cmd打开程序
错误分析
- cmdStr1、cmdStr2 :对于exe程序是不用加"cmd /c"前缀就可以直接打开的,这种方式打开时,Process的destory()与destroyForcibly()方法能正常杀死子进程。而加了"cmd /c"前缀的cmdStr3、cmdStr4打开的文件或程序都是“杀不死”的
- 然而对于其它的大部分的DOS指令是必须借助cmd.exe程序的,cmdStr3、cmdStr4中的cmd字符表示的就是windows系统目录中的程序“C:\\Windows\\System32\\cmd.exe”
- 如下所示的cmdStr3、cmdStr4中要想打开普通的word文件,如txt、ppt、excel、pdf、png、mp4等等都是要借用cmd.exe程序才能打开的,否则会报错说找不到路径
- cmdStr3、cmdStr4 这种直接指明参数文件,而没有指定打开此文件的程序时,默认会使用文件的关联程序进行打开,如下面的“E:/wmx/演讲稿.pptx”,如果关联的是Office则用Office打开,如果关联的是WPS,则用WPS打开
import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Created by Administrator on 2018/6/27 0027. * 杀进程测试类 */ public class ProcessTest { public static void main(String[] args) { try { String cmdStr1 = "D:\\PotPlayer\\PotPlayerMini.exe E:/wmx/zl2.mp4"; String cmdStr2 = "D:\\PotPlayer\\GPaoPao.exe"; String cmdStr3 = "cmd /c E:/wmx/演讲稿.pptx"; String cmdStr4 = "cmd /c E:/wmx/zl2.mp4"; Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec(cmdStr4); Thread.sleep(5000); if (process.isAlive()) { process.destroy(); } } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }
- 加了"cmd /c”前缀就表示会打开cmd.exe程序来执行后面的命令,所以cmdStr3、cmdStr4在执行之后任务管理器就会有cmd进程,于是当Process调用destory()或者destroyForcibly()杀子进程的时候,杀死的只是cmd.exe,而真正需要结束的是打开mp4文件的PotPlayer,或者是打开pptx文件的WPS程序
解决方式 1
- 如果能准确知道打开文件的程序路径,则建议直接写上程序路径。如下所示,直接指定打开.docx文件的Office程序,这样结束子进程的时候就不会有问题了
import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Created by Administrator on 2018/6/27 0027. * 杀进程测试类 */ public class ProcessTest { public static void main(String[] args) { try { /** 当路径中有空格时,则应该程序和参数分开写*/ String[] paramArr = new String[2]; paramArr[0] = "C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\WINWORD.EXE"; paramArr[1] = "E:\\wmx\\Map_in-depth.docx"; Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec(paramArr); Thread.sleep(15000); if (process.isAlive()) { process.destroy(); } } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }
解决方式 2
- 因为很多时候可能并不知道某个文件到底将来会用什么程序打开,如.docx文件可能是Office打开,也可能是WPS打开
- 而且形如Office与WPS安装的路径也不一定知道或者路径不固定,此时采用杀进程树的方式来解决
- 因为加"cmd /c"前缀使用cmd.exe程序来执行命令时,是先打开了cmd程序,然后打开的文件关联程序打开文件,后者属于cmd.exe程序的子进程,它们属于同一个进程树,而cmd提供了杀死整个进程树的命令,taskkill
- taskkill /PID xxx /F /T:taskkill表示杀死任务,/PID表示按进程的pid进行关闭,xxx表示进程pid值,/F表示强制终止进程,/T表示终止指定的进程和由它启用的子进程。
- 这种方式时借助JNA的API会更加方便,可以参考《JNA 理论详解 1》、《JNA 实战 1》
- 使用JNA则先要导入它的两个开发包
import com.sun.jna.Pointer; import com.sun.jna.platform.win32.Kernel32; import com.sun.jna.platform.win32.WinNT; import java.lang.reflect.Field; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; /** * Created by Administrator on 2018/6/26 0026. * 系统工具类 */ public class SystemUtils {
package com.lct.utils; import com.sun.jna.Pointer; import com.sun.jna.platform.win32.Kernel32; import com.sun.jna.platform.win32.WinNT; import java.lang.reflect.Field; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; /** * Created by Administrator on 2018/6/26 0026. * 系统工具类 */ public class SystemUtils { /** * 杀死指定进程数,即包括process进程的所有子进程 * * @param process */ public static void killProcessTree(Process process) { try { if (process != null && process.isAlive()) { Field f = process.getClass().getDeclaredField("handle"); f.setAccessible(true); long handl = f.getLong(process); Kernel32 kernel = Kernel32.INSTANCE; WinNT.HANDLE handle = new WinNT.HANDLE(); handle.setPointer(Pointer.createConstant(handl)); int ret = kernel.GetProcessId(handle); Long PID = Long.valueOf(ret); String cmd = "cmd /c taskkill /PID " + PID + " /F /T "; Runtime rt = Runtime.getRuntime(); Process killPrcess = rt.exec(cmd); killPrcess.waitFor(); killPrcess.destroyForcibly(); } } catch (Exception e) { e.printStackTrace(); } } }
import com.lct.utils.SystemUtils; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Created by Administrator on 2018/6/27 0027. * 杀进程测试类 */ public class ProcessTest { public static void main(String[] args) { try { String cmdStr4 = "cmd /c E:\\wmx\\Map_in-depth.docx"; Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec(cmdStr4); /** 休眠5秒后关闭子进程树*/ Thread.sleep(5000); SystemUtils.killProcessTree(process); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }
- 这样就能杀死打开.docx文件的程序了