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/
|