Construct Single-Launch Applications





Construct Single-Launch Applications

Only allow one instance of a program, notifying the existing instance when the user tries to launch a new one.

Most graphical desktop applications are designed for multitasking. You start your program to work on something, then switch to another program and come back later. Oftentimes you'll leave a large program, like a word processor, running in the background to be used again when you need to open another document, say an email attachment you received. When you click on the attachment, your operating system won't start a new instance of the word processor; instead, it will send a message to the currently running instance to open the new filesaving lots of system resources.

Java programs aren't designed with single-launch behavior in mind. They still use the old Unix style of single use, command-line launching. You start the program to do something and it finishes quickly. If you use the program again it will start a new instance, do the work, and finish. There is never any instance reuse, but modern desktop programs demand it. Because Java doesn't support single-launch applications, this hack shows you how to build it into your programs with a simple use of sockets.

Local Sockets

Building a single-launch application requires two parts. When the program starts, it needs to detect if another copy is already running. If there is, then the program can quit instead of continuing to launch. The new program also needs to tell the first copy about any command-line argumentsthe filename to open, for example. You could create a temp file in a known location and look to see if it already exists. This would take care of multiple program instances, but not passing arguments around. Plus, you would need to worry about race conditions and cleaning up the temp file when the last program exits. Thankfully, there's a much better solution: local sockets.

A socket is a network connection defined by a hostname and a port. A local socket is a network connection only on the local machine. When you open a socket to listen for connections you are binding to that port. Only one program can bind to any given port at one time, so the port itself can serve as your lock. If your program cannot connect to the port, then another program must already be using it. It doesn't matter what the port number is, as long as your program always uses the same one. Here is the beginning of the code to put this into action:

	public class SingleLauncherApplication implements Runnable {
		public static final int PORT = 38629;
		public JLabel label;
		public ServerSocket server;

		public void launch(String[] args) {
			try {
				server = new ServerSocket(PORT);
				new Thread(this).start();
				firstMain(args);

			} catch (IOException ioex) {
				System.out.println("already running!");
				relaunch(args);

			}
		}

SingleLauncherApplication is a class with one core method: launch(). launch() takes the same arguments as the standard main() method, which is important since you are essentially creating fake versions of main. When launch is called, it will first try to bind to the port by creating a server socket. Notice the PORT constant set to the number 38629. It is important that the port isn't reserved for use by any system services. Some operating systems also restrict user programs to only use ports over 1,000, so I picked this number at random from the 20,000 to 60,000 range. This makes it very unlikely that the port will already be in use by any other program.

If launch() can create a ServerSocket, then it will start a new thread, engage run(), and then call firstMain():

	public void firstMain(String[] args) {
		JFrame frame = new JFrame("Single Launch Application");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		String word = "";
		if(args.length >= 1) {

			word = args[0];
		}
		label = new JLabel("The word of the day is: " + word);
		frame.getContentPane().add(label);
		frame.pack();
		frame.show();
		
	}

firstMain() contains the normal startup code that would have gone in main() previously. Here, it creates a new JFrame with a single label in it, containing the text "The word of the day is:" followed by the first commandline argument.

Next comes the run() method, which is launched in the new thread by launch(). Its entire purpose in life is to sit on that port and wait for connections. If another instance connects, then it reads in the arguments and calls otherMain() to relaunch the application:

	public void run() {
		System.out.println("waiting for a connection");
		while(true) {
			try {
				// wait for a socket connection
				Socket sock = server.accept();
				
				// read the contents into a string buffer
				InputStreamReader in = new InputStreamReader(
				sock.getInputStream());    
				StringBuffer sb = new StringBuffer(); 
				char[] buf = new char[256];
				while(true) {
				int n = in.read(buf);
				if(n < 0) { break; }
				sb.append(buf,0,n);
				}
				// split the string buffer into strings
				String[] results = sb.toString().split("\\n");
				// call other main
				otherMain(results);

			} catch (IOException ex) {
				System.out.println("ex: " + ex);
				ex.printStackTrace();

			}
		}
	}

server.accept() will block until another program connects. When one does, the code will open an input stream for the socket and begin to read out characters and save it in a string buffer. If in.read(buf) returns -1 (n<0), then it knows the other end disconnected and that's all there is. Command-line arguments are just strings, but they may have spaces in them, so the code above will split the string buffer by new lines ("\\n") into an array of strings. Finally, it calls otherMain() with the string array:

	public void otherMain(final String[] args) {
		if(args.length >= 1) {
			SwingUtilities.invokeLater(new Runnable() {


	         public void run() { 
				label.setText("The word of the day is: " + args[0]);
			}
		});
	}
}

otherMain() doesn't repeat the startup steps of firstMain(). Instead it just changes the text of the label to match the new command-line arguments. Because Swing isn't thread-safe, you can't set the label text in the launching thread. Instead, you need to call it from the Swing event thread. The easiest way to do this is with the utility method: SwingUtilities.invokeLater(). You can pass it an anonymous Runnable() that does the actual setText() call. Note that the input variable args has to be made final for this to work.

If this is the second instance of the program, then the new ServerSocket() will fail, and relaunch() is called to send the command-line arguments to the first instance:

	public void relaunch(String[] args) {
		try {
		// open a socket to the original instance
		Socket sock = new Socket("localhost",PORT);

		// write the args to the output stream 
		OutputStreamWriter out = new OutputStreamWriter(
			sock.getOutputStream());
		for(int i=0; i<args.length; i++) {
			out.write(args[i]+"\n");
			p("wrote: " + args[i]);
		}
		// cleanup
		out.flush();
		out.close();
		
	} catch (Exception ex) {
		System.out.println("ex: " + ex);
		ex.printStackTrace();

	}
}

relaunch() opens a socket to the port on localhost. Since new ServerSocket() failed, it knows that the first instance of the program is already waiting on the other side of that port. Once it connects, it writes the command-line arguments to the socket's output stream, separating each argument with a newline (\n). Finally, it flushes the output stream to make sure everything was written, and then closes it. After that, the relaunch() and launch() methods will return, quitting the second instance of the program. The arguments have now been sent to the first instance.

To actually start this whole class, you simply call the launch method like this:

	public static void main(String[] args) {
	   new SingleLauncherApplication().launch(args);
	  }

The first time the program is launched it will create the window with the label. For example, running the following command would result in Figure.

	java -cp . SingleLauncherApplication 'anonymous'

Initial launch


Now, start the program again, with the first running:

	java -cp . SingleLauncherApplication 'perspicacity'

Instead of creating a new window, the program will contact the original (and still running) instance and change the text to .

After relaunching



     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows