当寻找一种可以在 Java 中显示 HTML、执行 JavaScript 或使用 OAuth2 协议的解决方案时, 人们通常会寻找一种成熟的可嵌入浏览器。如果您选择 Chromium,则可以使用 JCEF 或 JxBrowser 进行集成。
我们收集了最常见的比较点并为您编写了本指南。为了帮助您在库之间进行选择,我们比较了它们的架构、功能和所需的集成工作。
简而言之
JCEF 是一款开源且免费的工具,对于开源、低预算或学术项目来说,它可能是一个可行的解决方案。
但是,它仍然需要一定的维护成本。您的开发团队需要投入更多时间和专业知识来处理 JCEF 社区未涵盖的事项, 例如:
- 维护不同平台的构建基础设施。
- 定期构建和打包 JCEF 的新版本。
- 在 macOS 和 Windows 上对二进制文件进行认证和签名。
- 修复问题并实现新功能。
相比之下,JxBrowser 无需任何维护工作。 您可以在五分钟内开始使用它。该库提供了更多功能,并对所有 UI 工具包提供了本地支持。如果您缺少某个功能、发现问题或有任何疑问,您可以随时依赖我们的帮助。
入门
JCEF 是 JxBrowser 的开源替代品。它基于 Chromium 嵌入式框架 (CEF), 允许您将 Web 浏览器嵌入到 Java Swing 桌面应用程序中。
要开始使用 JCEF,您需要自己构建它。这个过程分为三个部分:
- 配置环境。
- 编译二进制文件和 Java 类。
- 将其全部打包到 JAR 中并将其添加到您的应用程序中。
JxBrowser 是一个商业专有库。要开始使用 JxBrowser,请将单个依赖项添加到您的 Gradle 或 Maven 应用程序。或者下载库并将 JxBrowser 添加 JxBrowser JAR 文件包含所有必要的二进制文件,并且不需要开发人员执行额外操作。 适用于 macOS 的二进制文件已经过公证,适用于 Windows 的二进制文件已经过签署。
内部是什么
JxBrowser 和 JCEF 都在底层使用 Chromium。
JCEF 建立在 CEF 之上。这是一个著名的 C++ 框架,它也绑定了其他编程语言。
JxBrowser 使用专有的内部解决方案进行 Chromium 集成。它不基于 CEF。自 2007 年以来,我们一直在开发它,并在 DotNetBrowser 中使用它。
支持的 UI 工具包
JCEF 提供了一个 Swing 组件。如果您使用 JavaFX 或 SWT 开发应用程序,则需要使用集成桥。它们是用于 JavaFX 的 SwingNode 和用于 SWT 的 SWT_AWT 桥。
JxBrowser 为所有主要的 UI 工具包提供原生组件:JavaFX、Swing 和 SWT。 在我们的快速入门指南中可查看如何使用任何工具包创建简单的应用程序。
我们的客户还在 Eclipse RCP、IntelliJ IDEA[1] 和 NetBeans 插件。
嵌入桌面
编写一个简单的桌面应用程序所需的工作量对于这两个库来说是相似的。下面是一个带有地址栏和浏览器的示例:
JCEF
import org.cef.CefApp;
import org.cef.CefClient;
import org.cef.CefSettings;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefMessageRouter;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
* 这是一个带有地址栏和 JCEF 的简单应用程序。
*/
public final class JcefExample {
private static final String URL = "https://teamdev.com";
private static final boolean OFFSCREEN = false;
private static final boolean TRANSPARENT = false;
public static void main(String[] args) {
if (!CefApp.startup(args)) {
System.out.println("启动初始化失败!");
return;
}
CefSettings settings = new CefSettings();
settings.windowless_rendering_enabled = OFFSCREEN;
CefApp cefApp = CefApp.getInstance(settings);
CefClient client = cefApp.createClient();
client.addMessageRouter(CefMessageRouter.create());
CefBrowser browser = client.createBrowser(URL, OFFSCREEN, TRANSPARENT);
JTextField address = new JTextField(URL);
address.addActionListener(e -> browser.loadURL(address.getText()));
JFrame frame = new JFrame("JCEF");
frame.add(address, BorderLayout.NORTH);
frame.add(browser.getUIComponent(), BorderLayout.CENTER);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setSize(1280, 900);
frame.setVisible(true);
}
}
JxBrowser
import static com.teamdev.jxbrowser.engine.RenderingMode.HARDWARE_ACCELERATED;
import com.teamdev.jxbrowser.browser.Browser;
import com.teamdev.jxbrowser.engine.Engine;
import com.teamdev.jxbrowser.view.swing.BrowserView;
import java.awt.BorderLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
/**
* 这是一个带有地址栏和 JxBrowser 的简单应用程序。
*/
public final class JxBrowserExample {
private static final String URL = "https://teamdev.com";
public static void main(String[] args) {
Engine engine = Engine.newInstance(HARDWARE_ACCELERATED);
Browser browser = engine.newBrowser();
SwingUtilities.invokeLater(() -> {
final BrowserView view = BrowserView.newInstance(browser);
JTextField address = new JTextField(URL);
address.addActionListener(e -> browser.navigation().loadUrl(address.getText()));
JFrame frame = new JFrame("JxBrowser");
frame.add(address, BorderLayout.NORTH);
frame.add(view, BorderLayout.CENTER);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setSize(1280, 900);
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
engine.close();
}
});
browser.navigation().loadUrl(address.getText());
});
}
}
无头嵌入
如果您的应用程序在没有 UI 的情况下运行,那么 JCEF 并不适合您。它需要一个可见的 Swing UI,否则,浏览器不会完全初始化。
使用 JxBrowser,您可以创建具有真正离屏渲染的功能齐全的浏览器。请看:
import com.teamdev.jxbrowser.browser.Browser;
import com.teamdev.jxbrowser.engine.Engine;
import com.teamdev.jxbrowser.ui.Bitmap;
import com.teamdev.jxbrowser.view.swing.graphics.BitmapImage;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import static com.teamdev.jxbrowser.engine.RenderingMode.OFF_SCREEN;
/**
* JxBrowser 的无 UI 启动示例。
*/
public final class ServerApplicationExample {
public static void main(String[] args) {
try (Engine engine = Engine.newInstance(OFF_SCREEN)) {
Browser browser = engine.newBrowser();
browser.resize(1280, 1024);
browser.navigation().loadUrlAndWait("https://teamdev.com");
// 该浏览器已启动运行。让我们来截图检查一下。
Bitmap bitmap = browser.bitmap();
BufferedImage image = BitmapImage.toToolkit(bitmap);
try {
ImageIO.write(image, "PNG", new File("screenshot.png"));
} catch (IOException e) {
System.out.println("保存图像失败。" + e.getMessage());
}
}
}
}
API 与功能
JCEF 提供了多种功能来管理 Chromium 网络、打印、处理下载等。
使用 JxBrowser,您可以做同样的事情,甚至更多。在本节中,我们将演示 JCEF 中没有的功能。
使用 DOM
说到 DOM,您可以将浏览器的全部功能封装到一个可理解的 Java API 中。浏览此示例或查看指南。
// 在 DOM 树中查找节点。通过标签名称。
List<Element> links = document.findElementsByTagName("a");
// 通过 CSS 选择器。
Optional<Element> logo = document.findElementByCssSelector("#logo");
// 甚至可以通过 JavaScript 进行查找。
FormElement form = document.frame().executeJavaScript("document.forms[0]");
// 访问属性。
links.forEach(link -> link.putAttribute("target", "blank"));
// 提交表单。
form.submit();
// 滚动到元素。
logo.ifPresent(l -> l.scrollIntoView(BOTTOM));
JavaScript 和 Java 之间的交互
从 Java 执行 JavaScript 代码,从 JavaScript 调用 Java 方法。并享受自动类型转换。 浏览此示例或查看指南。
// 从 JavaScript 中获取对象。作为字符串。
String string = frame.executeJavaScript("'A string literal'");
// 作为数组。
JsArray array = frame.executeJavaScript("[0, 1, 2, 3, 4]");
// 或作为通用的 JavaScript 对象。
JsObject window = frame.executeJavaScript("window");
// 将任何 Java 对象传递到 JavaScript 世界。
window.putProperty("java", new MyJavaObject());
模拟用户输入
通过模拟用户交互来加强您的自动化测试和工具。浏览此示例或查看指南。
Point rect = buttonElement.boundingClientRect();
Point center = Point.of(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2);
browser.dispatch(MousePressed.newBuilder(location)
.button(MouseButton.PRIMARY)
.build());
browser.dispatch(MouseReleased.newBuilder(location)
.button(MouseButton.PRIMARY)
.build());
专有编解码器和 Widevine
默认禁用 H264[2] 和 AAC[3] 编解码器等专有技术,但启用它们也非常容易:
EngineOptions options =
EngineOptions.newBuilder(HARDWARE_ACCELERATED)
.enableProprietaryFeature(ProprietaryFeature.AAC)
.enableProprietaryFeature(ProprietaryFeature.H_264)
.enableProprietaryFeature(ProprietaryFeature.WIDEVINE)
.build();
Engine engine = Engine.newInstance(options);
甚至更多
有许多功能您只能在 JxBrowser 中找到。比如:
- 管理 Chromium 配置文件;
- 管理密码;
- 在网页上自动填写网络表单;
- 打印预览对话框;
- 开箱即用的 SSO 支持;
- 离屏渲染模式下的
<datalist>HTML 标签; - 离屏渲染模式下的拖放;
- WebRTC 和屏幕共享;
架构
库之间的主要架构差异是处理模型。JCEF 在 Java 进程中启动 Chromium。而 JxBrowser 在单独的本机进程中启动 Chromium。
JCEF 的模型允许您更快地初始化 Chromium,但它有几大缺点:
- Chromium 会消耗 Java 进程的内存和 CPU。您创建的 Chromium 实例越多,从您的应用程序中占用的资源就越多。
- Chromium 中的任何错误或意外行为都可能导致 JVM 崩溃并终止您的 Java 应用程序,从而导致潜在的用户数据丢失。
- Java 进程容易受到 Chromium 中的安全漏洞的影响。
JxBrowser 在单独的进程中运行 Chromium,并通过 IPC 与其通信。这样,Chromium 就不会影响 Java 进程的内存使用。
如果 Chromium 崩溃,Java 进程将继续运行。JxBrowser 甚至提供应用程序接口 API 让您的应用程序知道 Chromium 何时崩溃。使您能够重新启动浏览器并恢复用户会话。
获取帮助
JCEF 是一个开源项目。如果您需要帮助,可以在论坛[4]上提问或在错误跟踪器中创建问题[5]。 如果有您想要的功能,请积极提出您的建议和想法[6]。
JxBrowser 是为商业公司创建的商业产品。 我们关心我们的客户并提供支持服务。当您联系我们的客服时,您将直接与我们产品开发的工程师交谈。
如果您发现错误,我们将对其进行修复并在之后为您提供预览版本。如果缺少某项功能,我们将为您实现并在未来版本之一中发布。
对于高级案例和定制请求,我们将提供高级服务。
更新和发布周期
2021 年,JCEF 共推出了五个版本。每个新版本都附带 Chromium 版本升级。由于缺少发行说明,因此很难分析新版本中还包含其他哪些内容。
JxBrowser 在 2021 年共推出了 12 个版本。每个版本都附带修复和改进,我们在版本发布说明中进行了详细描述。我们会在 Chromium 正式发布后的 3-4 周内将其升级到最新的稳定版本。这些升级包括最新的安全补丁和修复的漏洞。
参考资料
[1] IntelliJ IDEA: plugins.jetbrains.com/plugin/9212…
[2] H264: zh.wikipedia.org/wiki/H.264/…
[3] AAC: zh.wikipedia.org/wiki/%E9%80…
[4] 论坛: magpcss.org/ceforum/vie…
[5] 创建问题: id.atlassian.com/login?appli…
[6] 建议和想法: bitbucket.org/chromiumemb…