Der Zustand docs JavaFX , dass ein WebView
bereit ist , wenn Worker.State.SUCCEEDED
erreicht wird , aber wenn Sie eine Weile warten (dh Animation
, Transition
, PauseTransition
, etc.), wird eine leere Seite gerendert.
Dies deutet darauf hin, dass im WebView ein Ereignis auftritt, das es für eine Erfassung vorbereitet. Was ist das?
Es gibt über 7.000 Code-Schnipsel auf GitHub, die verwendet werden,SwingFXUtils.fromFXImage
aber die meisten scheinen entweder nicht in Beziehung zu WebView
stehen, interaktiv zu sein (der Mensch maskiert die Rassenbedingungen) oder willkürliche Übergänge zu verwenden (irgendwo zwischen 100 ms und 2.000 ms).
Ich habe es versucht:
Abhören
changed(...)
innerhalb derWebView
Dimensionen des Geräts (DoubleProperty
Geräte für Höhen- und BreiteneigenschaftenObservableValue
, die diese Dinge überwachen können)- 🚫Nicht lebensfähig. Manchmal scheint sich der Wert getrennt von der Malroutine zu ändern, was zu einem Teilinhalt führt.
Blind alles und jeden
runLater(...)
im FX Application Thread erzählen .- 🚫Viele Techniken verwenden dies, aber meine eigenen Komponententests (sowie einige großartige Rückmeldungen von anderen Entwicklern) erklären, dass Ereignisse häufig bereits im richtigen Thread sind und dieser Aufruf überflüssig ist. Das Beste, was ich mir vorstellen kann, ist, dass die Warteschlange gerade so verzögert wird, dass es für einige funktioniert.
Hinzufügen eines DOM-Listeners / -Triggers oder JavaScript-Listeners / -Triggers zum
WebView
- 🚫 Sowohl JavaScript als auch das DOM scheinen ordnungsgemäß geladen zu sein, wenn
SUCCEEDED
es trotz der leeren Erfassung aufgerufen wird. DOM / JavaScript-Listener scheinen nicht zu helfen.
- 🚫 Sowohl JavaScript als auch das DOM scheinen ordnungsgemäß geladen zu sein, wenn
Verwenden eines
Animation
oder,Transition
um effektiv zu "schlafen", ohne den Haupt-FX-Thread zu blockieren.- ⚠️ Dieser Ansatz funktioniert und wenn die Verzögerung lang genug ist, können bis zu 100% der Komponententests durchgeführt werden. Die Übergangszeiten scheinen jedoch ein zukünftiger Moment zu sein, den wir nur erraten und das Design schlecht ist. Für performante oder geschäftskritische Anwendungen zwingt dies den Programmierer, einen Kompromiss zwischen Geschwindigkeit oder Zuverlässigkeit einzugehen, was für den Benutzer eine potenziell schlechte Erfahrung ist.
Wann ist ein guter Zeitpunkt, um anzurufen? WebView.snapshot(...)
?
Verwendungszweck:
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.
*/
Code-Auszug:
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();
}
}
Verbunden:
- Screenshot der vollständigen Webseite, die in die JavaFX WebView-Komponente geladen wurde, nicht nur des sichtbaren Teils
- Kann ich programmgesteuert einen Schnappschuss einer Szene aufnehmen?
- Screenshot der ganzen Seite, Java
- JavaFX 2.0+ WebView / WebEngine rendern Webseiten zu einem Bild
- Stellen Sie Höhe und Breite von Bühne und Szene in Javafx ein
- JavaFX: Ändern der Größe der Bühne bei Verwendung von Webview
- Richtige Größe der in Tabelcell eingebetteten Webview
- 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
wurde getestet und behebt es nicht. Bitte versuchen Sie es selbst, wenn Sie nicht einverstanden sind. Ich würde mich freuen, falsch zu liegen, es würde das Problem schließen.SUCCEEDED
Status (von dem der Listener auf den FX-Thread feuert) die richtige Technik ist. Wenn es eine Möglichkeit gibt, Ereignisse in der Warteschlange anzuzeigen, würde ich mich freuen, es zu versuchen. Ich habe spärliche Vorschläge durch Kommentare in den Oracle-Foren und einige SO-Fragen gefunden, die vonWebView
Natur aus in einem eigenen Thread ausgeführt werden müssen. Nach Tagen des Testens konzentriere ich mich dort auf Energie. Wenn diese Annahme falsch ist, großartig. Ich bin offen für vernünftige Vorschläge, die das Problem ohne willkürliche Wartezeiten beheben.loadContent
Methode oder beim Laden einer Datei-URL geschieht .Antworten:
Es scheint, dass dies ein Fehler ist, der bei der Verwendung der WebEngine-
loadContent
Methoden auftritt . Es tritt auch auf, wennload
eine lokale Datei geladen wird. In diesem Fall wird jedoch reload () aufgerufen. ausgeglichen.Da die Bühne angezeigt werden muss, wenn Sie einen Schnappschuss machen, müssen Sie
show()
vor dem Laden des Inhalts anrufen . Da Inhalte asynchron geladen werden, ist es durchaus möglich, dass sie vor der Anweisung nach dem Aufruf vonload
oder geladen werdenloadContent
beendet wird.Die Problemumgehung besteht also darin, den Inhalt in eine Datei zu platzieren und die WebEngine aufzurufen
reload()
Methode genau einmal . Beim zweiten Laden des Inhalts kann ein Snapshot erfolgreich von einem Listener der State-Eigenschaft des Load Workers erstellt werden.Normalerweise wäre das einfach:
Da Sie jedoch
static
für alles verwenden, müssen Sie einige Felder hinzufügen:Und Sie können sie hier verwenden:
Und dann müssen Sie es jedes Mal zurücksetzen, wenn Sie Inhalte laden:
Beachten Sie, dass es bessere Möglichkeiten gibt, eine Multithread-Verarbeitung durchzuführen. Anstatt Atomklassen zu verwenden, können Sie einfach
volatile
Felder verwenden:(Boolesche Felder sind standardmäßig falsch und Objektfelder sind standardmäßig null. Anders als in C-Programmen ist dies eine harte Garantie von Java; es gibt keinen nicht initialisierten Speicher.)
Anstatt in einer Schleife nach Änderungen zu suchen, die in einem anderen Thread vorgenommen wurden, ist es besser, die Synchronisation, eine Sperre oder eine übergeordnete Klasse wie CountDownLatch zu verwenden, die diese Dinge intern verwendet:
reloaded
wird nicht als flüchtig deklariert, da nur im JavaFX-Anwendungsthread darauf zugegriffen wird.quelle
volatile
Variablen. Leider funktioniert das AnrufenWebEngine.reload()
und Warten auf eine FolgeSUCCEEDED
nicht. Wenn ich einen Zähler in den HTML-Inhalt setze, erhalte ich Folgendes:0, 0, 1, 3, 3, 5
anstatt darauf0, 1, 2, 3, 4, 5
hinzuweisen, dass die zugrunde liegende Race-Bedingung dadurch nicht behoben wird.CountDownLatch
". Upvoting, da diese Informationen nicht leicht zu finden waren und die Geschwindigkeit und Einfachheit des Codes beim ersten FX-Start erhöht.Um die Größenänderung sowie das zugrunde liegende Snapshot-Verhalten zu berücksichtigen, habe ich (wir) die folgende funktionierende Lösung entwickelt. Beachten Sie, dass diese Tests 2.000-mal ausgeführt wurden (Windows, MacOS und Linux) und zufällige WebView-Größen mit 100% Erfolg lieferten.
Zuerst zitiere ich einen der JavaFX-Entwickler. Dies wird aus einem privaten (gesponserten) Fehlerbericht zitiert:
600
wenn die Höhe genau ist0
. Die Wiederverwendung von CodeWebView
sollte verwendet werdensetMinHeight(1)
,setPrefHeight(1)
um dieses Problem zu vermeiden. Dies ist nicht im folgenden Code enthalten, aber für jeden, der es an sein Projekt anpasst, erwähnenswert.quelle