仅在尚未运行时启动 Java 程序

发布于 2024-07-15 11:00:22 字数 772 浏览 12 评论 0原文

我需要在 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 可执行文件。

I need to start 1-3 external programs in my Java application that have paths defined by the user. I have few requirements:

  1. I don't want the program to execute if it is already running

  2. I don't want any of the programs to steal focus from my Java application

  3. I don't care if any of them fail to start or not. They just need to fail silently.

Here is what I have come up with so far:

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

And then I repeat that 3 more times with the other two paths. This starts like I would expect and meets my third requirement just fine, but fails on the first two.

What is the best way to do this?

Edit:

  1. I don't have any control of these other apps. They are third party. Also, they could have been start or stopped by the user manually at any time.

  2. I know the exact names of the executables (e.g. "blah.exe") and they will always be the same, but the paths to the executables won't necessarily be.

  3. Batch file wrappers are not feasible here.

  4. The other apps are not java apps, just plain old Windows executables.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

二智少女猫性小仙女 2024-07-22 11:00:22

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

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);
    }
}

In order to avoid potential lock file / crash issues, it is possible to start a server and catch the port collision. These servers are automatically stopped on system shutdown (Even after a crash)

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);
    }
}
苍白女子 2024-07-22 11:00:22

我猜你无法控制其他两个应用程序...如果你这样做了,这还不错——你可以让它们监听套接字,并在你来时查看套接字是否可用向上。

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

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

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

我最后的努力可能是获取进程状态并解析它,但是您必须确切地知道其他应用程序在 PS 中的名称,这并不是一件小事。 此外,所有 Java 应用程序在大多数进程状态打印输出中往往具有相同的精确签名,这可能使其毫无用处。

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

I'm guessing you don't have control over the other two apps... If you did, this wouldn't be too bad--you could just have them listen to a socket and see if the socket is available when you come up.

The next solution may actually be language independent. You could manage the whole system through batch file wrappers. Write a batch file that creates a file when it starts up and deletes it when it stops. Unix systems use this technique a lot--they call the file a lock file more often than not.

If only your app will ever start these other apps, then you could simply track if you've started it or not, so I'm guessing this isn't possible or you wouldn't be asking, so I'm assuming that the user may have launched these programs through some other mechanism.

If you have NO control over the launching of the other apps and can't even write a batch file to launch them, then you just can't do what you want to do (Note, the apps would have to always use the batch file, even if the user started them by hand).

I just a very-last ditch effort might be to get a process status and parse it, but you'd have to know exactly what the other apps were called in the PS, this isn't really trivial. Also, all java apps tend to have the same exact signature in most process status printouts which could make this useless.

The problem is that if one of these programs were started outside your app, you have virtually NO WAY to identify that fact unless you happen to know it's exact process status signature, and even then it's flaky.

逆流 2024-07-22 11:00:22

我提供两个答案,一个适用于 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 应用程序不会同时运行两次,因为如果程序崩溃或挂起永远并被杀死,那么锁就会处于不一致的状态,甚至可能在重新启动后仍然存在,这会导致问题,因为程序如何知道仍在运行的程序和已崩溃并保持锁定的程序之间的区别文件被锁定?

I provide two answers, one for Linux:

Don't run the program if it's already running, put this in a file called 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){}
    }
  }
}

Compile it and run it. Then open a new terminal and try to run it again while the other is running and it won't.

Another answer for Windows

This program will not allow itself to be run if it is already running on the current system. This is for windows only systems.

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");
        }
    }
}

The strategy of the above code is to keep the PID around from the last run, if that PID is found running on the system, don't start. If you finish, reset.

The preferences are stored on Windows Registry in HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs

I advise against using File Locking to make sure a java application doesn't run twice at the same time because if the program crashes or hangs forever and is killed, then the lock is left in an inconsistent state and perhaps may even survive a reboot which will cause problems because how will the program know the difference between a program which is still running and a program that has crashed and left the locked file locked?

沧桑㈠ 2024-07-22 11:00:22

这就是我为解决问题所做的,这是使用纯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();
}

This is what I did to solve the problem, it's a simple solution in for using pure java:

Note, if the following code crashes once, it is left in an inconsistent state and has no way of dealing with a hung or crashed program.

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();
}
幸福丶如此 2024-07-22 11:00:22

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

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


编辑:

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

If I were dealing with this problem on a unix based system I would be tempted to write a native function to gather process information and attempt to launch the external applications. This goes against the spirit of Java, but the type of information you are trying to gather and the control of application launch are outside of the JVM.

I do not know enough about Windows programming to point you in the right direction, but I imagine there are Windows API calls you could access in .NET languages or plain C++ that could help you with your second criterion. Perhaps you could change the question to attract the non-Java developers who could help.


Edit:

Check out the Shell Execute Function of the Windows API. The SW_SHOWNOACTIVATE option appears to allow the current window to remain active.

岁月静好 2024-07-22 11:00:22

您可以对所有系统执行一项小操作,其中包括文件锁定功能。

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

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

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

There is one minor thing you can do to all systems, and this includes the file lock ability.

On program run
First, create a file and lock it. Name it something like lockfile. Generate a timestamp and encrypt it. Write this into it.

On same program run
First, check if there is a lock file present. Attempt to lock it. Ignore the possibility that this file is locked. Read and check timestamp. If the time stamp is something inanely small (like 1 hour ago), attempt to run the program. You will error writing a timestamp back, for it is locked. In this case, assume there is a program already open.

On program exit, delete this lock file.

超可爱的懒熊 2024-07-22 11:00:22

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

on windows xp (pro?) you can launch the command 'tasklist' and parse the output to determine if a process is running.
you can use threads for avoiding any problem with focus (i think)

习ぎ惯性依靠 2024-07-22 11:00:22

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

You can use jps to know the running java program status.
jps is one the java tool like javac, java etc.,

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文