Quiet
  • 主页
  • 归档
  • 分类
  • 标签
  • 链接
  • 关于我

bajiu

  • 主页
  • 归档
  • 分类
  • 标签
  • 链接
  • 关于我
Quiet主题
  • NodeJs
  • Electron

Electron性能调优

bajiu
前端

2024-08-13 12:03:00

Electron 本质上是一个NodeJs + Chromium 的浏览器应用程序,所以我们在开发的时候,必不可少的也需要考虑到很对对应的性能问题,下面是最近总结的一些经验.

调优主要分为两个方向,一个是渲染进程的方向,一个是主进程方向,其实对于重web的项目来说,更多的是需要拆分看哪些是可以依赖主进程进行一些处理的。

主进程

  1. 接口和数据的缓存,因为浏览器最多并发6个接口请求,这样可以在node层将一部分数据缓存下来整好了再发给前端,比如一下子加载150多个dicom序列的这种情况,或者在前端自行304一下子,在这里墙裂推荐使用RxJs,sqlLite的话酌情考虑,我感觉对于前端来说一般用Json文件做临时缓存的IO足够了。
  2. NodeJs 进程一定不能崩溃,debug时开启--inspect查看日志。
  3. 不确定的地方使用try-catch,跟实际崩溃相比之下,其实没多少性能消耗,但要记得在catch里大大方方的写好日志。
  4. 使用Electron提供的错误处理API,Electron Crash Reporter 支持将崩溃堆栈上传到在线的第三方服务平台

此时渲染进程可能已经崩溃,要记录崩溃日志之后再崩溃或者重启

app.on('gpu-process-crashed', (event, kill) => {
  console.warn('app:gpu-process-crashed', event, kill);
});
 
app.on('renderer-process-crashed', (event, webContents, kill) => {
  console.warn('app:renderer-process-crashed', event, webContents, kill);
});
 
app.on('render-process-gone', (event, webContents, details) => {
  console.warn('app:render-process-gone', event, webContents, details);
});
 
app.on('child-process-gone', (event, details) => {
  console.warn('app:child-process-gone', event, details);
});
  1. IPC这里要是多个窗口大数据传输也非常耗内存,当时具体数据找不着了反正非常耗内存,一般命令直接使用IPC,大数据的话还是各自读比较好。

渲染进程

  1. 在web端代理console.error()方法,以及将Performance信息定期传给主进行做LOG。
  2. 上面说NodeJs进程不能崩溃,渲染进程也同理,这玩意一崩了整个应用程序也崩了
  3. 黑屏和白屏问题
    • 显存崩了倒还好,黑一下又回来了至少我们能知道是显存崩了去进行对应的debug
    • 白屏我们可以根据对应API来进行一下监听和找到原因,并且让软件自动重启(虽然这也很影响用户体验)

写的有点儿乱,遇到了再加

其他

  1. 如果环境允许的化尽量还是搞搞CI/CD,加上自动化测试其实非常适合发现问题。

多个子窗口调优

这个暂时没遇到过,不过正常作为一个桌面端这确实要考虑这方面调优的

一个多窗口管理:

const { app, BrowserWindow, ipcMain } = require('electron')
const { join } = require('path')

process.env.ROOT = join(__dirname, '../../')

const isDevelopment = process.env.NODE_ENV == 'development'
// const winURL = isDevelopment ? 'http://localhost:3000/' : join(__dirname, 'dist/index.html')
const winURL = isDevelopment ? process.env.VITE_DEV_SERVER_URL : join(process.env.ROOT, 'dist/index.html')

// 配置参数
const defaultConfig = {
    id: null,               // 窗口唯一id
    background: '#fff',     // 背景色
    route: '',              // 路由地址url
    title: '',              // 标题
    data: null,             // 传入数据参数
    width: '',              // 窗口宽度
    height: '',             // 窗口高度
    minWidth: '',           // 窗口最小宽度
    minHeight: '',          // 窗口最小高度
    x: '',                  // 窗口相对于屏幕左侧坐标
    y: '',                  // 窗口相对于屏幕顶端坐标
    resize: true,           // 是否支持缩放
    maximize: false,        // 最大化窗口
    isMultiWin: false,      // 是否支持多开窗口
    isMainWin: false,       // 是否主窗口
    parent: '',             // 父窗口(需传入父窗口id)
    modal: false,           // 模态窗口(模态窗口是浮于父窗口上,禁用父窗口)
    alwaysOnTop: false      // 置顶窗口
}

class MultiWindows {
    constructor() {
        // 主窗口
        this.mainWin = null
        // 窗口组
        this.winLs = {}

        // ...
    }

    winOpts() {
        return {
            // 窗口图标
            icon: join(process.env.ROOT, 'resource/shortcut.ico'),
            backgroundColor: '#fff',
            autoHideMenuBar: true,
            titleBarStyle: 'hidden',
            width: 1000,
            height: 640,
            resizable: true,
            minimizable: true,
            maximizable: true,
            frame: false,
            show: false,
            webPreferences: {
                contextIsolation: true, // 启用上下文隔离(为了安全性)(默认true)
                // nodeIntegration: false, // 启用Node集成(默认false)
                preload: join(process.env.ROOT, 'resource/preload.js'),
                // devTools: true,
                // webSecurity: false
            }
        }
    }

    // 创建新窗口
    createWin(options) {
        const args = Object.assign({}, defaultConfig, options)
        console.log(args)

        // 判断窗口是否存在
        for(let i in this.winLs) {
            if(this.getWin(i) && this.winLs[i].route === args.route && !this.winLs[i].isMultiWin) {
                this.getWin(i).focus()
                return
            }
        }

        let opt = this.winOpts()
        if(args.parent) {
            opt.parent = this.getWin(args.parent)
        }

        if(typeof args.modal === 'boolean') opt.modal = args.modal
        if(typeof args.resize === 'boolean') opt.resizable = args.resize
        if(typeof args.alwaysOnTop === 'boolean') opt.alwaysOnTop = args.alwaysOnTop
        if(args.background) opt.backgroundColor = args.background
        if(args.width) opt.width = args.width
        if(args.height) opt.height = args.height
        if(args.minWidth) opt.minWidth = args.minWidth
        if(args.minHeight) opt.minHeight = args.minHeight
        if(args.x) opt.x = args.x
        if(args.y) opt.y = args.y

        console.log(opt)

        // 创建窗口对象
        let win = new BrowserWindow(opt)
        // 是否最大化
        if(args.maximize && args.resize) {
            win.maximize()
        }
        this.winLs[win.id] = {
            route: args.route, isMultiWin: args.isMultiWin
        }
        args.id = win.id


        // 加载页面
        let $url
        if(!args.route) {
            if(process.env.VITE_DEV_SERVER_URL) {
                // 打开开发者调试工具
                // win.webContents.openDevTools()
    
                $url = process.env.VITE_DEV_SERVER_URL
            }else {
                $url = winURL
            }
        }else {
            $url = `${winURL}#${args.route}`
        }
        win.loadURL($url)
        /*if(process.env.VITE_DEV_SERVER_URL) {
            win.loadURL($url)
        }else {
            win.loadFile($url)
        }*/
        win.webContents.openDevTools()

        win.once('ready-to-show', () => {
            win.show()
        })

        win.on('close', () => win.setOpacity(0))

        // 初始化渲染进程
        win.webContents.on('did-finish-load', () => {
            // win.webContents.send('win-loaded', '加载完成~!')
            win.webContents.send('win-loaded', args)
        })
    }

    // 获取窗口
    getWin(id) {
        return BrowserWindow.fromId(Number(id))
    }

    // 获取全部窗口
    getAllWin() {
        return BrowserWindow.getAllWindows()
    }

    // 关闭全部窗口
    closeAllWin() {
        try {
            for(let i in this.winLs) {
                if(this.getWin(i)) {
                    this.getWin(i).close()
                }else {
                    app.quit()
                }
            }
        } catch (error) {
            console.log(error)
        }
    }

    // 开启主进程监听
    ipcMainListen() {
        // 设置标题
        ipcMain.on('set-title', (e, data) => {
            const webContents = e.sender
            const wins = BrowserWindow.fromWebContents(webContents)
            wins.setTitle(data)
        })
        // 是否最大化
        ipcMain.handle('isMaximized', (e) => {
            const win = BrowserWindow.getFocusedWindow()
            return win.isMaximized()
        })

        ipcMain.on('min', e => {
            const win = BrowserWindow.getFocusedWindow()
            win.minimize()
        })
        ipcMain.handle('max2min', e => {
            const win = BrowserWindow.getFocusedWindow()
            if(win.isMaximized()) {
                win.unmaximize()
                return false
            }else {
                win.maximize()
                return true
            }
        })
        ipcMain.on('close', (e, data) => {
            // const wins = BrowserWindow.getFocusedWindow()
            // wins.close()
            this.closeAllWin()
        })

        // ...
    }
}

module.exports = MultiWindows

这么用

const createWindow = () => {
    let window = new MultiWindows()

    window.createWin({isMainWin: true})
    window.ipcMainListen()
}

调优主要策略方向:

  1. 减少主进程负载
  2. 使用懒加载和按需加载
  3. 减少资源消耗
  4. 优化代码和依赖
  5. 减少打包大小
  6. 利用硬件加速
  7. 性能监控和调试
  8. 优化网络请求
  9. 其他进程和线程优化
上一篇

RxJs基本概念和使用

下一篇

在Express中使用中间件

©2024 By bajiu.