gpt4 book ai didi

java - WebView 何时准备好进行快照()?

转载 作者:行者123 更新时间:2023-12-01 09:40:34 24 4
gpt4 key购买 nike

JavaFX 文档声明 WebView is ready when Worker.State.SUCCEEDED is reached但是,除非您稍等片刻(即 AnimationTransitionPauseTransition 等),否则会呈现一个空白页面。

这表明 WebView 内部发生了一个事件,为捕获做好了准备,但它是什么?

over 7,000 code snippets on GitHub which use SwingFXUtils.fromFXImage 但它们中的大多数似乎与 WebView 无关,是交互式的(人类掩盖竞争条件)或使用任意转换(从 100 毫秒到 2,000 毫秒)。

我试过了:

  • 收听 changed(...) 来自WebView的尺寸(高度和宽度属性 DoubleProperty 实现 ObservableValue ,可以监控这些东西)
  • 🚫不可行。有时,该值似乎与绘制例程分开更改,从而导致部分内容。
  • 一味的告诉 runLater(...) 在 FX 应用程序线程上。
  • 🚫许多技术都使用这个,但我自己的单元测试(以及其他开发人员的一些很好的反馈)解释说事件通常已经在正确的线程上,这个调用是多余的。我能想到的最好的方法是通过排队增加足够的延迟,这对某些人有用。
  • 将 DOM 监听器/触发器或 JavaScript 监听器/触发器添加到 WebView
  • 🚫在 SUCCEEDED 时,JavaScript 和 DOM 似乎都正确加载了尽管有空白捕获,但仍会调用。 DOM/JavaScript 监听器似乎没有帮助。
  • 使用 AnimationTransition在不阻塞主 FX 线程的情况下有效地“ sleep ”。
  • ⚠️ 这种方法有效,如果延迟足够长,可以产生高达 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
  • 最佳答案

    看来这是使用 WebEngine 的 loadContent 时出现的错误。方法。使用 load 时也会出现这种情况加载本地文件,但在这种情况下,调用 reload()会补偿的。

    此外,由于拍摄快照时需要显示舞台,因此您需要调用 show()在加载内容之前。由于内容是异步加载的,因此完全有可能在调用 load 之后的语句之前加载它。或 loadContent完成。

    因此,解决方法是将内容放在一个文件中,然后调用 WebEngine 的 reload()方法恰好一次。第二次加载内容时,可以从加载 worker 的 state 属性的监听器成功获取快照。

    通常,这很容易:

    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);
    }
    }));

    请注意,有更好的方法来执行多线程处理。您可以简单地使用 volatile 而不是使用原子类领域:
    private static volatile boolean started;
    private static volatile boolean finished = true;
    private static volatile Throwable thrown;
    private static volatile BufferedImage capture;

    ( boolean 字段默认为 false,对象字段默认为 null。与 C 程序不同,这是 Java 做出的硬保证;不存在未初始化内存之类的东西。)

    与其在循环中轮询另一个线程中所做的更改,不如使用同步、锁或更高级别的类,如 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未声明为 volatile,因为它仅在 JavaFX 应用程序线程中访问。

    关于java - WebView 何时准备好进行快照()?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59803411/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com