JavaFX 单实例应用程序

Posted

技术标签:

【中文标题】JavaFX 单实例应用程序【英文标题】:JavaFX Single Instance Application 【发布时间】:2017-04-24 09:12:36 【问题描述】:

试图做到这一点,当用户“关闭”程序时单击所有退出按钮,这样就没有更多的托盘图标了。

我调用了 Platform.setImplicitExit(false);所以程序仍然在后台运行。

我正在尝试学习如何制作它,因此当用户重新单击运行 jar 的 .exe 文件时,它不会运行新程序,而是重新显示正在后台运行的程序。

 Platform.setImplicitExit(false);

【问题讨论】:

我用jsmooth制作.exe 【参考方案1】:

这是基于博文中的解决方案:Java Single Instance Application。

解决方案使用“套接字技术”:

通过这种技术,我们开始侦听端口,只有一个进程可以侦听套接字,因此在我们的应用程序的第一个实例将自身绑定到套接字后,其他实例将收到 BindException,这意味着我们已经在运行。

这种方法的缺点是某些病毒扫描程序会在应用程序开始侦听套接字时发出警告,这取决于您的用户群,这可能会被错误地解释。您应该选择一个不常用且较高的端口号,否则您甚至不会运行应用程序的单个实例。

在示例中,我们为应用程序实例创建了一个唯一的实例 ID,并记录了一些选项。

Minimize 将最小化窗口。 Hide 将隐藏它(因此它不会显示为最小化,但应用程序仍在运行)。 退出 将结束申请过程。

窗口上的操作系统关闭按钮将关闭应用程序窗口,但应用程序进程将继续在后台运行(因此它的作用与“隐藏”按钮相同)。

当你启动一个应用实例时,它会打开一个套接字并监听它。

当您尝试启动另一个应用程序实例时,它将尝试绑定到侦听套接字。如果它不能绑定,那么它知道已经有一个应用程序实例在该套接字上运行。如果检测到另一个实例,则会通过套接字向现有实例发送一条消息,导致现有实例取消隐藏或取消最小化自身并尝试将其舞台置于前面。

请不要滥用这个,有很多我不喜欢的程序隐藏在后台。

import javafx.application.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;

public class SingleInstanceApp extends Application 

    private static final int SINGLE_INSTANCE_LISTENER_PORT = 9999;
    private static final String SINGLE_INSTANCE_FOCUS_MESSAGE = "focus";

    private static final String instanceId = UUID.randomUUID().toString();

    // We define a pause before focusing on an existing instance
    // because sometimes the command line or window launching the instance
    // might take focus back after the second instance execution complete
    // so we introduce a slight delay before focusing on the original window
    // so that the original window can retain focus.
    private static final int FOCUS_REQUEST_PAUSE_MILLIS = 500;

    private Stage stage;

    public void init() 
        CountDownLatch instanceCheckLatch = new CountDownLatch(1);

        Thread instanceListener = new Thread(() -> 
            try (ServerSocket serverSocket = new ServerSocket(SINGLE_INSTANCE_LISTENER_PORT, 10)) 
                instanceCheckLatch.countDown();

                while (true) 
                    try (
                            Socket clientSocket = serverSocket.accept();
                            BufferedReader in = new BufferedReader(
                                    new InputStreamReader(clientSocket.getInputStream()))
                    ) 
                        String input = in.readLine();
                        System.out.println("Received single instance listener message: " + input);
                        if (input.startsWith(SINGLE_INSTANCE_FOCUS_MESSAGE) && stage != null) 
                            Thread.sleep(FOCUS_REQUEST_PAUSE_MILLIS);
                            Platform.runLater(() -> 
                                System.out.println("To front " + instanceId);
                                stage.setIconified(false);
                                stage.show();
                                stage.toFront();
                            );
                        
                     catch (IOException e) 
                        System.out.println("Single instance listener unable to process focus message from client");
                        e.printStackTrace();
                    
                
             catch(java.net.BindException b) 
                System.out.println("SingleInstanceApp already running");

                try (
                        Socket clientSocket = new Socket(InetAddress.getLocalHost(), SINGLE_INSTANCE_LISTENER_PORT);
                        PrintWriter out = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream()))
                ) 
                    System.out.println("Requesting existing app to focus");
                    out.println(SINGLE_INSTANCE_FOCUS_MESSAGE + " requested by " + instanceId);
                 catch (IOException e) 
                    e.printStackTrace();
                

                System.out.println("Aborting execution for instance " + instanceId);
                Platform.exit();
             catch(Exception e) 
                System.out.println(e.toString());
             finally 
                instanceCheckLatch.countDown();
            
        , "instance-listener");
        instanceListener.setDaemon(true);
        instanceListener.start();

        try 
            instanceCheckLatch.await();
         catch (InterruptedException e) 
            Thread.interrupted();
        
    

    public void stop() 
        System.out.println("Exiting instance " + instanceId);
    

    @Override
    public void start(Stage stage) throws Exception
        this.stage = stage;

        System.out.println("Starting instance " + instanceId);

        Platform.setImplicitExit(false);

        Button minimize = new Button("Minimize");
        minimize.setOnAction(event -> stage.setIconified(true));

        Button hide = new Button("Hide");
        hide.setOnAction(event -> stage.hide());

        Button exit = new Button("Exit");
        exit.setOnAction(event -> Platform.exit());

        Label instance = new Label(instanceId);

        Pane layout = new VBox(10, instance, new HBox(10, minimize, hide, exit));
        layout.setPadding(new Insets(10));

        Scene scene = new Scene(layout);
        stage.setScene(scene);
        stage.show();
    

    public static void main(String[] args) 
        launch(args);
    

【讨论】:

以上是关于JavaFX 单实例应用程序的主要内容,如果未能解决你的问题,请参考以下文章

如何将JavaFx与Jade框架链接到代理

JavaFX 启动另一个应用程序

无法使用 Gradle 实例化 FXML 加载器

JavaFX之PathTransition类的使用-简单实例PathTransitionDemo.java

tomcat单实例多应用多实例多应用部署

单实例应用程序