Кажется, это ошибка, возникающая при использовании методов loadContent
WebEngine. Это также происходит при использовании load
для загрузки локального файла, но в этом случае вызов reload () компенсирует это.
Кроме того, поскольку сцена должна отображаться, когда Вы делаете снимок, вам нужно позвонить show()
перед загрузкой содержимого. Поскольку контент загружается асинхронно, вполне возможно, что он будет загружен до завершения оператора, следующего за вызовом load
или loadContent
.
Временное решение заключается в размещении контента в файле. и вызовите метод reload()
WebEngine ровно один раз. Во второй раз, когда содержимое загружается, снимок может быть успешно взят из прослушивателя свойства состояния загрузчика.
Обычно это будет легко:
Path htmlFile = Files.createTempFile("snapshot-", ".html");
Files.writeString(htmlFile, html);
WebEngine engine = myWebView.getEngine();
engine.getLoadWorker().stateProperty().addListener(
new ChangeListener<Worker.State>() {
private boolean reloaded;
@Override
public void changed(ObservableValue<? extends Worker.State> obs,
Worker.State oldState,
Worker.State newState) {
if (reloaded) {
Image image = myWebView.snapshot(null, null);
doStuffWithImage(image);
try {
Files.delete(htmlFile);
} catch (IOException e) {
log.log(Level.WARN, "Couldn't delete " + htmlFile, e);
}
} else {
reloaded = true;
engine.reload();
}
}
});
engine.load(htmlFile.toUri().toString());
Но поскольку вы используя static
для всего, вам нужно будет добавить несколько полей:
private static boolean reloaded;
private static volatile Path htmlFile;
И вы можете использовать их здесь:
/** Listens for a SUCCEEDED state to activate image capture **/
private static ChangeListener<Worker.State> stateListener = (ov, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
if (reloaded) {
WritableImage snapshot = webView.snapshot(new SnapshotParameters(), null);
capture.set(SwingFXUtils.fromFXImage(snapshot, null));
finished.set(true);
stage.hide();
try {
Files.delete(htmlFile);
} catch (IOException e) {
log.log(Level.WARN, "Couldn't delete " + htmlFile, e);
}
} else {
reloaded = true;
webView.getEngine().reload();
}
}
};
И тогда вам придется сбросить его каждый раз при загрузке контента:
Path htmlFile = Files.createTempFile("snapshot-", ".html");
Files.writeString(htmlFile, html);
Platform.runLater(new Thread(() -> {
try {
reloaded = false;
stage.show(); // JDK-8087569: will not capture without showing stage
stage.toBack();
webView.getEngine().load(htmlFile);
}
catch(Throwable t) {
thrown.set(t);
}
}));
Обратите внимание, что существуют более эффективные способы выполнения многопоточной обработки. Вместо использования классов Atomi c вы можете просто использовать volatile
поля:
private static volatile boolean started;
private static volatile boolean finished = true;
private static volatile Throwable thrown;
private static volatile BufferedImage capture;
(логические поля по умолчанию имеют значение false, а объектные поля по умолчанию равны нулю. В отличие от C программ, Это жесткая гарантия, предоставляемая Java, не существует такой вещи, как неинициализированная память.)
Вместо опроса в al oop изменений, внесенных в другом потоке, лучше использовать синхронизацию, блокировку или класс более высокого уровня, такой как CountDownLatch , который использует эти вещи внутренне:
private static final CountDownLatch initialized = new CountDownLatch(1);
private static volatile CountDownLatch finished;
private static volatile BufferedImage capture;
private static volatile Throwable thrown;
private static boolean reloaded;
private static volatile Path htmlFile;
// main javafx objects
private static WebView webView = null;
private static Stage stage = null;
private static ChangeListener<Worker.State> stateListener = (ov, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
if (reloaded) {
WritableImage snapshot = webView.snapshot(null, null);
capture = SwingFXUtils.fromFXImage(snapshot, null);
finished.countDown();
stage.hide();
try {
Files.delete(htmlFile);
} catch (IOException e) {
log.log(Level.WARNING, "Could not delete " + htmlFile, e);
}
} else {
reloaded = true;
webView.getEngine().reload();
}
}
};
@Override
public void start(Stage primaryStage) {
log.fine("Started JavaFX, creating WebView...");
stage = primaryStage;
primaryStage.setScene(new Scene(webView = new WebView()));
Worker<Void> worker = webView.getEngine().getLoadWorker();
worker.stateProperty().addListener(stateListener);
webView.getEngine().setOnError(e -> {
thrown = e.getException();
});
// Prevents JavaFX from shutting down when hiding window, useful for calling capture(...) in succession
Platform.setImplicitExit(false);
initialized.countDown();
}
public static BufferedImage capture(String html)
throws InterruptedException,
IOException {
htmlFile = Files.createTempFile("snapshot-", ".html");
Files.writeString(htmlFile, html);
if (initialized.getCount() > 0) {
new Thread(() -> Application.launch(SnapshotRaceCondition2.class)).start();
initialized.await();
}
finished = new CountDownLatch(1);
thrown = null;
Platform.runLater(() -> {
reloaded = false;
stage.show(); // JDK-8087569: will not capture without showing stage
stage.toBack();
webView.getEngine().load(htmlFile.toUri().toString());
});
finished.await();
if (thrown != null) {
throw new IOException(thrown);
}
return capture;
}
reloaded
не объявлен как энергозависимый, поскольку к нему обращаются только в потоке приложения JavaFX.