簡體   English   中英

使用多個 UI 線程運行 java 應用程序

[英]Running java Application with Multiple UI Threads

我正在嘗試用 java 編寫一個非常簡單的客戶端-服務器電子郵件項目。 我已經使用套接字對客戶端和服務器之間的通信進行了編碼,現在我正在嘗試編寫一些測試,其中還包括一個非常簡單的 UI。 我的想法是創建與我擁有的客戶端一樣多的線程,我希望每個線程都開始打開一個用 Java FX 創建的簡單 UI 窗口,但我遇到了一些問題。

這是主類:

import java.io.*;

public class ClientController{
    public static void main(String args[]) throws IOException {
        ParallelClient c1=new ParallelClient("aaaa@gmail.com");
        ParallelClient c2=new ParallelClient("bbbb@gmail.com");
        c1.start();
        c2.start();
    }
}

這是 ParallelClient 類:

import ...

public class ParallelClient extends Thread{
    private String user;

    public ParallelClient(String user){
        this.user=user;
    }

    public void run(){
        ClientApp app=new ClientApp();
        try {
            app.start(new Stage());
        } catch (Exception e) {
            e.printStackTrace();
        }
        ...
    }
    ...
}

這是設置新窗口的 ClientApp 類:

import ...

public class ClientApp extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        try {
            Parent root = FXMLLoader.load(getClass().getResource("ui/client-management.fxml"));
            stage.setTitle("ClientMail");
            stage.setScene(new Scene(root, 1080, 720));
            stage.show();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

當我嘗試運行代碼時,我遇到了以下問題,我無法理解如何修復它:

Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.NoClassDefFoundError: Could not initialize class javafx.stage.Screen
    at javafx.stage.Window.<init>(Window.java:1439)
    at javafx.stage.Stage.<init>(Stage.java:252)
    at javafx.stage.Stage.<init>(Stage.java:240)
    at model.ParallelClient.run(ParallelClient.java:25)
java.lang.ExceptionInInitializerError
    at javafx.stage.Window.<init>(Window.java:1439)
    at javafx.stage.Stage.<init>(Stage.java:252)
    at javafx.stage.Stage.<init>(Stage.java:240)
    at model.ParallelClient.run(ParallelClient.java:25)
Caused by: java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = Thread-1
    at com.sun.glass.ui.Application.checkEventThread(Application.java:441)
    at com.sun.glass.ui.Screen.setEventHandler(Screen.java:369)
    at com.sun.javafx.tk.quantum.QuantumToolkit.setScreenConfigurationListener(QuantumToolkit.java:728)
    at javafx.stage.Screen.<clinit>(Screen.java:74)
    ... 4 more

正如您在問題中發布的那樣,應用程序的結構存在幾個問題。

Application類代表整個應用程序的生命周期,通過調用其init()start()stop()方法進行管理。 整個應用程序中應該只有一個Application實例,通常這個實例是由 JavaFX 啟動機制創建的,因此您不應自己實例化Application子類。

JavaFX 應用程序需要啟動 JavaFX 運行時,其中包括啟動 JavaFX 應用程序線程。 這是通過調用靜態Application.launch()方法完成的,該方法只能調用一次。 launch()方法啟動 JavaFX 運行時,創建Application類的實例,調用init() ,然后在 FX 應用程序線程上調用start() (在 JavaFX 9 及更高版本中,您還可以通過調用Platform.startup()來啟動運行時,但這種情況很少見)。

請注意,在您的應用程序中,沒有調用Application.launch() (或Platform.startup() ),因此 JavaFX 運行時永遠不會啟動。

某些操作只能在 FX 應用線程上執行。 這些包括創建StageScene ,以及對已顯示的 UI 元素的屬性進行任何修改。 因此,您不能在單獨的線程中“運行每個客戶端”。 這是您的異常的原因:您試圖在不是 FX 應用程序線程的線程上創建一個新Stage

每個客戶端不需要一個新線程來顯示 UI(並且,如上所述,不能這樣做)。 您可能需要在單獨的線程上執行每個客戶端與服務器的通信(因為這些操作需要很長時間,您不應該阻塞 FX 應用程序線程)。 您可以通過為每個客戶端的服務器通信創建一個新線程來實現,或者使用共享執行程序服務以便每個客戶端可以從池中獲取一個線程(這可能是首選方法)。

所以你的結構應該是這樣的:

public class Client {

    private Parent ui ;
    private ExecutorService exec ; // for handling server communication

    private final String user ;

    public Client(String user, ExecutorService exec) {
        this.user = user ;
        this.exec = exec ;
        try {
            ui = FXMLLoader.load(getClass().getResource("ui/client-management.fxml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public Client(String user) {
        this(user, Executors.newSingleThreadExecutor());
    }

    public Parent getUI() {
        return ui ;
    }

    public void showInNewWindow() {
        Scene scene = new Scene(ui);
        Stage stage = new Stage();
        stage.setScene(scene);
        stage.show();
    }

    public void checkForNewEmail() {
        Task<List<Email>> newEmailTask = new Task<>() {
            @Override
            protected List<Email> call() throws Exception {
                List<Email> newEmails = new ArrayList<>();
                // contact server and retrieve any new emails
                return newEmails ;
            }
        };
        newEmailTask.setOnSucceeded(e -> {
            List<Email> newEmails = newEmailTask.getValue();
            // update UI with new emails...
        });
        exec.submit(newEmailTask);
    }

    // etc ...
}

然后你的ClientController類可以做這樣的事情:

public class ClientController {

    private ExecutorService exec = Executors.newCachedThreadPool();

    public void startClients() {
        Client clientA = new Client("aaaa@gmail.com", exec);
        Client clientB = new Client("bbbb@gmail.com", exec);
        clientA.showInNewWindow();
        clientB.showInNewWindow();
    }
}

你的應用程序類可以做

public class ClientApp extends Application {

    @Override
    public void start(Stage primaryStage) {
        ClientController controller = new ClientController();
        controller.startClients();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM