studio - ¿Cómo implementar una aplicación Java de instancia única?
programacion android pdf 2018 (15)
Clase ManagementFactory admitida en J2SE 5.0 o posterior
pero ahora uso J2SE 1.4 y encontré este http://audiprimadhanty.wordpress.com/2008/06/30/ensuring-one-instance-of-application-running-at-one-time/ pero nunca lo pruebo. ¿Qué piensa usted al respecto?
En algún momento veo muchas aplicaciones como msn, windows media player, etc. que son aplicaciones de instancia única (cuando el usuario se ejecuta mientras la aplicación está ejecutándose, no se creará una nueva instancia de la aplicación).
En C #, utilizo la clase Mutex
para esto, pero no sé cómo hacer esto en Java.
En Windows, puede usar launch4j .
He encontrado una solución, una explicación un tanto caricaturesca, pero todavía funciona en la mayoría de los casos. Utiliza el antiguo archivo de bloqueo simple creando cosas, pero en una vista bastante diferente:
http://javalandscape.blogspot.com/2008/07/single-instance-from-your-application.html
Creo que será una ayuda para aquellos con una configuración de firewall estricta.
Podría intentar usar la API de Preferencias. Es plataforma independiente.
Puede abrir un archivo asignado a la memoria y luego ver si ese archivo ya está abierto. si ya está abierto, puedes regresar desde main.
Otras formas es usar archivos de bloqueo (práctica estándar de Unix). Una forma más es colocar algo en el portapapeles cuando se inicia el proceso principal después de verificar si algo ya está en el portapapeles.
De lo contrario, puede abrir un socket en un modo de escucha (ServerSocket). Primero intente conectarse a la toma de corriente; si no puede conectarse, abra un servidor. si te conectas, sabrás que ya se está ejecutando otra instancia.
Entonces, casi cualquier recurso del sistema se puede usar para saber que se está ejecutando una aplicación.
BR, ~ A
Puede usar la biblioteca JUnique. Proporciona soporte para ejecutar aplicaciones java de instancia única y es de código abierto.
http://www.sauronsoftware.it/projects/junique/
La biblioteca JUnique se puede utilizar para evitar que un usuario ejecute al mismo tiempo más instancias de la misma aplicación Java.
JUnique implementa bloqueos y canales de comunicación compartidos entre todas las instancias de JVM lanzadas por el mismo usuario.
public static void main(String[] args) {
String appId = "myapplicationid";
boolean alreadyRunning;
try {
JUnique.acquireLock(appId, new MessageHandler() {
public String handle(String message) {
// A brand new argument received! Handle it!
return null;
}
});
alreadyRunning = false;
} catch (AlreadyLockedException e) {
alreadyRunning = true;
}
if (!alreadyRunning) {
// Start sequence here
} else {
for (int i = 0; i < args.length; i++) {
JUnique.sendMessage(appId, args[0]));
}
}
}
Debajo del capó, crea bloqueos de archivos en la carpeta% USER_DATA% /. Junique y crea un socket de servidor en un puerto aleatorio para cada ID de aplicación única que permite enviar / recibir mensajes entre aplicaciones Java.
Sí, esta es una respuesta realmente decente para eclipse RCP eclipse aplicación de instancia única a continuación es mi código
en application.java
if(!isFileshipAlreadyRunning()){
MessageDialog.openError(display.getActiveShell(), "Fileship already running", "Another instance of this application is already running. Exiting.");
return IApplication.EXIT_OK;
}
private static boolean isFileshipAlreadyRunning() {
// socket concept is shown at http://www.rbgrn.net/content/43-java-single-application-instance
// but this one is really great
try {
final File file = new File("FileshipReserved.txt");
final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
final FileLock fileLock = randomAccessFile.getChannel().tryLock();
if (fileLock != null) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
fileLock.release();
randomAccessFile.close();
file.delete();
} catch (Exception e) {
//log.error("Unable to remove lock file: " + lockFile, e);
}
}
});
return true;
}
} catch (Exception e) {
// log.error("Unable to create and/or lock file: " + lockFile, e);
}
return false;
}
Si creo en este article , por:
teniendo la primera instancia intento de abrir un socket de escucha en la interfaz localhost. Si es capaz de abrir el socket, se supone que esta es la primera instancia de la aplicación que se lanzará. De lo contrario, se supone que ya se está ejecutando una instancia de esta aplicación. La nueva instancia debe notificar a la instancia existente que se intentó un inicio y luego salir. La instancia existente se hace cargo después de recibir la notificación y activa un evento para el oyente que maneja la acción.
Nota: Ahe menciona en el comentario que usar InetAddress.getLocalHost()
puede ser complicado:
- no funciona como se esperaba en DHCP-environment porque la dirección devuelta depende de si la computadora tiene acceso a la red.
La solución fue abrir la conexión conInetAddress.getByAddress(new byte[] {127, 0, 0, 1})
;
Probablemente relacionado con el error 4435662 .
- También encontré el error 4665037 que informa que los resultados esperados de
getLocalHost
: devolver la dirección IP de la máquina, frente a los resultados reales: return127.0.0.1
.
es sorprendente tener
getLocalHost
return127.0.0.1
en Linux pero no en Windows.
O puede usar el objeto ManagementFactory
. Como se explica here :
El
getMonitoredVMs(int processPid)
recibe como parámetro el PID de la aplicación actual y captura el nombre de la aplicación que se llama desde la línea de comandos, por ejemplo, la aplicación se inició desde la rutac:/java/app/test.jar
, luego el valor la variable es "c://java//app//test.jar
". De esta forma, capturaremos solo el nombre de la aplicación en la línea 17 del código a continuación.
Después de eso, buscamos JVM para otro proceso con el mismo nombre, si lo encontramos y el PID de la aplicación es diferente, significa que es la segunda instancia de la aplicación.
JNLP también ofrece un SingleInstanceListener
Si la aplicación. tiene una GUI, inicie con JWS y use SingleInstanceService
. Vea la demostración. del SingleInstanceService para el código de ejemplo (demostración y).
Una forma más genérica de limitar el número de instancias en una sola máquina, o incluso en una red completa, es usar un socket de multidifusión.
El uso de un socket de multidifusión le permite transmitir un mensaje a cualquier cantidad de instancias de su aplicación, algunas de las cuales pueden estar en máquinas físicamente remotas a través de una red corporativa.
De esta forma puede habilitar muchos tipos de configuraciones, controlar cosas como
- Una o muchas instancias por máquina
- Una o muchas instancias por red (por ejemplo, controlar las instalaciones en un sitio de cliente)
El soporte de multidifusión de Java se realiza a través del paquete java.net, siendo MulticastSocket y DatagramSocket las herramientas principales.
Nota : MulticastSocket no garantiza la entrega de paquetes de datos, por lo que debe usar una herramienta creada sobre conectores de multidifusión como JGroups . JGroups garantiza la entrega de todos los datos. Es un solo archivo jar, con una API muy simple.
JGroups ha existido desde hace un tiempo, y tiene algunos usos impresionantes en la industria, por ejemplo, apuntala el mecanismo de agrupación de JBoss para transmitir datos a todas las instancias de un clúster.
Para usar JGroups, limitar el número de instancias de una aplicación (en una máquina o en una red, digamos: a la cantidad de licencias que un cliente ha comprado) es conceptualmente muy simple:
- Tras el inicio de su aplicación, cada instancia intenta unirse a un grupo nombrado, por ejemplo, "Mi gran grupo de aplicaciones". Habrás configurado este grupo para permitir 0, 1 o N miembros
- Cuando el recuento de miembros del grupo es mayor que el que ha configurado para él ... su aplicación debe rechazar iniciarse.
Usé conectores para eso y, dependiendo de si la aplicación está del lado del cliente o del servidor, el comportamiento es un poco diferente:
- lado del cliente: si ya existe una instancia (no puedo escuchar en un puerto específico), pasaré los parámetros de la aplicación y saldré (es posible que desee realizar algunas acciones en la instancia anterior), de lo contrario, iniciaré la aplicación.
- lado del servidor: si ya existe una instancia, imprimiré un mensaje y saldré; si no, comenzaré la aplicación.
Uso el siguiente método en el método principal. Este es el método más simple, más robusto y menos intrusivo que he visto, así que pensé que lo compartiría.
private static boolean lockInstance(final String lockFile) {
try {
final File file = new File(lockFile);
final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
final FileLock fileLock = randomAccessFile.getChannel().tryLock();
if (fileLock != null) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
fileLock.release();
randomAccessFile.close();
file.delete();
} catch (Exception e) {
log.error("Unable to remove lock file: " + lockFile, e);
}
}
});
return true;
}
} catch (Exception e) {
log.error("Unable to create and/or lock file: " + lockFile, e);
}
return false;
}
Utilizamos el bloqueo de archivos para esto (tomamos un bloqueo exclusivo en un archivo mágico en el directorio de datos de la aplicación del usuario), pero estamos interesados principalmente en evitar que se ejecuten varias instancias.
Si está tratando de hacer que la segunda instancia pase args de línea de comando, etc ... a la primera instancia, entonces usar una conexión de socket en localhost matará dos pájaros de un tiro. Algoritmo general:
- En el lanzamiento, intente abrir el oyente en el puerto XXXX en localhost
- si falla, abra un escritor a ese puerto en localhost y envíe los argumentos de línea de comando, luego cierre
- de lo contrario, escuche en el puerto XXXXX en localhost. Cuando reciba args de línea de comando, trátelos como si la aplicación se hubiera iniciado con esa línea de comando.
EDITAR : en lugar de utilizar este enfoque de WatchService, se podría usar un simple subproceso de temporizador de 1 segundo para verificar si el indicatorFile.exists (). Eliminarlo, luego llevar la aplicación a Front ().
EDITAR : Me gustaría saber por qué esto fue votado negativamente. Es la mejor solución que he visto hasta ahora. Por ejemplo, el enfoque del socket del servidor falla si otra aplicación ya está escuchando el puerto.
Simplemente descargue Microsoft Windows Sysinternals TCPView (o use netstat), inícielo, ordene por "Estado", busque el bloque de línea que dice "ESCUCHAR", elija uno cuya dirección remota diga el nombre de su computadora, ponga ese puerto en su nuevo socket ()-solución. En mi implementación de esto, puedo producir fallas todo el tiempo. Y es lógico , porque es la base misma del enfoque. ¿O qué no estoy obteniendo con respecto a cómo implementar esto?
¡Por favor, infórmenme si me equivoco y cómo me equivoco!
Mi punto de vista, que le pido que refute si es posible, es que se recomienda a los desarrolladores usar un enfoque en el código de producción que fallará en al menos 1 de aproximadamente 60000 casos. Y si esta vista resulta ser correcta, no puede ser que una solución presentada que no tiene este problema sea downvoted y criticada por su cantidad de código.
Desventajas del enfoque socket en comparación:
- Falla si se elige el boleto de lotería equivocado (número de puerto).
- Se produce un error en el entorno de varios usuarios: solo un usuario puede ejecutar la aplicación al mismo tiempo. (Mi enfoque debería modificarse ligeramente para crear el archivo (s) en el árbol de usuarios, pero eso es trivial).
- Falla si las reglas del firewall son demasiado estrictas.
- Hace que los usuarios sospechosos (que conocí en la naturaleza) se pregunten qué chanchullos estás haciendo cuando tu editor de texto reclama un socket de servidor.
Acabo de tener una buena idea de cómo resolver el problema de comunicación Java de nueva instancia con instancia existente de una manera que debería funcionar en todos los sistemas. Entonces, aumenté esta clase en aproximadamente dos horas. Funciona como un encanto: D
Se basa en el enfoque de bloqueo de archivos de Robert (también en esta página), que he usado desde entonces. Para indicar a la instancia en ejecución que otra instancia intentó iniciar (pero no lo hizo) ... se crea un archivo y se elimina de inmediato, y la primera instancia usa WatchService para detectar el cambio de contenido de esta carpeta. No puedo creer que aparentemente esta sea una idea nueva, dado cuán fundamental es el problema.
Esto puede cambiarse fácilmente para simplemente crear y no eliminar el archivo, y luego se puede ingresar información que la instancia apropiada puede evaluar, por ejemplo, los argumentos de línea de comando, y la instancia apropiada puede realizar la eliminación. Personalmente, solo necesitaba saber cuándo restaurar la ventana de mi aplicación y enviarla al frente.
Ejemplo de uso:
public static void main(final String[] args) {
// ENSURE SINGLE INSTANCE
if (!SingleInstanceChecker.INSTANCE.isOnlyInstance(Main::otherInstanceTriedToLaunch, false)) {
System.exit(0);
}
// launch rest of application here
System.out.println("Application starts properly because it''s the only instance.");
}
private static void otherInstanceTriedToLaunch() {
// Restore your application window and bring it to front.
// But make sure your situation is apt: This method could be called at *any* time.
System.err.println("Deiconified because other instance tried to start.");
}
Aquí está la clase:
package yourpackagehere;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import java.nio.file.*;
/**
* SingleInstanceChecker v[(2), 2016-04-22 08:00 UTC] by dreamspace-president.com
* <p>
* (file lock single instance solution by Robert https://.com/a/2002948/3500521)
*/
public enum SingleInstanceChecker {
INSTANCE; // HAHA! The CONFUSION!
final public static int POLLINTERVAL = 1000;
final public static File LOCKFILE = new File("SINGLE_INSTANCE_LOCKFILE");
final public static File DETECTFILE = new File("EXTRA_INSTANCE_DETECTFILE");
private boolean hasBeenUsedAlready = false;
private WatchService watchService = null;
private RandomAccessFile randomAccessFileForLock = null;
private FileLock fileLock = null;
/**
* CAN ONLY BE CALLED ONCE.
* <p>
* Assumes that the program will close if FALSE is returned: The other-instance-tries-to-launch listener is not
* installed in that case.
* <p>
* Checks if another instance is already running (temp file lock / shutdownhook). Depending on the accessibility of
* the temp file the return value will be true or false. This approach even works even if the virtual machine
* process gets killed. On the next run, the program can even detect if it has shut down irregularly, because then
* the file will still exist. (Thanks to Robert https://.com/a/2002948/3500521 for that solution!)
* <p>
* Additionally, the method checks if another instance tries to start. In a crappy way, because as awesome as Java
* is, it lacks some fundamental features. Don''t worry, it has only been 25 years, it''ll sure come eventually.
*
* @param codeToRunIfOtherInstanceTriesToStart Can be null. If not null and another instance tries to start (which
* changes the detect-file), the code will be executed. Could be used to
* bring the current (=old=only) instance to front. If null, then the
* watcher will not be installed at all, nor will the trigger file be
* created. (Null means that you just don''t want to make use of this
* half of the class'' purpose, but then you would be better advised to
* just use the 24 line method by Robert.)
* <p>
* BE CAREFUL with the code: It will potentially be called until the
* very last moment of the program''s existence, so if you e.g. have a
* shutdown procedure or a window that would be brought to front, check
* if the procedure has not been triggered yet or if the window still
* exists / hasn''t been disposed of yet. Or edit this class to be more
* comfortable. This would e.g. allow you to remove some crappy
* comments. Attribution would be nice, though.
* @param executeOnAWTEventDispatchThread Convenience function. If false, the code will just be executed. If
* true, it will be detected if we''re currently on that thread. If so,
* the code will just be executed. If not so, the code will be run via
* SwingUtilities.invokeLater().
* @return if this is the only instance
*/
public boolean isOnlyInstance(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {
if (hasBeenUsedAlready) {
throw new IllegalStateException("This class/method can only be used once, which kinda makes sense if you think about it.");
}
hasBeenUsedAlready = true;
final boolean ret = canLockFileBeCreatedAndLocked();
if (codeToRunIfOtherInstanceTriesToStart != null) {
if (ret) {
// Only if this is the only instance, it makes sense to install a watcher for additional instances.
installOtherInstanceLaunchAttemptWatcher(codeToRunIfOtherInstanceTriesToStart, executeOnAWTEventDispatchThread);
} else {
// Only if this is NOT the only instance, it makes sense to create&delete the trigger file that will effect notification of the other instance.
//
// Regarding "codeToRunIfOtherInstanceTriesToStart != null":
// While creation/deletion of the file concerns THE OTHER instance of the program,
// making it dependent on the call made in THIS instance makes sense
// because the code executed is probably the same.
createAndDeleteOtherInstanceWatcherTriggerFile();
}
}
optionallyInstallShutdownHookThatCleansEverythingUp();
return ret;
}
private void createAndDeleteOtherInstanceWatcherTriggerFile() {
try {
final RandomAccessFile randomAccessFileForDetection = new RandomAccessFile(DETECTFILE, "rw");
randomAccessFileForDetection.close();
Files.deleteIfExists(DETECTFILE.toPath()); // File is created and then instantly deleted. Not a problem for the WatchService :)
} catch (Exception e) {
e.printStackTrace();
}
}
private boolean canLockFileBeCreatedAndLocked() {
try {
randomAccessFileForLock = new RandomAccessFile(LOCKFILE, "rw");
fileLock = randomAccessFileForLock.getChannel().tryLock();
return fileLock != null;
} catch (Exception e) {
return false;
}
}
private void installOtherInstanceLaunchAttemptWatcher(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {
// PREPARE WATCHSERVICE AND STUFF
try {
watchService = FileSystems.getDefault().newWatchService();
} catch (IOException e) {
e.printStackTrace();
return;
}
final File appFolder = new File("").getAbsoluteFile(); // points to current folder
final Path appFolderWatchable = appFolder.toPath();
// REGISTER CURRENT FOLDER FOR WATCHING FOR FILE DELETIONS
try {
appFolderWatchable.register(watchService, StandardWatchEventKinds.ENTRY_DELETE);
} catch (IOException e) {
e.printStackTrace();
return;
}
// INSTALL WATCHER THAT LOOKS IF OUR detectFile SHOWS UP IN THE DIRECTORY CHANGES. IF THERE''S A CHANGE, ANOTHER INSTANCE TRIED TO START, SO NOTIFY THE CURRENT ONE OF THAT.
final Thread t = new Thread(() -> watchForDirectoryChangesOnExtraThread(codeToRunIfOtherInstanceTriesToStart, executeOnAWTEventDispatchThread));
t.setDaemon(true);
t.setName("directory content change watcher");
t.start();
}
private void optionallyInstallShutdownHookThatCleansEverythingUp() {
if (fileLock == null && randomAccessFileForLock == null && watchService == null) {
return;
}
final Thread shutdownHookThread = new Thread(() -> {
try {
if (fileLock != null) {
fileLock.release();
}
if (randomAccessFileForLock != null) {
randomAccessFileForLock.close();
}
Files.deleteIfExists(LOCKFILE.toPath());
} catch (Exception ignore) {
}
if (watchService != null) {
try {
watchService.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
Runtime.getRuntime().addShutdownHook(shutdownHookThread);
}
private void watchForDirectoryChangesOnExtraThread(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {
while (true) { // To eternity and beyond! Until the universe shuts down. (Should be a volatile boolean, but this class only has absolutely required features.)
try {
Thread.sleep(POLLINTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
final WatchKey wk;
try {
wk = watchService.poll();
} catch (ClosedWatchServiceException e) {
// This situation would be normal if the watcher has been closed, but our application never does that.
e.printStackTrace();
return;
}
if (wk == null || !wk.isValid()) {
continue;
}
for (WatchEvent<?> we : wk.pollEvents()) {
final WatchEvent.Kind<?> kind = we.kind();
if (kind == StandardWatchEventKinds.OVERFLOW) {
System.err.println("OVERFLOW of directory change events!");
continue;
}
final WatchEvent<Path> watchEvent = (WatchEvent<Path>) we;
final File file = watchEvent.context().toFile();
if (file.equals(DETECTFILE)) {
if (!executeOnAWTEventDispatchThread || SwingUtilities.isEventDispatchThread()) {
codeToRunIfOtherInstanceTriesToStart.run();
} else {
SwingUtilities.invokeLater(codeToRunIfOtherInstanceTriesToStart);
}
break;
} else {
System.err.println("THIS IS THE FILE THAT WAS DELETED: " + file);
}
}
wk.reset();
}
}
}
public class SingleInstance { public static final String LOCK = System.getProperty("user.home") + File.separator + "test.lock"; public static final String PIPE = System.getProperty("user.home") + File.separator + "test.pipe"; private static JFrame frame = null; public static void main(String[] args) { try { FileChannel lockChannel = new RandomAccessFile(LOCK, "rw").getChannel(); FileLock flk = null; try { flk = lockChannel.tryLock(); } catch(Throwable t) { t.printStackTrace(); } if (flk == null || !flk.isValid()) { System.out.println("alread running, leaving a message to pipe and quitting..."); FileChannel pipeChannel = null; try { pipeChannel = new RandomAccessFile(PIPE, "rw").getChannel(); MappedByteBuffer bb = pipeChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1); bb.put(0, (byte)1); bb.force(); } catch (Throwable t) { t.printStackTrace(); } finally { if (pipeChannel != null) { try { pipeChannel.close(); } catch (Throwable t) { t.printStackTrace(); } } } System.exit(0); } //We do not release the lock and close the channel here, // which will be done after the application crashes or closes normally. SwingUtilities.invokeLater( new Runnable() { public void run() { createAndShowGUI(); } } ); FileChannel pipeChannel = null; try { pipeChannel = new RandomAccessFile(PIPE, "rw").getChannel(); MappedByteBuffer bb = pipeChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1); while (true) { byte b = bb.get(0); if (b > 0) { bb.put(0, (byte)0); bb.force(); SwingUtilities.invokeLater( new Runnable() { public void run() { frame.setExtendedState(JFrame.NORMAL); frame.setAlwaysOnTop(true); frame.toFront(); frame.setAlwaysOnTop(false); } } ); } Thread.sleep(1000); } } catch (Throwable t) { t.printStackTrace(); } finally { if (pipeChannel != null) { try { pipeChannel.close(); } catch (Throwable t) { t.printStackTrace(); } } } } catch(Throwable t) { t.printStackTrace(); } } public static void createAndShowGUI() { frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(800, 650); frame.getContentPane().add(new JLabel("MAIN WINDOW", SwingConstants.CENTER), BorderLayout.CENTER); frame.setLocationRelativeTo(null); frame.setVisible(true); } }