当前位置: 首页 > news >正文

从 JavaFX WebView 迁移至 JxBrowser

长久以来,JavaFX 一直包含一个内置的 WebView 组件,这是在 Java 应用中渲染 Web 内容的一个稳定方案。然而,在更复杂或要求更高的使用场景中,它可能就不够用了。因此,许多开发者转向了像 JxBrowser 这样的替代方案。

在本迁移指南中,我们将详细介绍如何从 JavaFX WebView 迁移至 JxBrowser,并提供代码示例以及相关 JxBrowser 文档的链接。

本文侧重介绍如何迁移至 JxBrowser。若想了解为何要迁移,请参阅文章 JxBrowser 还是 JavaFX WebView,我们在其中详细解析了这两种方案的技术和架构差异。

依赖项

将 JxBrowser 添加到项目中,就像将一些 JAR 文件添加到类路径中一样简单。例如,一个运行于 Windows 的 JavaFX 应用将需要以下文件:

  • jxbrowser-8.9.2.jar. 该文件包含 JxBrowser 的大部分 API。
  • jxbrowser-javafx-8.9.2.jar. 该文件包含 JxBrowser 的 JavaFX 组件。
  • jxbrowser-win64-8.9.2.jar. 该文件包含适用于 64 位 Windows 的 Chromium 二进制文件。

你可以从 JxBrowser 8.9.2 版本发布说明页面下载所需文件。

如果您使用的是标准的 Maven 或 Gradle,只需像平常一样添加 Maven 仓库中的依赖项即可:

Maven

<repositories><repository><id>com.teamdev</id><url>https://europe-maven.pkg.dev/jxbrowser/releases</url></repository>
</repositories>
<dependency><groupId>com.teamdev.jxbrowser</groupId><artifactId>jxbrowser-javafx</artifactId><version>{version}</version>
</dependency>
<dependency><groupId>com.teamdev.jxbrowser</groupId><artifactId>jxbrowser-win64</artifactId><version>{version}</version>
</dependency>

Gradle

plugins {id("com.teamdev.jxbrowser") version "{gradle_plugin_version}"
}
jxbrowser {version = "{version}"
}
dependencies {implementation(jxbrowser.javafx)implementation(jxbrowser.win64)
}

线程安全性

WebViewWebEngine 并非线程安全的;访问它们及其 DOM/JavaScript 对象时,必须始终仅从 JavaFX 应用程序线程进行。

而 JxBrowser 是线程安全的。您可以在不同线程中安全地使用 JxBrowser 对象。不过,在 JavaFX 应用线程中调用 JxBrowser API 时需谨慎,因为它的许多方法是阻塞式的。为了避免影响用户体验,我们通常建议不要在 JavaFX 应用线程中调用 JxBrowser。

迁移

创建浏览器

JavaFX 提供了可视化的 WebView 组件(可添加到场景中),以及非可视的 WebEngine(包含实际的 Browser API)。

创建和使用方法如下:

WebView webView = new WebView();
WebEngine webEngine = webView.getEngine();
webEngine.load("https://example.com");
scene.getRoot().getChildren().add(webView);

JxBrowser 同样由可视化和非可视化部分组成。该库提供了非可视化的 EngineBrowser 对象,它们封装了 Browser API;还提供了一个可视化的 BrowserView 组件,可将其添加到场景中以显示加载的 Web 内容。

// 非可视化部分:
Engine engine = Engine.newInstance(HARDWARE_ACCELERATED);
Browser browser = engine.newBrowser();
browser.navigation().loadUrl("https://example.com");// 可视化部分:
Platform.runLater(() -> {BrowserView browserView = BrowserView.newInstance(browser);  scene.getRoot().getChildren().add(browserView);
});

在上述示例中,我们创建了一个非可视化的 Engine 实例,用于表示 Chromium 主进程。然后,我们创建一个非可视化的 Browser 实体,用于表示主进程中的特定浏览器——类似于 Google Chrome 中的浏览器标签页。最后,我们创建一个可视化的 BrowserView 节点并将其添加到场景中。

提示: 就像在 Google Chrome 中可以打开多个标签页一样,您可以在同一个 Engine 实例中创建多个 Browser 对象。

如需了解 JxBrowser API 中主要组件、进程模型及其他架构细节,请查阅架构指南。

关闭浏览器

在 JavaFX 中,并不需要显式关闭 WebView 实例。通常将其从场景图(scene graph)中移除就已足够。

而在 JxBrowser 中,仅从场景图中移除 BrowserView 不会关闭浏览器并释放所有已分配的资源。你必须手动关闭 BrowserEngine 对象:

JavaFX

scene.getRoot().getChildren().add(webView);

JxBrowser

// 关闭单个 Browser。
browser.close();// 关闭 Engine。此操作将自动关闭其包含的所有 Browser。
engine.close();

页面导航

WebEngine 中的导航功能几乎可以直接转换为 JxBrowser 的调用方式:

JavaFX

webEngine.load("https://example.com");
webEngine.reload();WebHistory history = webEngine.getHistory();
var currentIndex = history.getCurrentIndex();
var historySize = history.getEntries().size();// 后退到上一个历史页面。
var previousPage = currentIndex - 1;
if (previousPage >= 0 && previousPage < historySize) {history.go(previousPage);
}// 前进到下一个历史页面。
var nextPage = currentIndex + 1;
if (nextPage < historySize) {history.go(nextPage);
}

JxBrowser

Navigation navigation = browser.navigation();
navigation.loadUrl("https://example.com");
navigation.reload();navigation.goBack();
navigation.goForward();// 跳转到指定的历史记录索引
navigation.goToIndex(2);
导航监听器

在这两种解决方案中,加载过程都是在后台进行的,因此需要注册监听器来检测加载何时完成。

在 JavaFX 中,可以通过监听加载工作器的状态来实现:

var worker = webEngine.getLoadWorker();
worker.stateProperty().addListener((ov, oldState, newState) -> {if (newState == State.SUCCEEDED) {// 此处可以执行 JavaScript 并访问 DOM 树。} else {System.out.println("导航失败!");}
});

在 JxBrowser 中,通知更加精细:

// 当导航操作完成时会触发该事件。
// 此时 frame 和 DOM 树可能尚未初始化。
navigation.on(NavigationFinished.class, event -> {if (event.error() != OK) {System.out.println("Navigation failed!");}
});// 当 frame 的文档加载完成,且可以访问 DOM 时,会触发此事件。
navigation.on(FrameDocumentLoadFinished.class, event -> {// 此处可以执行 JavaScript 并访问 DOM 树。
});// 此回调允许您在 frame 刚刚完成加载、但**尚未执行其自身的 JavaScript**之前
// 执行您的 JavaScript 代码。
browser.set(InjectJsCallback.class, params -> {Frame frame = params.frame();JsObject window = frame.executeJavaScript("window");if (window != null) {...}return Response.proceed();
});

JxBrowser 共提供种细粒度的导航事件。完整列表请参阅导航事件文档。

从 Java 调用 JavaScript

在这两种方案中,您都可以执行任意的 JavaScript 代码,在 Java 中获取 JavaScript 对象,并享受自动类型转换的便利:

JavaFX

// JavaScript 对象会被转换为 JSObject。
JSObject dialogs = (JSObject) webEngine.executeScript("dialogs");
dialogs.call("showError", "The card number is not correct!");// JavaScript 字符串会被转换为 String。
String locale = (String) dialogs.getMember("locale");

JxBrowser

browser.mainFrame().ifPresent(frame -> {// JavaScript 对象会被转换为 JsObject。JsObject dialogs = frame.executeJavaScript("dialogs");jsObject.call("showError", "The card number is not correct!");// JavaScript 字符串会被转换为 String。String locale = dialogs.property("locale");
});

JavaFX 会自动转换传入的 JavaScript 值。原始类型会被转换为对应的 Java 类型,JavaScript 对象则会被转换为 JSObject 实例。

JxBrowser 执行类似的转换,但为特定的 JavaScript 类型(例如函数、PromiseArrayBuffer 等)提供了专用的 Java 类型。在类型转换指南中可查看完整列表。

对于用于访问带索引的 JavaScript 对象的 JSObject.getSlot()JSObject.setSlot() 方法,JxBrowser 没有直接的替代方案。

JavaScript 对象的生命周期

在 JavaFX 和 JxBrowser 中,只要对应的 JavaScript 对象还存在,JSObjectJsObject 实例就能正常工作。当 JavaScript 对象被垃圾回收或所在的 frame 加载了新的文档时,该对象就会失效。无论是在 JavaFX 还是 JxBrowser 中,尝试使用已失效的 JavaScript 对象都会抛出异常。

JavaScript 对象被垃圾回收的时间难以预测,因此在 JxBrowser 中,传递给 Java 的 JavaScript 对象会被保护,防止被垃圾回收,直到新文档加载时才会关闭。若想释放对该 JavaScript 对象的引用,使其可以被垃圾回收,可以调用 JsObject.close() 方法:

JsObject persistentObject = frame.executeJavaScript("dialogs");
persistentObject.close();

从 JavaScript 调用 Java

要从 JavaScript 调用 Java 代码,需要将 Java 对象注入到 JavaScript 环境中。这两种方案中的做法非常相似:

JavaFX

public static class GreetingService {public void greet(String name) {System.out.println("Hello, " + name + "!");}
}...GreetingService greetings = new GreetingService();
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("greetings", greetings);

JxBrowser

@JsAccessible
public static class GreetingService {public void greet(String name) {System.out.println("Hello, " + name + "!");}
}...GreetingService greetings = new GreetingService();
JsObject window = frame.executeScript("window");
window.putProperty("greetings", greetings);
成员访问权限

在 JavaFX 中,JavaScript 可以访问被注入的 Java 对象的所有公共成员。

而在 JxBrowser 中,需要显式地将 Java 类及其成员标记为允许被 JavaScript 访问:

// 类的所有公共成员都将可被访问。
@JsAccessible
public class AccessibleClass {public String sayHelloTo(String firstName) {...}
}// 仅该类的单个方法可被访问。
public class RestrictedClass {@JsAccessiblepublic String sayHelloTo(String firstName) {...}
}

对于无法添加注解的类(例如标准库类),可以使用以下方式使其可访问:

JsAccessibleTypes.makeAccessible(java.util.HashMap.class);

更多关于如何使对象对 JavaScript 可访问的信息,请参阅 JavaScript 指南。

Java 对象的生命周期

JavaFX 对传递给 JavaScript 的 Java 对象使用弱引用。这意味着如果该对象被垃圾回收,其对应的 JavaScript 对象会变为 undefined

而 JxBrowser 使用的是强引用,会防止 Java 对象被回收。只有在以下几种情况下,引用才会被移除:frame 加载了新文档;frame 被移除;或 browser 被关闭时。

代理配置

JavaFX 的 WebView 使用的是 Java 运行时自带的网络栈,因此会自动遵循 Java 的代理配置。

在 JxBrowser 中,Chromium 在单独的进程中使用其自身的网络,并遵循系统代理设置。如果您不想使用系统设置,可以为每个 Profile 单独配置代理:

JavaFX

// 配置代理设置。
System.setProperty("http.proxyHost", "proxy.com");
System.setProperty("http.proxyPort", "8080");
System.setProperty("https.proxyHost", "proxy.com");
System.setProperty("https.proxyPort", "8081");
System.setProperty("nonProxyHosts", "example.com|microsoft.com");// 配置代理身份验证。
Authenticator.setDefault(new Authenticator() {protected PasswordAuthentication getPasswordAuthentication() {return new PasswordAuthentication("username", "password".toCharArray());}
});

JxBrowser

// 配置代理设置。
var profile = engine.profiles().defaultProfile();
var exceptions = "example.com,microsoft.com";
var proxyRules = "http=proxy.com:8080;https=proxy.com:8081";
profile.proxy().config(CustomProxyConfig.newInstance(proxyRules, exceptions));// 配置代理身份验证。
profile.network().set(AuthenticateCallback.class, (params, tell) -> {if (params.isProxy()) {tell.authenticate("username", "password");} else {// 跳过其他身份验证请求。tell.cancel();}
});

DOM 访问

JavaFX 和 JxBrowser 提供了一组类似的功能来访问 DOM 树:

JavaFX

var document = webEngine.getDocument();
var element = document.getElementById("exit-app");
((EventTarget) element).addEventListener("click", listener, false);

JxBrowser

browser.mainFrame().flatMap(Frame::document).flatMap(document -> document.findElementById("exit-app")).ifPresent(element -> {element.addEventListener(CLICK, listener, true);});

在 JxBrowser 中,DOM 节点在 Java 和 JavaScript 之间传递时会自动转换为对应的 Java 类型。与 JsObject 类似,它们在浏览器中会被保护,不会被垃圾回收。如需手动释放资源,可调用 close() 方法,使其可被回收。

更多关于 DOM 操作的内容,请参阅 DOM 指南。

打印功能

JavaFX 提供了 API 来打印任何可视节点,包括 WebView。您可以选择打印机、通过代码配置部分打印参数,或者向用户展示系统打印对话框。

JxBrowser 则使用 Chromium 的打印能力。它同样支持选择打印机、以编程方式设置打印参数,并在需要时显示 Chromium 的打印预览对话框。

JavaFX

var printer = findMyPrinter(Printer.getAllPrinters());
var job = PrinterJob.createPrinterJob(printer);
if (showDialogs) {// 向用户显示系统对话框。job.showPageSetupDialog(stage);job.showPrintDialog(stage);
} else {// 或者静默打印var settings = printerJob.getJobSettings();settings.setCopies(3);settings.setCollation(COLLATED);webView.getEngine().print(job);printerJob.job();
}

JxBrowser

browser.set(PrintCallback.class, (params, tell) -> {if (showDialogs) {// 向用户显示系统对话框。tell.showPrintPreview();} else {// 或静默打印。tell.print();}
});// 注册 `PrintHtmlCallback` 用于打印 HTML 页面。
// 若从 PDF 文件发起打印,则需使用 `PrintPdfCallback`。
browser.set(PrintHtmlCallback.class, (params, tell) -> {var printer = findMyPrinter(params.printers());var job = printer.printJob();var settings = job.settings();settings.copies(3);settings.enableCollatePrinting();job.on(PrintCompleted.class, event -> {System.out.println("Printing completed");});tell.proceed(printer);
});browser.set(PrintPdfCallback.class, (params, tell) -> {...
});

提示: 即使没有系统打印机,您也可以使用 Chromium 内置的 PDF 打印机:params.printers().pdfPrinter()

有关如何配置打印功能的更多信息,请参阅打印指南。

JxBrowser 中的打印预览对话框

用户代理

在 JavaFX 中,您可以自定义 Browser 的用户代理(User-Agent)。

在 JxBrowser 中,您可以自定义单个 Browser 的用户代理,也可以自定义整个 Engine:

JavaFX

webEngine.setUserAgent("custom user agent");

JxBrowser

// 在 Engine 启动时配置全局用户代理。
var opts = EngineOptions.newBuilder(HARDWARE_ACCELERATED).userAgent("custom user agent").build();
var engine = Engine.newInstance(opts);// 或者,为特定 Browser 配置 UI。
browser.userAgent("custom user agent");

用户数据目录

在 JavaFX 中,用户数据目录用于存储本地存储中的数据。您可以显式配置该目录,或者 Engine 会根据操作系统和用户偏好自动选择。

在 JxBrowser 中,用户数据目录存储所有用户数据,包括缓存、本地存储和其他相关信息。您可以在启动 Engine 时配置该目录,或者 JxBrowser 会使用临时目录,该目录将在 Engine 关闭时被删除:

JavaFX

webEngine.setUserDataDirectory(new File("/path/to/directory"));

JxBrowser

var opts = EngineOptions.newBuilder(HARDWARE_ACCELERATED).userDataDir(Paths.get("/path/to/directory")).build();
var engine = Engine.newInstance(opts);

请注意,同一个用户数据目录不能被单个或不同 Java 应用中运行的多个 Engine 实例同时使用。尝试使用同一个用户数据目录将导致 Engine 创建过程中抛出异常。

弹出窗口

在 JavaFX 中,当网页想要在新窗口中打开内容时,WebEngine 不会创建新窗口。相反,它会用新窗口替换当前加载的页面。

通过注册自定义弹出处理器,可以更改此行为:

webEngine.setCreatePopupHandler(features -> {if (noPopups) {// 返回 null 会取消弹出窗口的创建。return null;}// 通过创建新的 WebView,可以指示 JavaFX 为新弹出窗口使用它。var popupView = new WebView();scene.getRoot().getChildren().add(popupView);return popupView.getEngine();
});

在 JxBrowser 中,所有弹出窗口默认都是被抑制的。要更改此设置,需注册 CreatePopupCallback

browser.set(CreatePopupCallback.class, params -> {return noPopups? CreatePopupCallback.Response.suppress(): CreatePopupCallback.Response.create();}
});

如果允许创建弹出窗口且 BrowserView 在 UI 中可见,JxBrowser 会在新的 Stage 中打开弹出窗口。你可以在 OpenBrowserPopupCallback 中自定义此行为:

browser.set(OpenBrowserPopupCallback.class, params -> {var popupBrowser = params.popupBrowser();var popupBounds = params.initialBounds();Platform.runLater(() -> {var popupView = BrowserView.newInstance(browser);scene.getRoot().getChildren().add(popupView);});return OpenBrowserPopupCallback.Response.proceed();
});

有关处理弹出对话框的更多信息,请参阅弹出窗口指南。

JavaScript 对话框

JavaFX 和 JxBrowser 都允许您自定义 JavaScript 对话框(如 confirm、prompt 和 alert)的行为:

JavaFX

webEngine.setConfirmHandler(value -> {if (silent) {return null;} else {return showMyConfirmDialog();}
});webEngine.setPromptHandler(promptData -> {if (silent) {return null;} else {return showMyPromptDialog(promptData);}
});webEngine.setOnAlert(event -> System.out.println("Alert happened!"));

JxBrowser

browser.set(ConfirmCallback.class, (params, action) -> {if (silent) {action.cancel();} else {var result = showMyConfirmDialog(params);if (result) {action.ok();} else {action.cancel();   } }
});browser.set(PromptCallback.class, (params, action) -> {if (silent) {action.cancel();} else {action.ok(showMyPromptDialog(params));}
});browser.set(AlertCallback.class, (params, action) -> {System.out.println("Alert happened");action.ok();
});

如果您不配置这些处理程序,JavaFX 默认会抑制对话框 —— confirm 对话框返回 false,prompt 对话框返回空字符串。而 JxBrowser 则会调用 JavaFX 的默认对话框实现来显示这些对话框。

您可以阅读对话框指南,了解如何自定义文件选择器、身份验证和其他类型的对话框。

自定义 CSS

在 JavaFX 中,可以通过设置样式表文件路径,或使用包含样式的 Data URL 来注入自定义 CSS。

在 JxBrowser 中,可以通过将 CSS 样式作为字符串传入来注入自定义样式:

JavaFX

webEngine.setUserStyleSheetLocation("file:///path/theme.css");

JxBrowser

// 此回调在文档准备就绪后触发,此时可注入 CSS。
browser.set(InjectCssCallback.class, params -> {var styles = readFile("file:///path/theme.css")return InjectCssCallback.Response.inject(styles);
});

总结

JavaFX WebView 和 JxBrowser 都提供了类似的功能,从 WebView 迁移至 JxBrowser 不会给您带来太多麻烦。

在本指南中,我们提供了迁移 WebView 大部分功能的代码示例,并附上了相关文档的链接。

尽管实际项目中的迁移工作可能会比较复杂,但我们相信通过本指南可以大大简化这一过程。您可以将其作为迁移项目的起点,如有疑问,欢迎随时联系我们。

http://www.dtcms.com/a/274408.html

相关文章:

  • 【科研绘图系列】R语言绘制系统发育树和柱状图
  • 以科技赋能未来,科聪持续支持青年创新实践 —— 第七届“科聪杯”浙江省大学生智能机器人创意竞赛圆满落幕
  • 宝塔 php支持sqlserver
  • 稀疏激活大模型推理优化:突破效率瓶颈的曙光
  • JavaScript VMP (Virtual Machine Protection) 分析与调试
  • 动态规划初步(完全背包)
  • The 2023 ICPC Asia Hangzhou Regional Contest (H. Sugar Sweet II(基环树,期望))
  • 闲庭信步使用图像验证平台加速FPGA的开发:第九课——图像插值的FPGA实现
  • 【JMeter】执行SQL
  • Elasticsearch 滚动(Scroll)用法、使用场景及与扫描(Scan)的区别
  • Linux 下使用 vim 文本编辑器时的操作指令
  • OGG-00551 ODBC error: SQLSTATE 22007,从字符串转换日期和/或时间时,转换失败
  • 和鲸社区深度学习基础训练营2025年关卡3_Q1(1)
  • 零基础入门:Air8000系列蓝牙配网开发(LuatOS版)
  • 攻防世界——web题 upload
  • Laravel + Python 图片水印系统:实现与调试指南
  • 将七大核心理念融入AI Agent设计——构建“数字生命体”的宏伟蓝图
  • 大数据在UI前端的应用创新:基于用户反馈的产品迭代优化系统
  • UI前端大数据可视化实战技巧:如何利用数据故事化提升用户参与度?
  • 前端面试专栏-算法篇:22.树结构(二叉树、B树、红黑树)
  • Web 应用防火墙:守护应用安全的核心屏障
  • webpack高级配置
  • OpenAvatarChat数字人项目结构分析
  • 星云穿越与超光速飞行特效的前端实现原理与实践
  • 第三章 隧道与轨道交通工程 3.8 安全质量控制
  • 排序算法(一):冒泡排序
  • UniApp 生命周期详解:从启动到销毁的完整指南
  • 如何快速掌握WeNet:从零到一的端到端语音识别学习指南
  • SSRF11 各种限制绕过之DNS rebinding 绕过内网 ip 限制
  • FREERTOS根本不能使用连续接收串口思想