用Java简单模仿Chromium的架构
习惯了Electron的开发模式,我想用Java简单地模仿Chromium的架构,实现一个类似Electron的框架。毕竟,Electron的性能问题一直是人们诟病的地方,而Java作为一门成熟的语言,性能上有着很大的优势。当然,Chromium的架构非常复杂,这里只是一个简单的模仿.
Chromium架构简介
Chromium是一个开源的浏览器项目,它的架构非常复杂,但是也非常强大。它的架构图如下:
从图中可以看到,Chromium的架构分为多个层次,核心是一个Main Thread
然后浏览器通过IPC通信与渲染进程Render通信. 每个Render进程都有一个独立的渲染线程,用于渲染网页。
这样的架构使得Chromium非常稳定,同时也非常高效。
这样独立每个的渲染进程使得Chromium的鲁棒性更好,各个模块之间的耦合度更低,同时也更容易实现多进程并行处理。
Electron也使用了类似的架构,但是它是基于Node.js的.
Electron中,主要的模块为Main.js, preload.js, 和Render.js.
Main.js负责创建窗口,处理窗口事件,preload.js负责在渲染进程中注入一些Node.js的API, Render.js负责渲染网页。
而要在Java中模仿这样的架构,我们需要先搞清楚Chromium的架构更多细节.
ResourceDispatcherHost
ResourceDispatcherHost是Chromium中的一个重要模块,它负责处理网络请求。它的主要功能是将网络请求分发给不同的Render进程,然后将渲染结果返回给浏览器进程。这样的架构使得Chromium的网络请求非常高效。
假设我们是Render进程,如果我们要加载一个网页的图片,我们会向ResourceDispatcherHost发送一个请求.实际处理网络请求的是ResourceDispatcherHost,而Render只负责渲染结果.
IPC通信
Chromium中的IPC通信是非常重要的,它负责浏览器进程和渲染进程之间的通信。IPC通信的实现也是分发器.在Java中,我们可以简单地使用共享内存来实现IPC通信.
渲染进程
渲染进程是Chromium中的一个重要模块,它负责渲染网页。渲染进程是一个独立的进程,它有自己的渲染线程,用于渲染网页。在Java中,我将采用JavaFX来实现渲染进程.
Java模仿Chromium架构
事件处理和消息传递
在Java中,我们首先需要创建一个事件监听系统。
这个系统能够让不同的组件注册事件监听器,并在特定事件发生时调用这些监听器。
这样的设计模式可以让我们在Java中模仿类似Electron的事件监听和处理机制。
事件类(Event):
- 定义一个事件类,用于封装事件信息。这个类可以包含事件类型、源、以及任何相关数据。
事件监听器接口(EventListener):
- 创建一个事件监听器接口,定义一个方法,例如
onEvent(Event event)
,用于处理事件。
- 创建一个事件监听器接口,定义一个方法,例如
事件分发器(EventDispatcher):
- 管理事件监听器的注册和注销。
- 负责在事件发生时通知所有注册的监听器。
IPC通信类(IPCCommunication):
- 用于进程间的通信。
- 能够发送事件和接收来自其他进程的事件。
- 当接收到事件时,将其封装为
Event
对象并通过EventDispatcher
分发。
具体的事件处理类:
- 实现
EventListener
接口,定义对特定事件的响应逻辑。
- 实现
以下是一个简单的代码示例来说明这些概念:
1 |
|
在这个设计中,在主进程中创建EventDispatcher
和IPCCommunication
的实例,并在QuickHideAndShowListener
中定义对特定事件的处理逻辑。然后,当IPC通信接收到消息时,它会创建一个事件并通过EventDispatcher
分发给所有注册的监听器。这种设计模式允许在Java中模拟类似Electron的事件监听和处理机制。
我们使用IPCCommunication
类来实现Preload的功能。IPCCommunication充当了Preload的角色,可以给Render提供一些能力,例如发送消息给主进程。
渲染进程的基本实现
由于使用JavaFX作为GUI框架,我们可以包装一些JavaFX的功能。
1. 页面管理和切换
首先,我们定义一个PageInterface
,每个页面类都实现这个接口, 每个页面都需要有一个render函数(render函数中用JavaFX展示页面)。
1 | public interface PageInterface { |
这样,所有页面都实现PageInterface
。这个接口中的render
方法负责在给定的Stage
上渲染页面。
2. 事件处理和消息传递
要处理用户的操作,如按钮点击和输入验证,您可以在每个页面内部定义事件处理器。例如,对于按钮点击事件,您可以在ConnectPage
内部定义一个方法来处理连接操作。
示例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14class ConnectPage implements PageInterface {
private void handleConnectButtonClick() {
// 处理连接按钮点击事件
}
public void render(Stage stage) {
// ... 设置连接页面的布局和组件 ...
Button connectButton = new Button("Connect");
connectButton.setOnAction(event -> handleConnectButtonClick());
// ... 其他组件的设置 ...
stage.show();
}
}
3. 封装JavaFX组件
对于切换页面,您可以在PageInterface
中定义一个switchToPage
方法,用于切换页面。然后,可以在ConnectPage
中调用这个方法来切换到ChatPage
。
4. 统一页面渲染逻辑
1 | public class UIManager { |
优点
- 通过事件处理和消息传递,可以在Java中模拟类似Electron的事件监听和处理机制。
- 通过封装JavaFX组件,可以实现页面管理和切换。
- 通过统一页面渲染逻辑,可以简化页面切换的代码。
使用实例
用简单的代码完整实现登录功能举个例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105// Main类中
public class Main {
class login implements EventListener {
public void onEvent(Event event) {
if ("login".equals(event.getType())) {
JSONObject json = new JSONObject(event.getData().toString());
String address = json.get("address").toString();
String port = json.get("port").toString();
String user = json.get("user").toString();
// 建立连接
Socket socket = new Socket(address, Integer.parseInt(port));
// ...
// 先注册事件监听器再发送登录请求
dispatcher.registerListener("login-success", new EventListener() {
public void onEvent(Event event1) {
if(event1.getType().equals("login-success") && event1.getData().toString().equals("success")){
event.getCallback().call("success");
}else{
event.getCallback().call(event1.getData().toString());
}
dispatcher.unregisterListener("login-success", this); // 注销监听器
}
}); // 匿名类实现回调函数
sendRequestToServer("login", new JSONObject().put("user", user));
// 处理接收服务器消息的线程触发login-success事件
}
}
}
public static void main(String[] args) {
// 创建事件分发器
EventDispatcher dispatcher = new EventDispatcher();
// 创建IPC通信类
IPCCommunication ipc = new IPCCommunication(dispatcher);
// 启动渲染进程
Render.setIPC(ipc);
Render.launch(Render.class, args);
// 创建事件监听器并且注册事件监听器
dispatcher.registerListener("login", new login());
// ...
// IPC通信接收到消息时,会调用事件分发器的dispatchEvent方法
// 事件分发器会调用所有注册的监听器的onEvent方法
// ...
}
}
// IPCCommunication类中
public class IPCCommunication{
public void login(JSONObject data, Callback callback) {
Event event = new Event("login", data, callback);
dispatcher.dispatchEvent(event);
}
}
// Render类中
public class Render extends Application {
public static IPCCommunication ipc;
public static void setIPC(IPCCommunication i) {
ipc = i;
}
public static void login(String address, int port, String user){ // 某个button点击事件调用的函数
JSONObject data = new JSONObject();
data.put("address", address);
data.put("port", port);
data.put("user", user);
try {
ConnectPage.showLoading();
ipc.login(data, res -> {
if(res.equals("success")){
logger.info("login success");
serverMode = false;
ConnectPage.hideLoading();
ChatPage.HistoryMode = false;
uiManager.switchToPage(new ChatPage());
}else{
logger.error("login failed: " + res);
ConnectPage.showError(res);
}
});
} catch (Exception e) {
logger.error("login failed: " + e.getMessage());
ConnectPage.showError("login failed");
}
}
}
// 某个page中(ConnectPage举例):
public class ConnectPage implements PageInterface{
public void render(Stage primaryStage) {
// ... 设置连接页面的布局和组件 ...
Button connectButton = new Button("Connect");
connectButton.setOnAction(event -> {
// 处理连接按钮点击事件
Render.login(address, port, user);
});
// ... 其他组件的设置 ...
primaryStage.show();
}
}
一些挑战
- Java虚拟机的内存管理方式与Electron的V8引擎有很大不同,这可能会对性能和资源管理产生影响。
- JavaFX强制需要在主线程中运行,对于任何修改,都必须添加到修改队列,然后稍后执行,这可能会对性能产生影响。
- 这个模仿未在性能上进行优化,可能会有性能问题。
可能的优化与提升
- 使用线程池来处理事件分发,以提高性能。目前的设计中,事件都是同步事件,如果事件处理时间过长,会阻塞主线程.后续需要添加异步事件的支持
- 目前主进程如果需要调用渲染进程的函数,直接使用了静态函数,这样的设计耦合度较高,后续需要添加更好的设计模式来解耦
- 目前的设计中,事件分发器是单例模式,这样的设计可能会导致线程安全问题,后续需要添加线程安全的支持
- 目前的设计都是基于单窗口的应用考虑的,对于多窗口的进程管理与通信,还需要进一步的设计与实现
- 对于目前用事件分发来实现的回调函数,后续可以进一步包装成Promise模式,使得代码更加简洁
- 在评论区补充吧…
总结
对于Java桌面端应用的开发,可能这个只是一个初步的尝试. 希望这个设计可以提高开发的效率~
github仓库链接:
JElectron-demo
参考资料: