JavaFX中如何让线程等待WebView内JavaScript执行完毕再继续?
解决方案:异步任务队列 + JS-Java 回调
要解决循环等待JS函数执行完毕的问题,核心思路是放弃同步循环,改用异步任务队列+回调机制,完全贴合JavaFX的单线程UI模型,既不会阻塞GUI,也能保证任务按顺序执行。
1. 实现Java回调类(供JS调用)
首先定义一个Java类,用于接收JS的完成通知,触发下一个任务:
import javafx.application.Platform; public class JSCallback { private Runnable onComplete; public void setOnComplete(Runnable onComplete) { this.onComplete = onComplete; } // JS将调用此方法通知任务完成 public void change() { // 确保回调在JavaFX应用线程执行 Platform.runLater(() -> { if (onComplete != null) { onComplete.run(); } }); } }
2. 重构startExp方法:用任务队列替代同步循环
把原来的嵌套循环逻辑拆成独立任务,存入队列,只有当前任务的JS函数执行完毕后,才会触发下一个任务:
import javafx.concurrent.Worker; import javafx.beans.value.ChangeListener; import java.util.Queue; import java.util.LinkedList; import java.util.concurrent.ThreadLocalRandom; import java.net.URL; import netscape.javascript.JSObject; public void startExp() { root.setCenter(webView); // 初始化回调对象并注入到JS环境 JSCallback callback = new JSCallback(); webEngine.executeScript("window.invoke = " + JSObject.toJavaObject(callback)); // 创建任务队列,存储所有待执行的页面加载任务 Queue<Runnable> taskQueue = new LinkedList<>(); // 填充任务队列:把原循环逻辑转为独立任务 for (String expMode : expModes) { int r = -1; for (int j = 0; j < 2; j++) { int temp = r; while (r == temp) { r = ThreadLocalRandom.current().nextInt(0, 2); } final String mode = expMode + ".html"; taskQueue.add(() -> { System.out.println(mode); URL url = this.getClass().getResource(mode); // 复用加载监听器,避免重复添加 webEngine.getLoadWorker().stateProperty().removeListener(loadListener); webEngine.getLoadWorker().stateProperty().addListener(loadListener); webEngine.load(url.toString()); }); } } // 定义页面加载完成后的处理逻辑 ChangeListener<Worker.State> loadListener = (ov, oldState, newState) -> { if (newState == Worker.State.SUCCEEDED) { System.out.println("页面加载完成,执行JS timer函数"); // 设置回调:JS执行完毕后自动取下一个任务执行 callback.setOnComplete(() -> { if (!taskQueue.isEmpty()) { taskQueue.poll().run(); } }); // 传入实际的passage内容(原代码中传的是字符串'passage',这里修正) webEngine.executeScript("timer('" + passage + "');"); } }; // 启动第一个任务 if (!taskQueue.isEmpty()) { taskQueue.poll().run(); } }
3. 修正HTML中的JS代码
确保JS在timer执行完毕后正确调用Java回调,同时重置计数器避免后续调用出错:
<script type="application/javascript"> let i = 0; function timer(a) { const splitted = a.split(" "); const timerID = setInterval(highlight, 500); function highlight() { if (i === splitted.length) { clearInterval(timerID); // 调用Java端的回调通知任务完成 if (window.invoke) { invoke.change(); } i = 0; // 重置计数器,避免下次调用直接结束 return; } let markS = "<mark>"; let markE = "</mark>"; let temp = splitted[i]; splitted[i] = markS + splitted[i] + markE; document.getElementById("demo").innerHTML = splitted.join(" "); splitted[i] = temp; i++; } } </script>
方案说明
- 无阻塞UI:所有任务调度都基于事件回调,不会卡住JavaFX应用线程,GUI始终保持响应。
- 顺序执行:任务队列保证页面加载和JS执行严格按原循环顺序进行,只有当前任务完成才会启动下一个。
- 线程安全:通过
Platform.runLater()确保回调操作在JavaFX线程执行,符合WebView的线程要求。
内容的提问来源于stack exchange,提问作者asad
相关产品推荐
相关产品推荐

