РЕДАКТИРОВАТЬ : вместо использования этого подхода WatchService можно использовать простой поток 1-секундного таймера для проверки наличия indicatorFile.exists (). Удалите его, затем перенесите приложение в Front ().
РЕДАКТИРОВАТЬ : Я хотел бы знать, почему это было отклонено. Это лучшее решение, которое я когда-либо видел. Например, подход серверного сокета не работает, если другое приложение уже прослушивает порт.
Просто загрузите Microsoft Windows Sysinternals TCPView (или используйте netstat), запустите его, отсортируйте по «State», найдите строковый блок с надписью «LISTENING», выберите тот, чей удаленный адрес соответствует имени вашего компьютера, поместите этот порт в новый Socket ()-решение. В своей реализации я могу каждый раз давать сбой. И это логично , потому что это сама основа подхода. Или что я не понимаю, как это реализовать?
Пожалуйста, сообщите мне, если и чем я ошибаюсь по этому поводу!
Мое мнение - которое я прошу вас опровергнуть, если возможно, - заключается в том, что разработчикам рекомендуется использовать в производственном коде подход, который даст сбой по крайней мере в 1 из примерно 60000 случаев. И если эта точка зрения окажется правильной, то совершенно не может быть, что представленное решение, не имеющее этой проблемы, не получило голосов и критиковалось за объем кода.
Недостатки сокетного подхода в сравнении:
- Не работает, если выбран неправильный лотерейный билет (номер порта).
- Сбои в многопользовательской среде: только один пользователь может запускать приложение одновременно. (Мой подход нужно было бы немного изменить, чтобы создать файл (ы) в дереве пользователя, но это тривиально.)
- Не работает, если правила брандмауэра слишком строгие.
- Заставляет подозрительных пользователей (которых я встречал в дикой природе) задуматься, какие махинации вы затеваете, когда ваш текстовый редактор требует серверного сокета.
У меня просто возникла хорошая идея, как решить проблему связи Java между новым экземпляром и существующим таким образом, чтобы это работало в любой системе. Итак, я начал этот урок примерно за два часа. Работает как шарм: D
Он основан на подходе Роберта к блокировке файлов (также на этой странице), который я использовал с тех пор. Чтобы сообщить уже запущенному экземпляру, что другой экземпляр пытался запустить (но не сделал этого) ... файл создается и немедленно удаляется, и первый экземпляр использует WatchService для обнаружения изменения содержимого этой папки. Я не могу поверить, что это новая идея, учитывая, насколько фундаментальна проблема.
Это можно легко изменить, чтобы просто создать, а не удалить файл, а затем в него можно поместить информацию, которую может оценить соответствующий экземпляр, например, аргументы командной строки - и соответствующий экземпляр может затем выполнить удаление. Лично мне нужно было только знать, когда восстановить окно моего приложения и отправить его на передний план.
Пример использования:
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.");
}
Вот класс:
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();
}
}
}