CHỈNH SỬA : Thay vì sử dụng phương pháp WatchService này, một chuỗi bộ đếm thời gian 1 giây đơn giản có thể được sử dụng để kiểm tra xem indicatorFile.exists (). Xóa nó, sau đó đưa ứng dụng vềFront ().
CHỈNH SỬA : Tôi muốn biết lý do tại sao điều này bị từ chối. Đó là giải pháp tốt nhất mà tôi đã thấy cho đến nay. Ví dụ: cách tiếp cận ổ cắm máy chủ không thành công nếu một ứng dụng khác tình cờ đang lắng nghe cổng.
Chỉ cần tải xuống Microsoft Windows Sysinternals TCPView (hoặc sử dụng netstat), khởi động nó, sắp xếp theo "Trạng thái", tìm khối dòng có nội dung "LISTENING", chọn một khối có địa chỉ từ xa cho biết tên máy tính của bạn, đặt cổng đó vào Ổ cắm mới của bạn ()-giải pháp. Khi thực hiện nó, tôi có thể gặp thất bại bất cứ lúc nào. Và nó hợp lý , bởi vì nó là nền tảng của cách tiếp cận. Hoặc những gì tôi không nhận được về cách thực hiện điều này?
Xin vui lòng thông báo cho tôi nếu và làm thế nào tôi sai về điều này!
Quan điểm của tôi - mà tôi đang yêu cầu bạn bác bỏ nếu có thể - là các nhà phát triển đang được khuyên sử dụng một cách tiếp cận trong mã sản xuất sẽ không thành công trong ít nhất 1 trong số khoảng 60000 trường hợp. Và nếu quan điểm này là đúng, thì hoàn toàn có thể không có giải pháp được trình bày không có vấn đề này bị phản đối và bị chỉ trích vì số lượng mã của nó.
Nhược điểm của phương pháp tiếp cận socket so với:
- Không thành công nếu chọn sai vé số (số cổng).
- Không thành công trong môi trường nhiều người dùng: Chỉ một người dùng có thể chạy ứng dụng cùng một lúc. (Cách tiếp cận của tôi sẽ phải thay đổi một chút để tạo (các) tệp trong cây người dùng, nhưng điều đó không đáng kể.)
- Không thành công nếu các quy tắc tường lửa quá nghiêm ngặt.
- Khiến những người dùng đáng ngờ (mà tôi đã gặp trong tự nhiên) tự hỏi bạn đang nghĩ ra trò tai quái gì khi trình soạn thảo văn bản của bạn yêu cầu một ổ cắm máy chủ.
Tôi vừa có một ý tưởng hay về cách giải quyết vấn đề giao tiếp Java phiên bản mới đến phiên bản hiện tại theo cách có thể hoạt động trên mọi hệ thống. Vì vậy, tôi đã hoàn thành lớp học này trong khoảng hai giờ. Hoạt động như một cái duyên: D
Nó dựa trên phương pháp khóa tệp của Robert (cũng trên trang này), mà tôi đã sử dụng kể từ đó. Để thông báo cho phiên bản đang chạy rằng một phiên bản khác đã cố gắng khởi động (nhưng không) ... một tệp được tạo và xóa ngay lập tức, và phiên bản đầu tiên sử dụng WatchService để phát hiện sự thay đổi nội dung thư mục này. Tôi không thể tin rằng rõ ràng đây là một ý tưởng mới, với căn bản của vấn đề.
Điều này có thể dễ dàng được thay đổi để chỉ tạo và không xóa tệp, và sau đó thông tin có thể được đưa vào đó để cá thể thích hợp có thể đánh giá, ví dụ: đối số dòng lệnh - và cá thể thích hợp sau đó có thể thực hiện xóa. Cá nhân tôi chỉ cần biết khi nào cần khôi phục cửa sổ ứng dụng của mình và gửi nó về phía trước.
Ví dụ sử dụng:
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.");
}
Đây là lớp học:
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://stackoverflow.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://stackoverflow.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();
}
}
}