问题
I need to start 1-3 external programs in my Java application that have paths defined by the user. I have few requirements:
I don't want the program to execute if it is already running
I don't want any of the programs to steal focus from my Java application
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:
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.
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.
Batch file wrappers are not feasible here.
The other apps are not java apps, just plain old Windows executables.
回答1:
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.
回答2:
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?
回答3:
I am very confident this is the best way; The application runs a "server" at startup, and if the server's port is already taken, the application closes.
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);
}
}
回答4:
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();
}
回答5:
See Check if a program or process is running (Windows)
This tip will detect if a given program is running. Ok, it's not pretty because of the vbscript but it's easy to implement.
回答6:
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.
回答7:
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.
回答8:
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)
回答9:
You can use jps to know the running java program status. jps is one the java tool like javac, java etc.,
来源:https://stackoverflow.com/questions/660205/start-java-program-only-if-not-already-running