EDIT : Alih-alih menggunakan pendekatan WatchService ini, utas pengatur waktu 1 detik sederhana dapat digunakan untuk memeriksa apakah indicatorFile.exists (). Hapus, lalu bawa aplikasi keFront ().
EDIT : Saya ingin tahu mengapa ini tidak disukai. Itu solusi terbaik yang pernah saya lihat sejauh ini. Misalnya pendekatan soket server gagal jika aplikasi lain kebetulan sudah mendengarkan port.
Cukup unduh Microsoft Windows Sysinternals TCPView (atau gunakan netstat), mulai, urutkan berdasarkan "Status", cari blok baris yang bertuliskan "MENDENGARKAN", pilih salah satu yang alamat jarak jauhnya menyebutkan nama komputer Anda, masukkan port tersebut ke Socket baru Anda ()-larutan. Dalam implementasinya, saya bisa menghasilkan kegagalan setiap saat. Dan itu logis , karena itu adalah dasar dari pendekatan tersebut. Atau apa yang tidak saya dapatkan tentang cara menerapkan ini?
Tolong beri tahu saya jika dan bagaimana saya salah tentang ini!
Pandangan saya - yang saya minta untuk Anda sangkal jika memungkinkan - adalah bahwa pengembang disarankan untuk menggunakan pendekatan dalam kode produksi yang akan gagal setidaknya dalam 1 dari sekitar 60.000 kasus. Dan jika pandangan ini kebetulan benar, maka sama sekali tidak mungkin solusi yang disajikan yang tidak memiliki masalah ini tidak disukai dan dikritik karena jumlah kodenya.
Kerugian dari pendekatan soket dibandingkan:
- Gagal jika tiket lotere yang salah (nomor port) dipilih.
- Gagal di lingkungan multi-pengguna: Hanya satu pengguna yang dapat menjalankan aplikasi pada waktu yang sama. (Pendekatan saya harus sedikit diubah untuk membuat file di pohon pengguna, tapi itu sepele.)
- Gagal jika aturan firewall terlalu ketat.
- Membuat pengguna yang mencurigakan (yang saya temui di alam liar) bertanya-tanya apa yang Anda lakukan ketika editor teks Anda mengklaim soket server.
Saya baru saja mendapat ide bagus tentang cara menyelesaikan masalah komunikasi Java instance-to-existing-instance baru dengan cara yang seharusnya berfungsi pada setiap sistem. Jadi, saya memulai kelas ini dalam waktu sekitar dua jam. Bekerja seperti pesona: D
Ini didasarkan pada pendekatan kunci file Robert (juga di halaman ini), yang telah saya gunakan sejak saat itu. Untuk memberi tahu instance yang sudah berjalan bahwa instance lain mencoba untuk memulai (tetapi tidak) ... file dibuat dan segera dihapus, dan instance pertama menggunakan WatchService untuk mendeteksi perubahan konten folder ini. Saya tidak percaya ternyata ini adalah ide baru, mengingat betapa fundamental masalahnya.
Ini dapat dengan mudah diubah menjadi hanya membuat dan tidak menghapus file, dan kemudian informasi dapat dimasukkan ke dalamnya yang dapat dievaluasi oleh instance yang tepat, misalnya argumen baris perintah - dan instance yang sesuai kemudian dapat melakukan penghapusan. Secara pribadi, saya hanya perlu tahu kapan harus memulihkan jendela aplikasi saya dan mengirimkannya ke depan.
Contoh penggunaan:
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.");
}
Inilah kelasnya:
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();
}
}
}