用Java简单模仿Chromium的架构

习惯了Electron的开发模式,我想用Java简单地模仿Chromium的架构,实现一个类似Electron的框架。毕竟,Electron的性能问题一直是人们诟病的地方,而Java作为一门成熟的语言,性能上有着很大的优势。当然,Chromium的架构非常复杂,这里只是一个简单的模仿.

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的事件监听和处理机制。

  1. 事件类(Event):

    • 定义一个事件类,用于封装事件信息。这个类可以包含事件类型、源、以及任何相关数据。
  2. 事件监听器接口(EventListener):

    • 创建一个事件监听器接口,定义一个方法,例如onEvent(Event event),用于处理事件。
  3. 事件分发器(EventDispatcher):

    • 管理事件监听器的注册和注销。
    • 负责在事件发生时通知所有注册的监听器。
  4. IPC通信类(IPCCommunication):

    • 用于进程间的通信。
    • 能够发送事件和接收来自其他进程的事件。
    • 当接收到事件时,将其封装为Event对象并通过EventDispatcher分发。
  5. 具体的事件处理类:

    • 实现EventListener接口,定义对特定事件的响应逻辑。

以下是一个简单的代码示例来说明这些概念:

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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
@FunctionalInterface
public interface Callback {
void call(String data);
}

// 事件类,支持传输数据/回调函数
public class Event {
private String type;
private Object data; // 添加数据字段,一般采用JsonObject
private Callback callback; // 添加回调字段

public Event(String type, Object data) {
// 没有回调函数的构造函数
this.type = type;
this.data = data;
this.callback = null;
}

public Event(String type, Object data, Callback callback) {
// 有回调函数的构造函数
this.type = type;
this.data = data;
this.callback = callback;
}

// Getter 和 Setter
public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public Object getData() {
return data;
}

public void setData(Object data) {
this.data = data;
}

public Callback getCallback() {
return callback;
}

public void setCallback(Callback callback) {
this.callback = callback;
}

// 其他可选代码...
}

// 事件监听器接口
public interface EventListener {
void onEvent(Event event);
}

// 事件分发器
public class EventDispatcher {
private Map<String, List<EventListener>> listeners = new HashMap<>();

public void registerListener(String eventType, EventListener listener) {
this.listeners.computeIfAbsent(eventType, k -> new ArrayList<>()).add(listener);
}

public void unregisterListener(String eventType, EventListener listener) {
this.listeners.getOrDefault(eventType, new ArrayList<>()).remove(listener);
}

public void dispatchEvent(Event event) {
List<EventListener> eventListeners = listeners.getOrDefault(event.getType(), new ArrayList<>());
for (EventListener listener : eventListeners) {
listener.onEvent(event);
}
}
}

// IPC通信类
public class IPCCommunication {
private final EventDispatcher dispatcher;

public IPCCommunication(EventDispatcher dispatcher) { // dispatcher 在main中创建,传入IPC中
this.dispatcher = dispatcher;
}

public void sendMessage(String message) {
// 类似于IPC中注册的回调函数,在render中被调用,发送消息给main
// main中注册的EventListener会被调用

// 解析消息,创建事件
Event event = new Event("send-message", message);
// ... 解析逻辑 ...
dispatcher.dispatchEvent(event);
}
}

public class Render extends Thread{
private final IPCCommunication ipc;

public Render(IPCCommunication ipc) {
this.ipc = ipc;
}

@Override
public void run() {
// 创建事件监听器
// ...
// IPC通信接收到消息时,会调用事件分发器的dispatchEvent方法
// 事件分发器会调用所有注册的监听器的onEvent方法
// ...

// 入口,渲染默认页面
}
}

// main进程中
public class Main {
public static void main(String[] args) {
// 创建事件分发器
EventDispatcher dispatcher = new EventDispatcher();
// 创建IPC通信类
IPCCommunication ipc = new IPCCommunication(dispatcher);
// 启动渲染进程
new Thread(new Render(ipc)).start();

// 具体的事件处理类,以QuickHideAndShowListener举例
// 监听器在main中注册
class QuickHideAndShowListener implements EventListener {
@Override
public void onEvent(Event event) {
if ("set-quick-hide-and-show".equals(event.getType())) {
// 处理事件
System.out.println("set quickHideAndShow" + event.getData());
}
}
}
// 创建事件监听器并且注册事件监听器
dispatcher.registerListener("set-quick-hide-and-show", new QuickHideAndShowListener()); // 也可以采用匿名类的定义方法更简洁
// ...
// IPC通信接收到消息时,会调用事件分发器的dispatchEvent方法
// 事件分发器会调用所有注册的监听器的onEvent方法
// ...
}
}

在这个设计中,在主进程中创建EventDispatcherIPCCommunication的实例,并在QuickHideAndShowListener中定义对特定事件的处理逻辑。然后,当IPC通信接收到消息时,它会创建一个事件并通过EventDispatcher分发给所有注册的监听器。这种设计模式允许在Java中模拟类似Electron的事件监听和处理机制。

我们使用IPCCommunication类来实现Preload的功能。IPCCommunication充当了Preload的角色,可以给Render提供一些能力,例如发送消息给主进程。

渲染进程的基本实现

由于使用JavaFX作为GUI框架,我们可以包装一些JavaFX的功能。

1. 页面管理和切换

首先,我们定义一个PageInterface,每个页面类都实现这个接口, 每个页面都需要有一个render函数(render函数中用JavaFX展示页面)。

1
2
3
public interface PageInterface {
void render(Stage stage);
}

这样,所有页面都实现PageInterface。这个接口中的render方法负责在给定的Stage上渲染页面。

2. 事件处理和消息传递

要处理用户的操作,如按钮点击和输入验证,您可以在每个页面内部定义事件处理器。例如,对于按钮点击事件,您可以在ConnectPage内部定义一个方法来处理连接操作。
示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ConnectPage implements PageInterface {
private void handleConnectButtonClick() {
// 处理连接按钮点击事件
}

@Override
public void render(Stage stage) {
// ... 设置连接页面的布局和组件 ...
Button connectButton = new Button("Connect");
connectButton.setOnAction(event -> handleConnectButtonClick());
// ... 其他组件的设置 ...
stage.show();
}
}

3. 封装JavaFX组件

对于切换页面,您可以在PageInterface中定义一个switchToPage方法,用于切换页面。然后,可以在ConnectPage中调用这个方法来切换到ChatPage

4. 统一页面渲染逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UIManager {
private Stage primaryStage;

public UIManager(Stage primaryStage) {
this.primaryStage = primaryStage;
}

public void switchToPage(PageInterface page) {
Platform.runLater(() -> page.render(primaryStage));
}
}

// 切换页面
UIManager uiManager = new UIManager(primaryStage);
// 切换到连接页面
uiManager.switchToPage(new ConnectPage());

优点

  • 通过事件处理和消息传递,可以在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 {
@Override
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() {
@Override
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{
@Override
public void render(Stage primaryStage) {
// ... 设置连接页面的布局和组件 ...
Button connectButton = new Button("Connect");
connectButton.setOnAction(event -> {
// 处理连接按钮点击事件
Render.login(address, port, user);
});
// ... 其他组件的设置 ...
primaryStage.show();
}
}

一些挑战

  1. Java虚拟机的内存管理方式与Electron的V8引擎有很大不同,这可能会对性能和资源管理产生影响。
  2. JavaFX强制需要在主线程中运行,对于任何修改,都必须添加到修改队列,然后稍后执行,这可能会对性能产生影响。
  3. 这个模仿未在性能上进行优化,可能会有性能问题。

可能的优化与提升

  1. 使用线程池来处理事件分发,以提高性能。目前的设计中,事件都是同步事件,如果事件处理时间过长,会阻塞主线程.后续需要添加异步事件的支持
  2. 目前主进程如果需要调用渲染进程的函数,直接使用了静态函数,这样的设计耦合度较高,后续需要添加更好的设计模式来解耦
  3. 目前的设计中,事件分发器是单例模式,这样的设计可能会导致线程安全问题,后续需要添加线程安全的支持
  4. 目前的设计都是基于单窗口的应用考虑的,对于多窗口的进程管理与通信,还需要进一步的设计与实现
  5. 对于目前用事件分发来实现的回调函数,后续可以进一步包装成Promise模式,使得代码更加简洁
  6. 在评论区补充吧…

总结

对于Java桌面端应用的开发,可能这个只是一个初步的尝试. 希望这个设计可以提高开发的效率~

github仓库链接:
JElectron-demo

参考资料: