Files
pqs-9100_client/doc/生命周期描述.md

460 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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