Electron/React应用崩溃重启后显示模板窗口问题排查
我使用React和Electron开发了一款应用,目前遇到如下问题:应用启动时偶尔会因IPC通信失败触发未捕获异常。我按照文档在Electron入口文件中添加了异常处理器,但重启后并未加载我的应用,而是显示Electron模板窗口。
异常处理器代码:
process.on("uncaughtException", function (err) { //log the message and stack trace log.warn("uncaught Exception: ", err); //relaunch the app app.relaunch({ args: [] }); app.exit(0); handleLaunch(tray, window, store, __dirname); });
handleLaunch函数代码:
// Library Imports const { app, BrowserWindow, dialog } = require("electron"); const { autoUpdater } = require("electron-updater"); const log = require("electron-log"); //Functions const { createIpcBusBridge } = require("../ipc/createIpcBusBridge"); const { createWindow } = require("../window/createWindow"); const { createTray } = require("../tray/createTray"); const handleLaunch = (tray, window, store, directoryName) => { const isDev = !app.isPackaged; if (isDev === true) { const reactDevToolsId = "fmkadmapgofadopljbjfkapdkoienihi"; const { default: installExtension, REDUX_DEVTOOLS, } = require("electron-devtools-installer"); installExtension([REDUX_DEVTOOLS, reactDevToolsId]) .then((name) => console.log(`Added Extension: ${name}`)) .catch((err) => console.log("An error occurred: ", err)); } log.info("CREATING WINDOW..........."); window = createWindow(directoryName, process, store, isDev); if (tray !== undefined) { tray.destroy(); } tray = createTray(window, directoryName, process); createIpcBusBridge(); app.on("activate", function () { /* On macOS it's common to re-create a window in the app when the dock icon is clicked and there are no other windows open. */ if (BrowserWindow.getAllWindows().length === 0) createWindow(window, tray, directoryName, process, store); }); if (isDev === false) { autoUpdater.on("update-available", (_event, releaseNotes, releaseName) => { const dialogOpts = { type: "info", buttons: ["Ok"], title: "Application Update", message: process.platform === "win32" ? releaseNotes : releaseName, detail: "A new version is being downloaded.", }; dialog.showMessageBox(dialogOpts, (response) => {}); }); autoUpdater.on("update-downloaded", (_event, releaseNotes, releaseName) => { const dialogOpts = { type: "info", buttons: ["Restart", "Later"], title: "Application Update", message: process.platform === "win32" ? releaseNotes : releaseName, detail: "A new version has been downloaded. Restart the application to apply the updates.", }; dialog.showMessageBox(dialogOpts).then((returnValue) => { if (returnValue.response === 0) autoUpdater.quitAndInstall(); }); }); } }; module.exports = { handleLaunch };
createWindow函数代码:
// Library Imports const { BrowserWindow, ipcMain } = require("electron"); const path = require("path"); const url = require("url"); const log = require("electron-log"); // Functions const { handleResolutionPref } = require("./handleResolutionPref"); const { handlePositionPref } = require("./handlePositionPref"); const { maximizeWindow } = require("./maximizeWindow"); const { minimizeWindow } = require("./minimizeWindow"); const { closeWindow } = require("./closeWindow"); const createWindow = (directoryName, process, store, isDev) => { try { const [screenWidth, screenHeight] = handleResolutionPref(store); const [screenXCoordinate, screenYCoordinate, isScreenPositionCustom] = handlePositionPref(store); let window = null; const browserWindowOptions = { width: screenWidth, height: screenHeight, frame: false, fullscreenable: true, resizable: true, transparent: false, webPreferences: { preload: path.join(directoryName, "preload.bundle.js"), contextIsolation: false, nodeIntegration: true, sandbox: false, webSecurity: false, }, icon: path.join(directoryName, "icon.ico"), }; // Create the browser window. if (isScreenPositionCustom === true) { browserWindowOptions.x = screenXCoordinate; browserWindowOptions.y = screenYCoordinate; window = new BrowserWindow(browserWindowOptions); } else { window = new BrowserWindow(browserWindowOptions); } window.on("closed", () => (window = null)); if (isDev === true) { window.loadURL("http://localhost:3000"); } else { window.loadURL( url.format({ pathname: path.join(directoryName, "index.html"), protocol: "file:", slashes: true, }) ); } // Handle window toggling for custom titlebar ipcMain.on("windowMinimize", () => minimizeWindow(window)); ipcMain.on("windowMaximize", () => maximizeWindow(window)); ipcMain.on("windowClose", () => closeWindow(window)); // Open the DevTools. // window.webContents.openDevTools() return window; } catch (error) { log.info("CREATE WINDOW ERROR INSIDE FUNCTION: ", error); } }; module.exports = { createWindow };
app.exit(0)终止进程后后续代码无法执行
异常处理器中调用app.exit(0)会立即终止当前进程,后面的handleLaunch(tray, window, store, __dirname)完全不会运行。而app.relaunch()只是调度新进程启动,要等当前进程退出后才会执行,新进程会从头执行入口文件的启动流程,不会在当前进程里重新调用handleLaunch。activate事件中createWindow参数顺序错误
在handleLaunch的app.on("activate")回调里,调用createWindow时传参顺序是createWindow(window, tray, directoryName, process, store),但createWindow的定义是(directoryName, process, store, isDev),参数顺序完全错位,导致createWindow内逻辑出错(比如store参数拿到的是isDev的值),窗口初始化失败后就会显示Electron默认模板窗口。IPC事件重复绑定引发异常
每次调用createWindow都会重新绑定ipcMain.on("windowMinimize")等事件,多次绑定同一事件会导致触发时执行多个处理函数,可能引发IPC相关异常,尤其是重启或多次创建窗口时。createWindow错误处理不完整createWindow的try/catch捕获错误后仅打印日志,返回undefined,后续tray = createTray(window, ...)会因window为undefined出错,导致应用初始化失败。
1. 修正异常处理器逻辑
移除app.exit(0)后的handleLaunch调用,同时传递原进程参数给app.relaunch,确保新进程保留启动参数:
process.on("uncaughtException", function (err) { log.warn("uncaught Exception: ", err); // 传递原进程的参数(排除第一个执行路径) app.relaunch({ args: process.argv.slice(1) }); app.exit(0); });
2. 修正activate事件的createWindow传参
按照createWindow定义的参数顺序传递,保证参数正确:
app.on("activate", function () { if (BrowserWindow.getAllWindows().length === 0) createWindow(directoryName, process, store, isDev); });
3. 避免IPC事件重复绑定
将IPC事件绑定移到入口文件(如main.js)中,只初始化一次,通过获取当前聚焦窗口来处理操作,避免绑定到特定窗口实例:
// 入口文件中全局绑定一次IPC事件 const { ipcMain, BrowserWindow } = require('electron'); const { minimizeWindow, maximizeWindow, closeWindow } = require('./window/...'); ipcMain.on("windowMinimize", () => { const activeWindow = BrowserWindow.getFocusedWindow(); if (activeWindow) minimizeWindow(activeWindow); }); ipcMain.on("windowMaximize", () => { const activeWindow = BrowserWindow.getFocusedWindow(); if (activeWindow) maximizeWindow(activeWindow); }); ipcMain.on("windowClose", () => { const activeWindow = BrowserWindow.getFocusedWindow(); if (activeWindow) closeWindow(activeWindow); });
然后删除createWindow内的IPC绑定代码。
4. 完善createWindow错误处理
捕获错误后抛出异常,让上层处理,避免返回undefined导致后续流程出错:
const createWindow = (directoryName, process, store, isDev) => { try { // ... 原有逻辑 return window; } catch (error) { log.error("CREATE WINDOW ERROR INSIDE FUNCTION: ", error); // 抛出错误,让调用者处理 throw error; } };
内容的提问来源于stack exchange,提问作者Frank Edwards

