JavaFXWebEngine:在后台工作程序中到底发生了什么?UI挂起在loadContent(大HTML文档)上

JavaFXWebEngine:在后台工作程序中到底发生了什么?UI挂起在loadContent(大HTML文档)上,java,javafx,javafx-webengine,Java,Javafx,Javafx Webengine,从文档中: 加载总是在后台线程上进行。启动的方法 在安排后台作业后立即加载并返回。追踪 如果要进行和/或取消作业,请使用可从中获得的Worker实例 getLoadWorker()方法 我通过WebEngine.loadContent(String)在WebView上加载了一个。那根绳子大约有五百万个字符长。在Platform.runLater()中运行时(我必须在JavaFX线程中运行它,否则会出错),我的UI会挂起大约一分钟 如果我没有在Platform.runLater()中运行它,我会得

从文档中:

加载总是在后台线程上进行。启动的方法 在安排后台作业后立即加载并返回。追踪 如果要进行和/或取消作业,请使用可从中获得的Worker实例 getLoadWorker()方法

我通过
WebEngine.loadContent(String)
在WebView上加载了一个。那根绳子大约有五百万个字符长。在
Platform.runLater()
中运行时(我必须在JavaFX线程中运行它,否则会出错),我的UI会挂起大约一分钟

如果我没有在
Platform.runLater()中运行它,我会得到:

java.lang.IllegalStateException: Not on FX application thread; currentThread = populator
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:236)
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
    at javafx.scene.web.WebEngine.checkThread(WebEngine.java:1216)
    at javafx.scene.web.WebEngine.loadContent(WebEngine.java:931)
    at javafx.scene.web.WebEngine.loadContent(WebEngine.java:919)
    ...
我不能通过
webEngine.getLoadWorker().getProgress()
查询webEngine,也不能通过
webEngine.getLoadWorker().cancel()
取消webEngine,因为我必须再次在挂起的JavaFX线程上运行它

因此,我必须等待页面加载,然后任何以前提交的
Platform.runLater(()->webEngine.getLoadWorker().getProgress())
都会运行(在网页加载过程中),每次都会给我1.0

我用于查询工作者的代码:

// WebView
wvIn.getEngine().getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {
    class ProgressThread extends Thread {
        private Worker.State loadWorkerState;

        synchronized Worker.State getLoadWorkerState() {
            return loadWorkerState;
        }

        synchronized void setLoadWorkerState(Worker.State loadWorkerState) {
            this.loadWorkerState = loadWorkerState;
        }

        {
            setDaemon(true);
            setName("LoadingWebpageProgressThread");
        }

        public void run() {
            while (true) {
                try {
                    if (getLoadWorkerState() == Worker.State.RUNNING)
                        // piWv ProgressIndicator (WebView loading)
                        Platform.runLater(() -> piWv.setVisible(true));
                    while (getLoadWorkerState() == Worker.State.RUNNING) {
                        Platform.runLater(() -> {
                            piWv.setProgress(wvIn.getEngine().getLoadWorker().getProgress());
                            // TODO delete
                            System.out.println(wvIn.getEngine().getLoadWorker().getProgress());
                        });
                        Thread.sleep(100);
                    }
                    if (getLoadWorkerState() == Worker.State.SUCCEEDED) {
                        Platform.runLater(() -> piWv.setProgress(1d));
                        Thread.sleep(100);
                        Platform.runLater(() -> {
                            piWv.setVisible(false);
                            piWv.setProgress(0d);
                        });
                    }
                    synchronized (this) {
                        wait();
                    }
                } catch (InterruptedException e) {
                }
            }
        }
    };

    final ProgressThread progressThread = new ProgressThread();
    {
        progressThread.start();
    }

    // executed on JavaFX Thread
    @Override
    public void changed(ObservableValue<? extends State> observable, State oldValue, State newValue) {
        if (newValue == State.SUCCEEDED) {
            JSObject window = (JSObject) wvIn.getEngine().executeScript("window");
            window.setMember("controller", mainController);
            progressThread.setLoadWorkerState(newValue);
            progressThread.interrupt();
        } else if (newValue == State.RUNNING) {
            progressThread.setLoadWorkerState(newValue);
            progressThread.interrupt();
        }
        // TODO delete
        System.out.println(oldValue + "->" + newValue);
    }
});
//WebView
wvIn.getEngine().getLoadWorker().stateProperty().addListener(新的ChangeListener()){
类ProgressThread扩展线程{
私人工人。国家装卸工人州;
已同步的工作程序。状态getLoadWorkerState(){
返回装载机状态;
}
同步的void setLoadWorkerState(Worker.State loadWorkerState){
this.loadWorkerState=loadWorkerState;
}
{
setDaemon(true);
setName(“加载网页进度线程”);
}
公开募捐{
while(true){
试一试{
if(getLoadWorkerState()==Worker.State.RUNNING)
//piWv ProgressIndicator(网络视图加载)
Platform.runLater(()->piWv.setVisible(true));
while(getLoadWorkerState()==Worker.State.RUNNING){
Platform.runLater(()->{
piWv.setProgress(wvIn.getEngine().getLoadWorker().getProgress());
//待办事项删除
System.out.println(wvIn.getEngine().getLoadWorker().getProgress());
});
睡眠(100);
}
if(getLoadWorkerState()==Worker.State.successed){
Platform.runLater(()->piWv.setProgress(1d));
睡眠(100);
Platform.runLater(()->{
piWv.setVisible(假);
设定进度(0d);
});
}
已同步(此){
等待();
}
}捕捉(中断异常e){
}
}
}
};
最终ProgressThread ProgressThread=新ProgressThread();
{
progressThread.start();
}
//在JavaFX线程上执行
@凌驾

public void changed(observevalue您在问题中显示的HTML文件几乎立即加载到我的机器上

在我看来,问题在于你的代码。嗯,它相当麻烦。请允许我指出,它看起来不是很好,因为它很容易出错,并且使用异常来实现一种形式的

看起来你还没有完全理解这个概念。 除非为了简洁起见以某种方式切断了问题中的代码段,
ProgressThread
实例是完全无用的。 您已经有了一个根据需要调度事件的方法,在JavaFX应用程序线程上执行回调;只需使用它

indicator.setVisible(false); // Start hidden
engine.getLoadWorker().progressProperty().addListener((observable, oldValue, newValue) -> {
    indicator.setProgress(newValue.doubleValue());
    indicator.setVisible(indicator.getProgress() != 1D);
});
或者,您可以使用以下功能:

请注意,在我的机器上,加载过程中的进度始终为零。我认为问题出在您的HTML代码中:如果您尝试加载其他资源,例如您现在正在阅读的网页,则进度指示器将提供一种更愉快的体验,在页面打开之前被更新数次帐篷已经装好了。

所以

我最终构建了自己的Http服务器来测试
load
,而不是
loadContent
(注意:这是一个坏服务器):

我做了一些基准测试——测量loadWorker从开始运行到成功运行的时间:

via BadHttpServer: javascript and style after body: 13,5 s javascript and style before body: 8,6 s so I continued to test with javascript and style before body. without javascript injection: 8,5 s I continued to test with javascript injection. Next I did with loadContent: 170 s - no UI resposivness during loading Yep - almost 3 minutes... So I returned back with BadHttpServer. Next I tried "throttling the bandwidth" by changing the sleep time. no sleep: 8,2 s 5 ms sleep: 6,8 s 10 ms sleep: 6,7 s 20 ms sleep: 11,6 s In these cases the UI was responsive, although it was lagging a bit. The progressIndicator showed nicely, pausing a bit at 90%. By increasing the sleep time, the UI was more responsive. 通过BadHttpServer: 正文后的javascript和样式:13,5秒 正文前的javascript和样式:8,6 s 所以我继续在body之前使用javascript和样式进行测试。 没有javascript注入:8,5秒 我继续使用javascript注入进行测试。 接下来,我对loadContent进行了测试:170 s-加载期间没有UI响应 是的-差不多3分钟。。。 所以我带着BadHttpServer回来了。 接下来,我尝试通过改变睡眠时间来“调节带宽”。 无睡眠:8,2秒 5毫秒睡眠:6,8秒 10毫秒睡眠:6,7秒 20毫秒睡眠:11,6秒 在这些情况下,UI是响应性的,尽管有点滞后。 progressIndicator表现不错,在90%时稍作停顿。 通过增加睡眠时间,UI的响应性更强。
因此,我想
loadContent
方法可能会更好一些,除非我在实现它时做了一些错误…

loadContent
的文档说“与load(String)一样,这个方法是异步的”,所以我猜你不应该用
平台调用它。runLater
。直接调用它时会出现什么错误?我编辑了这个问题,以包括如果我不从JavaFX线程运行它会发生什么。这可能与此相关--可能只是WebEngine的一个bug/性能问题…@SlumpA关于这一点有一个未解决的问题:.还有一个解释是为什么它必须b
engine.documentProperty().addListener((observable, oldValue, newValue) -> {
    if (newValue == null) {
        return;
    }
    JSObject window = (JSObject)engine.executeScript("window");
    window.setMember("controller", mainController);
});
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.zip.GZIPOutputStream;

public class BadHttpServer {
    private static int request = 0, connection = 0;
    private static final BadHttpServer SINGLETON = new BadHttpServer();
    private ServerSocket serverSocket;
    private String htmlString, formattedDate;
    private byte[] compressedHTML;

    public String getURL() {
        return "http://localhost:60000";
    }

    synchronized byte[] getCompressedHTML() {
        return compressedHTML;
    }

    synchronized void setCompressedHTML(String string) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream(string.length() / 2);
                GZIPOutputStream gos = new GZIPOutputStream(baos);) {
            gos.write(string.getBytes("utf-8"));
            gos.close();
            this.compressedHTML = baos.toByteArray();
            formattedDate = String.format(Locale.US, "%1$ta, %1$td %1$tb %1$tY %1$tH:%1$tM:%1$tS %1$tZ",
                    Calendar.getInstance(TimeZone.getTimeZone("GMT")));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private synchronized String getFormattedDate() {
        return formattedDate;
    }

    private BadHttpServer() {
        try {
            serverSocket = new ServerSocket(60000);
        } catch (IOException e) {
            e.printStackTrace();
        }
        new Thread() {
            {
                setDaemon(true);
                setName("serverSocketThread");
            }

            public void run() {
                while (true) {
                    try {
                        new Thread(acceptAndSendHTML(serverSocket.accept())) {
                            {
                                setDaemon(true);
                                setName("acceptThread" + connection++);
                            }
                        }.start();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            };
        }.start();
    }

    protected Runnable acceptAndSendHTML(final Socket socket) {
        return new Runnable() {
            @Override
            public void run() { 
                try (BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "ASCII"));
                        BufferedWriter asciiWriter = new BufferedWriter(
                                new OutputStreamWriter(socket.getOutputStream(), "ASCII"));
                        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream(), 1000)) {
                    while (!socket.isClosed()) {
                        int request = BadHttpServer.request++;
                        // READING
                        StringBuilder stringBuilder = new StringBuilder();
                        stringBuilder.append(br.readLine() + "\r\n");
                        for (String line = ""; !(line = br.readLine()).isEmpty(); stringBuilder.append(line + "\r\n"))
                            ;
                        // TODO delete
                        System.out.print("Connection " + request + " request: " + stringBuilder);
                        // WRITING
                        byte[] compressedHTML = getCompressedHTML();
                        asciiWriter.write("HTTP/1.1 100 Continue\r\n");
                        asciiWriter.write("\r\n");
                        asciiWriter.write("HTTP/1.1 200 OK\r\n");
                        asciiWriter.write("Date: " + getFormattedDate() + "\r\n");
                        asciiWriter.write("Content-Type: text/html; charset=utf-8\r\n");
                        asciiWriter.write("Content-Encoding: gzip\r\n");
                        asciiWriter.write("Content-Charset: utf-8\r\n");
                        asciiWriter.write("Content-Length: " + compressedHTML.length + "\r\n");
                        // TODO delete
                        System.out.println("Content-Length: " + compressedHTML.length + "\r\n");
                        asciiWriter.write("\r\n");
                        asciiWriter.flush();
                        for (int writtenOverall = 0, writtenThisIteration = 0; writtenOverall < compressedHTML.length; writtenOverall += writtenThisIteration) {
                            bos.write(compressedHTML, writtenOverall,
                                    writtenThisIteration = Math.min(compressedHTML.length - writtenOverall, 1000));
                            Thread.sleep(10); // Bandwidth throttling
                        }
                        bos.flush();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
    }

    public static BadHttpServer getIntance() {
        return SINGLETON;
    }

    public synchronized void setHtmlString(String htmlString) {
        this.htmlString = htmlString;
        setCompressedHTML(htmlString);
    }

    synchronized String getHtmlString() {
        return htmlString;
    }

}
BadHttpServer.getIntance().setHtmlString(htmlString);
Platform.runLater(() -> wvIn.getEngine().load(BadHttpServer.getIntance().getURL()));
via BadHttpServer: javascript and style after body: 13,5 s javascript and style before body: 8,6 s so I continued to test with javascript and style before body. without javascript injection: 8,5 s I continued to test with javascript injection. Next I did with loadContent: 170 s - no UI resposivness during loading Yep - almost 3 minutes... So I returned back with BadHttpServer. Next I tried "throttling the bandwidth" by changing the sleep time. no sleep: 8,2 s 5 ms sleep: 6,8 s 10 ms sleep: 6,7 s 20 ms sleep: 11,6 s In these cases the UI was responsive, although it was lagging a bit. The progressIndicator showed nicely, pausing a bit at 90%. By increasing the sleep time, the UI was more responsive.