EDYCJA : Zamiast korzystać z tego podejścia WatchService, można użyć prostego 1-sekundowego wątku czasowego do sprawdzenia, czy parametr IndicatorFile.exists (). Usuń go, a następnie przenieś aplikację doFront ().
EDYCJA : Chciałbym wiedzieć, dlaczego ten głos został odrzucony. To najlepsze rozwiązanie, jakie do tej pory widziałem. Np. Podejście do gniazda serwera zawodzi, jeśli zdarzy się, że inna aplikacja już nasłuchuje na porcie.
Po prostu pobierz Microsoft Windows Sysinternals TCPView (lub użyj netstat), uruchom go, posortuj według „Stanu”, poszukaj bloku linii z napisem „LISTENING”, wybierz ten, którego zdalny adres zawiera nazwę twojego komputera, umieść ten port w nowym Socket ()-rozwiązanie. Wdrażając to, mogę za każdym razem spowodować porażkę. Jest to logiczne , ponieważ stanowi podstawę tego podejścia. Albo czego nie rozumiem, jeśli chodzi o to, jak to zaimplementować?
Proszę poinformuj mnie, jeśli i jak się mylę!
Mój pogląd - który proszę, abyście obalili, jeśli to możliwe - jest taki, że programiści powinni stosować podejście w kodzie produkcyjnym, które zawodzi w co najmniej 1 z około 60000 przypadków. A jeśli okaże się, że ten pogląd jest słuszny, to absolutnie nie może być tak, że przedstawione rozwiązanie, które nie ma tego problemu, zostało odrzucone i skrytykowane za ilość kodu.
Wady podejścia opartego na gniazdach w porównaniu:
- Nie powiedzie się, jeśli zostanie wybrany zły bilet na loterię (numer portu).
- Niepowodzenie w środowisku wielu użytkowników: tylko jeden użytkownik może uruchomić aplikację w tym samym czasie. (Moje podejście należałoby nieco zmienić, aby utworzyć plik (i) w drzewie użytkowników, ale to trywialne).
- Zawodzi, jeśli reguły zapory są zbyt surowe.
- Sprawia, że podejrzani użytkownicy (których spotkałem na wolności) zastanawiają się, jakie sztuczki robisz, gdy twój edytor tekstu zajmuje gniazdo serwera.
Właśnie przyszedł mi do głowy niezły pomysł, jak rozwiązać problem komunikacji Java od nowej instancji do istniejącej instancji w sposób, który powinien działać na każdym systemie. Więc przygotowałem te zajęcia w około dwie godziny. Działa jak marzenie: D
Opiera się na podejściu Roberta do blokowania plików (również na tej stronie), którego używam od tamtej pory. Aby poinformować już działającą instancję, że inna instancja próbowała się uruchomić (ale tego nie zrobiła) ... plik jest tworzony i natychmiast usuwany, a pierwsza instancja używa WatchService do wykrycia zmiany zawartości tego folderu. Nie mogę uwierzyć, że najwyraźniej jest to nowy pomysł, biorąc pod uwagę, jak fundamentalny jest problem.
Można to łatwo zmienić, aby po prostu utworzyć, a nie usunąć plik, a następnie umieścić w nim informacje, które może ocenić właściwa instancja, np. Argumenty wiersza poleceń - a następnie właściwa instancja może przeprowadzić usunięcie. Osobiście potrzebowałem tylko wiedzieć, kiedy przywrócić okno mojej aplikacji i wysłać je na wierzch.
Przykładowe zastosowanie:
public static void main(final String[] args) {
if (!SingleInstanceChecker.INSTANCE.isOnlyInstance(Main::otherInstanceTriedToLaunch, false)) {
System.exit(0);
}
System.out.println("Application starts properly because it's the only instance.");
}
private static void otherInstanceTriedToLaunch() {
System.err.println("Deiconified because other instance tried to start.");
}
Oto klasa:
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.*;
public enum SingleInstanceChecker {
INSTANCE;
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;
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) {
installOtherInstanceLaunchAttemptWatcher(codeToRunIfOtherInstanceTriesToStart, executeOnAWTEventDispatchThread);
} else {
createAndDeleteOtherInstanceWatcherTriggerFile();
}
}
optionallyInstallShutdownHookThatCleansEverythingUp();
return ret;
}
private void createAndDeleteOtherInstanceWatcherTriggerFile() {
try {
final RandomAccessFile randomAccessFileForDetection = new RandomAccessFile(DETECTFILE, "rw");
randomAccessFileForDetection.close();
Files.deleteIfExists(DETECTFILE.toPath());
} 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) {
try {
watchService = FileSystems.getDefault().newWatchService();
} catch (IOException e) {
e.printStackTrace();
return;
}
final File appFolder = new File("").getAbsoluteFile();
final Path appFolderWatchable = appFolder.toPath();
try {
appFolderWatchable.register(watchService, StandardWatchEventKinds.ENTRY_DELETE);
} catch (IOException e) {
e.printStackTrace();
return;
}
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) {
try {
Thread.sleep(POLLINTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
final WatchKey wk;
try {
wk = watchService.poll();
} catch (ClosedWatchServiceException e) {
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();
}
}
}