17

我需要在我的 Java 应用程序中启动 1-3 个外部程序,这些程序的路径由用户定义。我有几个要求:

  1. 如果程序已经在运行,我不希望它执行

  2. 我不希望任何程序从我的 Java 应用程序中窃取焦点

  3. 我不在乎他们中的任何一个是否无法启动。他们只需要默默地失败。

到目前为止,这是我想出的:

ProcessBuilder pb = new ProcessBuilder(userDefinedPath1);
try {
    pb.start();
}
catch (Exception e) {
    // Something went wrong, just ignore
}

然后我用其他两条路径再重复 3 次。这就像我期望的那样开始并且很好地满足了我的第三个要求,但前两个失败了。

做这个的最好方式是什么?

编辑:

  1. 我无法控制这些其他应用程序。他们是第三方。此外,它们可以随时由用户手动启动或停止。

  2. 我知道可执行文件的确切名称(例如“blah.exe”),它们总是相同的,但可执行文件的路径不一定是。

  3. 批处理文件包装器在这里不可行。

  4. 其他应用程序不是Java 应用程序,只是普通的旧 Windows 可执行文件。

4

9 回答 9

9

为了避免潜在的锁定文件/崩溃问题,可以启动服务器并捕获端口冲突。这些服务器在系统关闭时自动停止(即使在崩溃之后)

public static ServerSocket ss;

public static void main (String[] args) {

    ss = null;

    try {
        ss = new ServerSocket(1044);
    } catch (IOException e) {
        System.err.println("Application already running!");
        System.exit(-1);
    }
}
于 2017-08-14T19:36:25.020 回答
6

我猜你无法控制其他两个应用程序......如果你这样做了,这不会太糟糕 - 你可以让他们听一个套接字,看看你来的时候套接字是否可用向上。

下一个解决方案实际上可能与语言无关。您可以通过批处理文件包装器管理整个系统。编写一个批处理文件,启动时创建文件,停止时删除文件。Unix 系统经常使用这种技术——他们经常将文件称为锁定文件。

如果只有您的应用程序会启动这些其他应用程序,那么您可以简单地跟踪您是否已启动它,所以我猜这是不可能的,或者您不会问,所以我假设用户可能已经通过其他一些机制启动了这些程序。

如果您无法控制其他应用程序的启动,甚至无法编写批处理文件来启动它们,那么您就无法做您想做的事情(注意,应用程序必须始终使用批处理文件,即使用户手动启动它们)。

我只是最后的努力可能是获取进程状态并解析它,但你必须确切地知道其他应用程序在 PS 中被称为什么,这并不是微不足道的。此外,所有 Java 应用程序在大多数进程状态打印输出中往往具有相同的确切签名,这可能会使其无用。

问题是,如果其中一个程序是在您的应用程序之外启动的,您几乎无法识别该事实,除非您碰巧知道它是确切的进程状态签名,即使那样它也是不稳定的。

于 2009-03-18T22:16:09.837 回答
6

我提供了两个答案,一个针对 Linux:

如果程序已经在运行,不要运行它,把它放在一个名为 Main.java 的文件中

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

class JustOneLock {
  FileLock lock;
  FileChannel channel;

  public boolean isAppActive() throws Exception{
    File file = new File(System.getProperty("user.home"),
            "FireZeMissiles1111" + ".tmp");
    channel = new RandomAccessFile(file, "rw").getChannel();

    lock = channel.tryLock();
    if (lock == null) {
      return true;
    }
    Runtime.getRuntime().addShutdownHook(new Thread() {
      public void run() {
        try {
          lock.release();
          channel.close();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    });
    return false;
  }
}

public class Main {
  public static void main(String[] args)throws Exception {
    JustOneLock u = new JustOneLock();

    if (u.isAppActive()) {
      System.out.println("Already active, stop!");
      System.exit(1);
    }
    else {
      System.out.println("NOT active... Do hard work for 5 seconds.");
      try{Thread.sleep(5000);}catch(Exception e){}
    }
  }
}

编译并运行它。然后打开一个新终端并尝试在另一个正在运行时再次运行它,但它不会。

Windows的另一个答案

如果该程序已在当前系统上运行,则该程序将不允许自己运行。这仅适用于 Windows 系统。

import java.io.*;
import java.util.prefs.Preferences;

public class JavaApplication3 {

    public static void main(String[] args){
        if(isRunning()){
            System.out.println("Two instances of this program cannot " +
                    "be running at the same time.  Exiting now");
        }
        else{
            onStart();
            epicHeavyWorkGoesHere();
            onFinish();
        }
    }
    public static void epicHeavyWorkGoesHere(){
        try {
            Thread.sleep(5000);
        } catch (InterruptedException ex) {}
    }
    public static void onStart(){
        Preferences prefs = Preferences.systemRoot().node("JavaApplication3");
        prefs.put("RUNNINGPID", getCurrentPID());
    }
    public static void onFinish(){
        Preferences prefs = Preferences.systemRoot().node("JavaApplication3");
        prefs.put("RUNNINGPID", "");
    }
    public static boolean isRunning(){
        Preferences prefs = Preferences.systemRoot().node("JavaApplication3");

        if (prefs.get("RUNNINGPID", null) == null || prefs.get("RUNNINGPID", null).equals(""))
            return false;

        if (isProcessIdRunningOnWindows(Integer.parseInt(prefs.get("RUNNINGPID", null))))
            return true;
        return false;
    }
    public static String getCurrentPID(){
        //This function is designed to get the PID from the windows system, it may
        //not work for Linux or Mac.  You'll have to acquire a suitable getCurrentPID function
        try{
            java.lang.management.RuntimeMXBean runtime = java.lang.management.ManagementFactory.getRuntimeMXBean();
            java.lang.reflect.Field jvm = runtime.getClass().getDeclaredField("jvm");
            jvm.setAccessible(true);
            sun.management.VMManagement mgmt = (sun.management.VMManagement) jvm.get(runtime);
            java.lang.reflect.Method pid_method = mgmt.getClass().getDeclaredMethod("getProcessId");
            pid_method.setAccessible(true);
            return pid_method.invoke(mgmt) + "";
        }
        catch(Exception e){
            throw new RuntimeException("Cannot get the current PID");
        }
    }
    public static boolean isProcessIdRunningOnWindows(int pid){
        //This Function only works for windows, if you want it to work on linux
        //you will have to go find a replacement method that takes the processID
        //as a parameter and spits out a true/false if it is running on the system.
        try {
            Runtime runtime = Runtime.getRuntime();
            String cmds[] = {"cmd", "/c", "tasklist /FI \"PID eq " + pid + "\""};
            Process proc = runtime.exec(cmds);

            InputStream inputstream = proc.getInputStream();
            InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
            BufferedReader bufferedreader = new BufferedReader(inputstreamreader);
            String line;
            while ((line = bufferedreader.readLine()) != null) {
                if (line.contains(" " + pid + " ")){
                    return true;
                }
            }
            return false;
        }
        catch (Exception ex) {
            throw new RuntimeException("Cannot run the tasklist command to query if a pid is running or not");
        }
    }
}

上述代码的策略是保留上次运行的 PID,如果发现该 PID 在系统上运行,则不要启动。如果完成,请重置。

首选项存储在 Windows 注册表中HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs

我建议不要使用文件锁定来确保 java 应用程序不会同时运行两次,因为如果程序崩溃或永远挂起并被杀死,那么锁就会处于不一致的状态,甚至可能在重启后仍然存在会导致问题,因为程序如何知道仍在运行的程序与已崩溃并保持锁定文件锁定的程序之间的区别?

于 2013-08-02T04:31:11.350 回答
3

这就是我为解决问题所做的,这是使用纯 java 的简单解决方案:

请注意,如果以下代码崩溃一次,它就会处于不一致的状态,并且无法处理挂起或崩溃的程序。

public void main(String[] args){
    if(isRunning()){
        JOptionPane.showMessageDialog(this, "2 instances of this program cannot be running at the same time. \n Exiting now");
        System.exit(0);
    }else{
        onStart();
    }
}    

public final void onStart(){
    Preferences prefs;
    prefs = Preferences.userRoot().node(this.getClass().getName());
    prefs.put("RUNNING", "true");
}

public final void onFinish(){
    Preferences prefs;
    prefs = Preferences.userRoot().node(this.getClass().getName());
    prefs.put("RUNNING", "false");
}

public boolean isRunning(){
    Preferences prefs;
    prefs = Preferences.userRoot().node(this.getClass().getName());
    return prefs.get("RUNNING", null) != null ? Boolean.valueOf(prefs.get("RUNNING", null)) : false;
}

private void formWindowClosing(java.awt.event.WindowEvent evt) {
    onFinish();
}
于 2012-02-01T15:24:34.120 回答
1

如果我在基于 unix 的系统上处理这个问题,我会很想编写一个本机函数来收集进程信息并尝试启动外部应用程序。这与 Java 的精神背道而驰,但是您尝试收集的信息类型和应用程序启动的控制在 JVM 之外。

我对 Windows 编程的了解不足以为您指明正确的方向,但我想您可以使用 .NET 语言或纯 C++ 访问 Windows API 调用,这可以帮助您满足第二个标准。也许您可以更改问题以吸引可以提供帮助的非 Java 开发人员。


编辑:

查看 Windows API 的Shell 执行函数。出现 SW_SHOWNOACTIVATE 选项以允许当前窗口保持活动状态。

于 2009-03-18T23:31:48.253 回答
1

请参阅检查程序或进程是否正在运行 (Windows)

此提示将检测给定程序是否正在运行。好的,由于使用了 vbscript,它并不漂亮,但它很容易实现。

于 2009-03-18T23:35:09.413 回答
1

您可以对所有系统做一件小事,这包括文件锁定功能。

在程序运行时首先,创建一个文件并锁定它。将其命名为 lockfile 之类的名称。生成时间戳并加密。把这个写进去。

在同一程序运行首先,检查是否存在锁定文件。尝试锁定它。忽略此文件被锁定的可能性。读取并检查时间戳。如果时间戳非常小(例如 1 小时前),请尝试运行该程序。您将错误地写回时间戳,因为它已被锁定。在这种情况下,假设有一个程序已经打开。

在程序退出时,删除此锁定文件。

于 2016-05-04T22:55:28.890 回答
0

在 Windows XP(专业版?)上,您可以启动命令“任务列表”并解析输出以确定进程是否正在运行。您可以使用线程来避免任何焦点问题(我认为)

于 2009-03-23T07:43:21.103 回答
0

您可以使用 jps 来了解正在运行的 java 程序状态。jps 是 javac、java 等 java 工具之一,

于 2009-07-14T08:29:18.347 回答