【发布时间】:2020-05-05 06:56:44
【问题描述】:
JavaFX 文档声明 WebView is ready when Worker.State.SUCCEEDED is reached 但是,除非您等待一段时间(即 Animation、Transition、PauseTransition 等),否则会呈现一个空白页面。
这表明 WebView 内部发生了一个事件,准备捕获它,但它是什么?
有 over 7,000 code snippets on GitHub which use SwingFXUtils.fromFXImage,但它们中的大多数似乎与 WebView 无关,是交互式的(人类掩盖了竞争条件)或使用任意转换(从 100 毫秒到 2000 毫秒)。
我试过了:
-
在
WebView的维度内监听changed(...)(高度和宽度属性DoubleProperty实现ObservableValue,可以监控这些东西)- ????不可行。有时,值似乎会独立于绘制例程而发生变化,从而导致部分内容。
-
在 FX 应用程序线程上盲目地将任何事情告诉
runLater(...)。- ????许多技术都使用它,但我自己的单元测试(以及其他开发人员的一些很好的反馈)解释说事件通常已经在正确的线程上,这个调用是多余的。我能想到的最好的办法是通过排队增加足够的延迟,这对某些人有用。
-
将 DOM 侦听器/触发器或 JavaScript 侦听器/触发器添加到
WebView- ????尽管有空白捕获,但在调用
SUCCEEDED时,JavaScript 和 DOM 似乎都已正确加载。 DOM/JavaScript 监听器似乎没有帮助。
- ????尽管有空白捕获,但在调用
-
使用
Animation或Transition有效地“休眠”而不阻塞主FX 线程。- ⚠️ 这种方法有效,如果延迟足够长,可以产生高达 100% 的单元测试,但转换时间似乎是 some future moment that we're just guessing 和糟糕的设计。对于高性能或任务关键型应用程序,这迫使程序员在速度或可靠性之间做出权衡,这对用户来说都是一种潜在的糟糕体验。
什么时候可以拨打WebView.snapshot(...)?
用法:
SnapshotRaceCondition.initialize();
BufferedImage bufferedImage = SnapshotRaceCondition.capture("<html style='background-color: red;'><h1>TEST</h1></html>");
/**
* Notes:
* - The color is to observe the otherwise non-obvious cropping that occurs
* with some techniques, such as `setPrefWidth`, `autosize`, etc.
* - Call this function in a loop and then display/write `BufferedImage` to
* to see strange behavior on subsequent calls.
* - Recommended, modify `<h1>TEST</h1` with a counter to see content from
* previous captures render much later.
*/
代码片段:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.WritableImage;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
public class SnapshotRaceCondition extends Application {
private static final Logger log = Logger.getLogger(SnapshotRaceCondition.class.getName());
// self reference
private static SnapshotRaceCondition instance = null;
// concurrent-safe containers for flags/exceptions/image data
private static AtomicBoolean started = new AtomicBoolean(false);
private static AtomicBoolean finished = new AtomicBoolean(true);
private static AtomicReference<Throwable> thrown = new AtomicReference<>(null);
private static AtomicReference<BufferedImage> capture = new AtomicReference<>(null);
// main javafx objects
private static WebView webView = null;
private static Stage stage = null;
// frequency for checking fx is started
private static final int STARTUP_TIMEOUT= 10; // seconds
private static final int STARTUP_SLEEP_INTERVAL = 250; // millis
// frequency for checking capture has occured
private static final int CAPTURE_SLEEP_INTERVAL = 10; // millis
/** Called by JavaFX thread */
public SnapshotRaceCondition() {
instance = this;
}
/** Starts JavaFX thread if not already running */
public static synchronized void initialize() throws IOException {
if (instance == null) {
new Thread(() -> Application.launch(SnapshotRaceCondition.class)).start();
}
for(int i = 0; i < (STARTUP_TIMEOUT * 1000); i += STARTUP_SLEEP_INTERVAL) {
if (started.get()) { break; }
log.fine("Waiting for JavaFX...");
try { Thread.sleep(STARTUP_SLEEP_INTERVAL); } catch(Exception ignore) {}
}
if (!started.get()) {
throw new IOException("JavaFX did not start");
}
}
@Override
public void start(Stage primaryStage) {
started.set(true);
log.fine("Started JavaFX, creating WebView...");
stage = primaryStage;
primaryStage.setScene(new Scene(webView = new WebView()));
// Add listener for SUCCEEDED
Worker<Void> worker = webView.getEngine().getLoadWorker();
worker.stateProperty().addListener(stateListener);
// Prevents JavaFX from shutting down when hiding window, useful for calling capture(...) in succession
Platform.setImplicitExit(false);
}
/** Listens for a SUCCEEDED state to activate image capture **/
private static ChangeListener<Worker.State> stateListener = (ov, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
WritableImage snapshot = webView.snapshot(new SnapshotParameters(), null);
capture.set(SwingFXUtils.fromFXImage(snapshot, null));
finished.set(true);
stage.hide();
}
};
/** Listen for failures **/
private static ChangeListener<Throwable> exceptListener = new ChangeListener<Throwable>() {
@Override
public void changed(ObservableValue<? extends Throwable> obs, Throwable oldExc, Throwable newExc) {
if (newExc != null) { thrown.set(newExc); }
}
};
/** Loads the specified HTML, triggering stateListener above **/
public static synchronized BufferedImage capture(final String html) throws Throwable {
capture.set(null);
thrown.set(null);
finished.set(false);
// run these actions on the JavaFX thread
Platform.runLater(new Thread(() -> {
try {
webView.getEngine().loadContent(html, "text/html");
stage.show(); // JDK-8087569: will not capture without showing stage
stage.toBack();
}
catch(Throwable t) {
thrown.set(t);
}
}));
// wait for capture to complete by monitoring our own finished flag
while(!finished.get() && thrown.get() == null) {
log.fine("Waiting on capture...");
try {
Thread.sleep(CAPTURE_SLEEP_INTERVAL);
}
catch(InterruptedException e) {
log.warning(e.getLocalizedMessage());
}
}
if (thrown.get() != null) {
throw thrown.get();
}
return capture.get();
}
}
相关:
- Screenshot of the full web page loaded into JavaFX WebView component, not only visible part
- Can I capture snapshot of scene programmatically?
- Whole page screenshot, Java
- JavaFX 2.0+ WebView /WebEngine render web page to an image
- Set Height and Width of Stage and Scene in javafx
- JavaFX:how to resize the stage when using webview
- Correct sizing of Webview embedded in Tabelcell
- https://docs.oracle.com/javase/8/javafx/embedded-browser-tutorial/add-browser.htm#CEGDIBBI
- http://docs.oracle.com/javafx/2/swing/swing-fx-interoperability.htm#CHDIEEJE
- https://bugs.openjdk.java.net/browse/JDK-8126854
- https://bugs.openjdk.java.net/browse/JDK-8087569
【问题讨论】:
-
Platform.runLater 不是多余的。可能存在 WebView 完成其呈现所必需的待处理事件。 Platform.runLater 是我会尝试的第一件事。
-
比赛和单元测试表明事件不是未决的,而是发生在一个单独的线程中。
Platform.runLater已经过测试并且没有修复它。如果您不同意,请自行尝试。我很高兴错了,它会关闭问题。 -
此外,官方文档将
SUCCEEDED状态(侦听器在 FX 线程上触发)是正确的技术。如果有办法显示排队事件,我会很高兴尝试。我在 Oracle 论坛上通过 cmets 找到了一些建议,一些 SO 问题WebView必须按照设计在其自己的线程中运行,因此经过几天的测试,我将精力集中在那里。如果这个假设是错误的,那就太好了。我愿意接受任何可以解决问题而无需任意等待时间的合理建议。 -
我自己写了一个很短的测试,并且能够成功地在负载工作者的状态监听器中获取一个 WebView 的快照。但是你的程序确实给了我一个空白页。我仍在尝试了解其中的区别。
-
这似乎只在使用
loadContent方法或加载文件 URL 时发生。
标签: java multithreading javafx race-condition