460 lines
11 KiB
Markdown
460 lines
11 KiB
Markdown
|
|
# ElectronEgg 生命周期详解
|
|||
|
|
|
|||
|
|
本文档详细说明 ElectronEgg 框架的应用生命周期机制及其在项目中的实现。
|
|||
|
|
|
|||
|
|
## 生命周期流程图
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────┐
|
|||
|
|
│ new │ 创建 ElectronEgg 实例
|
|||
|
|
└──────┬──────┘
|
|||
|
|
│
|
|||
|
|
┌──────▼──────┐
|
|||
|
|
│ ready │ core app 加载完成(ee-core 框架初始化)
|
|||
|
|
└──────┬──────┘
|
|||
|
|
│
|
|||
|
|
┌──────▼─────────────┐
|
|||
|
|
│ electronAppReady │ Electron app 加载完成
|
|||
|
|
└──────┬─────────────┘
|
|||
|
|
│
|
|||
|
|
├─────────────────┐
|
|||
|
|
│ │
|
|||
|
|
┌──────▼──────┐ ┌─────▼────────┐
|
|||
|
|
│ mainWindow │ │ windowReady │ 主窗口创建完成
|
|||
|
|
└──────┬──────┘ └─────▲────────┘
|
|||
|
|
│ │
|
|||
|
|
└─────────────────┘
|
|||
|
|
│
|
|||
|
|
┌──────▼──────┐
|
|||
|
|
│ running │ 应用运行中
|
|||
|
|
└──────┬──────┘
|
|||
|
|
│
|
|||
|
|
┌──────▼──────┐
|
|||
|
|
│beforeClose │ 退出之前触发
|
|||
|
|
└──────┬──────┘
|
|||
|
|
│
|
|||
|
|
┌──────▼──────┐
|
|||
|
|
│ quit │ 应用退出
|
|||
|
|
└─────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 生命周期钩子详解
|
|||
|
|
|
|||
|
|
### 1. new - 实例创建
|
|||
|
|
|
|||
|
|
**触发时机**:调用 `new ElectronEgg()` 时
|
|||
|
|
|
|||
|
|
**实现位置**:[electron/main.js](electron/main.js#L6)
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
const { ElectronEgg } = require('ee-core');
|
|||
|
|
const app = new ElectronEgg();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**作用**:
|
|||
|
|
- 创建 ElectronEgg 应用实例
|
|||
|
|
- 初始化框架核心模块
|
|||
|
|
- 准备生命周期管理器
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 2. ready - 核心应用就绪
|
|||
|
|
|
|||
|
|
**触发时机**:ee-core 框架加载完成,Electron app 启动之前
|
|||
|
|
|
|||
|
|
**实现位置**:
|
|||
|
|
- 注册:[electron/main.js](electron/main.js#L10)
|
|||
|
|
- 实现:[electron/preload/lifecycle.js](electron/preload/lifecycle.js#L12-L14)
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 注册
|
|||
|
|
app.register("ready", life.ready);
|
|||
|
|
|
|||
|
|
// 实现
|
|||
|
|
async ready() {
|
|||
|
|
logger.info('[lifecycle] ready');
|
|||
|
|
// 在这里可以做:
|
|||
|
|
// - 初始化数据库连接
|
|||
|
|
// - 加载配置文件
|
|||
|
|
// - 初始化全局变量
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**适用场景**:
|
|||
|
|
- ✅ 初始化数据库连接
|
|||
|
|
- ✅ 加载应用配置
|
|||
|
|
- ✅ 注册全局服务
|
|||
|
|
- ✅ 初始化日志系统
|
|||
|
|
- ❌ 不能操作窗口(窗口还未创建)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3. electronAppReady - Electron 应用就绪
|
|||
|
|
|
|||
|
|
**触发时机**:Electron 的 `app.ready` 事件触发后,主窗口创建之前
|
|||
|
|
|
|||
|
|
**实现位置**:
|
|||
|
|
- 注册:[electron/main.js](electron/main.js#L11)
|
|||
|
|
- 实现:[electron/preload/lifecycle.js](electron/preload/lifecycle.js#L19-L21)
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 注册
|
|||
|
|
app.register("electron-app-ready", life.electronAppReady);
|
|||
|
|
|
|||
|
|
// 实现
|
|||
|
|
async electronAppReady() {
|
|||
|
|
logger.info('[lifecycle] electron-app-ready');
|
|||
|
|
// 在这里可以做:
|
|||
|
|
// - 注册全局快捷键
|
|||
|
|
// - 设置应用菜单
|
|||
|
|
// - 初始化托盘图标
|
|||
|
|
// - 注册协议处理
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**适用场景**:
|
|||
|
|
- ✅ 注册全局快捷键 (globalShortcut)
|
|||
|
|
- ✅ 创建应用菜单 (Menu)
|
|||
|
|
- ✅ 创建系统托盘 (Tray)
|
|||
|
|
- ✅ 注册自定义协议 (protocol)
|
|||
|
|
- ⚠️ 可以创建窗口,但通常在框架内部自动创建
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4. mainWindow - 主窗口创建
|
|||
|
|
|
|||
|
|
**触发时机**:框架创建主窗口时(内部流程,不需要手动注册)
|
|||
|
|
|
|||
|
|
**说明**:
|
|||
|
|
- 这是框架内部自动执行的步骤
|
|||
|
|
- 根据 `electron/config/config.*.js` 中的 `windowsOption` 配置创建窗口
|
|||
|
|
- 窗口创建完成后会触发 `windowReady` 钩子
|
|||
|
|
|
|||
|
|
**配置示例**:
|
|||
|
|
```javascript
|
|||
|
|
// electron/config/config.default.js
|
|||
|
|
windowsOption: {
|
|||
|
|
width: 1200,
|
|||
|
|
height: 800,
|
|||
|
|
show: false, // 设置为 false 可实现无白屏启动
|
|||
|
|
webPreferences: {
|
|||
|
|
contextIsolation: false,
|
|||
|
|
nodeIntegration: true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 5. windowReady - 窗口就绪
|
|||
|
|
|
|||
|
|
**触发时机**:主窗口创建完成,页面加载完毕
|
|||
|
|
|
|||
|
|
**实现位置**:
|
|||
|
|
- 注册:[electron/main.js](electron/main.js#L12)
|
|||
|
|
- 实现:[electron/preload/lifecycle.js](electron/preload/lifecycle.js#L26-L37)
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 注册
|
|||
|
|
app.register("window-ready", life.windowReady);
|
|||
|
|
|
|||
|
|
// 实现
|
|||
|
|
async windowReady() {
|
|||
|
|
logger.info('[lifecycle] window-ready');
|
|||
|
|
|
|||
|
|
// 延迟显示窗口,避免白屏
|
|||
|
|
const { windowsOption } = getConfig();
|
|||
|
|
if (windowsOption.show == false) {
|
|||
|
|
const win = getMainWindow();
|
|||
|
|
win.once('ready-to-show', () => {
|
|||
|
|
win.show(); // 显示窗口
|
|||
|
|
win.focus(); // 聚焦窗口
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 在这里可以做:
|
|||
|
|
// - 向渲染进程发送初始化数据
|
|||
|
|
// - 检查更新
|
|||
|
|
// - 加载用户配置
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**适用场景**:
|
|||
|
|
- ✅ 操作主窗口 (show/hide/maximize 等)
|
|||
|
|
- ✅ 向渲染进程发送消息
|
|||
|
|
- ✅ 执行自动更新检查
|
|||
|
|
- ✅ 加载用户数据并同步到前端
|
|||
|
|
- ✅ 实现无白屏启动(配合 `show: false`)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 6. running - 应用运行中
|
|||
|
|
|
|||
|
|
**触发时机**:窗口显示后,应用正常运行期间
|
|||
|
|
|
|||
|
|
**说明**:
|
|||
|
|
- 这不是一个独立的生命周期钩子
|
|||
|
|
- 表示应用的正常运行状态
|
|||
|
|
- 此时所有功能都可用
|
|||
|
|
|
|||
|
|
**可用操作**:
|
|||
|
|
- IPC 通信(前后端交互)
|
|||
|
|
- 业务逻辑处理
|
|||
|
|
- 数据库操作
|
|||
|
|
- 网络请求
|
|||
|
|
- 文件系统操作
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 7. beforeClose - 关闭前钩子
|
|||
|
|
|
|||
|
|
**触发时机**:用户点击关闭按钮或调用 `app.quit()` 之前
|
|||
|
|
|
|||
|
|
**实现位置**:
|
|||
|
|
- 注册:[electron/main.js](electron/main.js#L13)
|
|||
|
|
- 实现:[electron/preload/lifecycle.js](electron/preload/lifecycle.js#L42-L44)
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 注册
|
|||
|
|
app.register("before-close", life.beforeClose);
|
|||
|
|
|
|||
|
|
// 实现
|
|||
|
|
async beforeClose() {
|
|||
|
|
logger.info('[lifecycle] before-close');
|
|||
|
|
// 在这里可以做:
|
|||
|
|
// - 保存用户数据
|
|||
|
|
// - 关闭数据库连接
|
|||
|
|
// - 清理临时文件
|
|||
|
|
// - 释放系统资源
|
|||
|
|
// - 确认是否退出
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**适用场景**:
|
|||
|
|
- ✅ 保存应用状态
|
|||
|
|
- ✅ 关闭数据库连接
|
|||
|
|
- ✅ 清理临时资源
|
|||
|
|
- ✅ 询问用户是否确认退出
|
|||
|
|
- ✅ 上传日志或统计数据
|
|||
|
|
|
|||
|
|
**阻止关闭示例**:
|
|||
|
|
```javascript
|
|||
|
|
async beforeClose(args, event) {
|
|||
|
|
const { dialog } = require('electron');
|
|||
|
|
const result = await dialog.showMessageBox({
|
|||
|
|
type: 'question',
|
|||
|
|
buttons: ['取消', '退出'],
|
|||
|
|
message: '确定要退出应用吗?'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (result.response === 0) {
|
|||
|
|
// 阻止关闭
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 允许关闭
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 8. quit - 应用退出
|
|||
|
|
|
|||
|
|
**触发时机**:`beforeClose` 完成后,应用进程终止
|
|||
|
|
|
|||
|
|
**说明**:
|
|||
|
|
- 这是最终状态,不可逆
|
|||
|
|
- 所有资源清理应在 `beforeClose` 中完成
|
|||
|
|
- 退出后进程结束,无法执行代码
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 额外生命周期:preload
|
|||
|
|
|
|||
|
|
### preload - 预加载模块
|
|||
|
|
|
|||
|
|
**触发时机**:应用启动时,在所有其他钩子之前
|
|||
|
|
|
|||
|
|
**实现位置**:
|
|||
|
|
- 注册:[electron/main.js](electron/main.js#L16)
|
|||
|
|
- 实现:[electron/preload/index.js](electron/preload/index.js#L7-L9)
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 注册
|
|||
|
|
app.register("preload", preload);
|
|||
|
|
|
|||
|
|
// 实现
|
|||
|
|
function preload() {
|
|||
|
|
logger.info('[preload] load 1');
|
|||
|
|
// 在这里可以做:
|
|||
|
|
// - 加载环境变量
|
|||
|
|
// - 注册原生模块
|
|||
|
|
// - 设置全局异常处理
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**适用场景**:
|
|||
|
|
- ✅ 加载环境变量
|
|||
|
|
- ✅ 注册 Node.js 原生模块
|
|||
|
|
- ✅ 设置全局错误处理
|
|||
|
|
- ✅ 初始化第三方 SDK
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 实际开发示例
|
|||
|
|
|
|||
|
|
### 示例 1:数据库初始化
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// electron/preload/lifecycle.js
|
|||
|
|
const Database = require('better-sqlite3');
|
|||
|
|
let db;
|
|||
|
|
|
|||
|
|
class Lifecycle {
|
|||
|
|
async ready() {
|
|||
|
|
// 在 ready 钩子中初始化数据库
|
|||
|
|
const path = require('path');
|
|||
|
|
const dbPath = path.join(app.getPath('userData'), 'app.db');
|
|||
|
|
db = new Database(dbPath);
|
|||
|
|
logger.info('[lifecycle] database initialized');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async beforeClose() {
|
|||
|
|
// 在关闭前关闭数据库连接
|
|||
|
|
if (db) {
|
|||
|
|
db.close();
|
|||
|
|
logger.info('[lifecycle] database closed');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 示例 2:自动更新检查
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// electron/preload/lifecycle.js
|
|||
|
|
const { autoUpdater } = require('electron-updater');
|
|||
|
|
|
|||
|
|
class Lifecycle {
|
|||
|
|
async windowReady() {
|
|||
|
|
// 窗口就绪后检查更新
|
|||
|
|
const { getMainWindow } = require('ee-core/electron');
|
|||
|
|
const win = getMainWindow();
|
|||
|
|
|
|||
|
|
autoUpdater.checkForUpdates();
|
|||
|
|
|
|||
|
|
autoUpdater.on('update-available', () => {
|
|||
|
|
win.webContents.send('update-available');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
logger.info('[lifecycle] update check started');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 示例 3:托盘图标
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// electron/preload/lifecycle.js
|
|||
|
|
const { Tray, Menu } = require('electron');
|
|||
|
|
let tray;
|
|||
|
|
|
|||
|
|
class Lifecycle {
|
|||
|
|
async electronAppReady() {
|
|||
|
|
// 在 Electron 就绪后创建托盘
|
|||
|
|
const path = require('path');
|
|||
|
|
const iconPath = path.join(__dirname, '../../public/images/tray-icon.png');
|
|||
|
|
|
|||
|
|
tray = new Tray(iconPath);
|
|||
|
|
const contextMenu = Menu.buildFromTemplate([
|
|||
|
|
{ label: '打开主窗口', click: () => {
|
|||
|
|
const { getMainWindow } = require('ee-core/electron');
|
|||
|
|
getMainWindow().show();
|
|||
|
|
}},
|
|||
|
|
{ label: '退出', click: () => {
|
|||
|
|
const { app } = require('electron');
|
|||
|
|
app.quit();
|
|||
|
|
}}
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
tray.setContextMenu(contextMenu);
|
|||
|
|
logger.info('[lifecycle] tray created');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 示例 4:无白屏启动
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// electron/config/config.default.js
|
|||
|
|
windowsOption: {
|
|||
|
|
width: 1200,
|
|||
|
|
height: 800,
|
|||
|
|
show: false, // 关键配置:初始不显示
|
|||
|
|
backgroundColor: '#ffffff'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// electron/preload/lifecycle.js
|
|||
|
|
class Lifecycle {
|
|||
|
|
async windowReady() {
|
|||
|
|
// 页面加载完成后再显示,避免白屏
|
|||
|
|
const { getMainWindow } = require('ee-core/electron');
|
|||
|
|
const win = getMainWindow();
|
|||
|
|
|
|||
|
|
win.once('ready-to-show', () => {
|
|||
|
|
win.show();
|
|||
|
|
win.focus();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 生命周期执行顺序总结
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. preload() # 预加载模块
|
|||
|
|
2. new ElectronEgg() # 创建应用实例
|
|||
|
|
3. ready() # 核心框架就绪
|
|||
|
|
4. electronAppReady() # Electron 就绪
|
|||
|
|
5. [创建主窗口] # 框架内部创建窗口
|
|||
|
|
6. windowReady() # 窗口就绪
|
|||
|
|
7. [应用运行中] # 正常运行状态
|
|||
|
|
8. beforeClose() # 关闭前钩子
|
|||
|
|
9. [应用退出] # 进程结束
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 注意事项
|
|||
|
|
|
|||
|
|
1. **异步支持**:所有生命周期钩子都支持 `async/await`,可以执行异步操作
|
|||
|
|
|
|||
|
|
2. **错误处理**:建议在每个钩子中添加 try-catch 错误处理
|
|||
|
|
|
|||
|
|
3. **顺序依赖**:不要在早期钩子中访问尚未初始化的资源(如在 `ready` 中操作窗口)
|
|||
|
|
|
|||
|
|
4. **性能优化**:避免在钩子中执行耗时操作,会阻塞应用启动
|
|||
|
|
|
|||
|
|
5. **资源清理**:在 `beforeClose` 中务必清理所有资源,避免内存泄漏
|
|||
|
|
|
|||
|
|
6. **日志记录**:建议在每个钩子中记录日志,方便排查问题
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 相关文件
|
|||
|
|
|
|||
|
|
- 生命周期注册:[electron/main.js](electron/main.js)
|
|||
|
|
- 生命周期实现:[electron/preload/lifecycle.js](electron/preload/lifecycle.js)
|
|||
|
|
- 预加载模块:[electron/preload/index.js](electron/preload/index.js)
|
|||
|
|
- 窗口配置:[electron/config/config.default.js](electron/config/config.default.js)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 参考资源
|
|||
|
|
|
|||
|
|
- ElectronEgg 官方文档:https://www.kaka996.com/pages/987b1c/
|
|||
|
|
- Electron 官方文档:https://www.electronjs.org/zh/docs/latest/
|