Electron 应用多开 indexedDB 打开异常问题分析与解决

4,043次阅读
2 条评论

共计 3537 个字符,预计需要花费 9 分钟才能阅读完成。

提醒:本文最后更新于2025-07-07 14:38,文中所关联的信息可能已发生改变,请知悉!

electron 中的应用实例多开,会因为 session 共享而存在 indexedDB 多次打开异常。大致会遇到类似如下报错:

DOMException: Internal error opening backing store for indexedDB.open.

当应用中使用了 indexedDB,这是个必然会面对和需要解决的问题。

  • 如果没有必要,可以在启动时检测和禁止应用多开。
  • 若确有应用多开需求,可通过多开检测和设置独立的 session 的方式实现资源隔离。

1. 单例模式:禁止 electron 应用多开

大多数情况下,通过创建多窗口的方式即可满足大多数应用模拟多开的需求。这也是 electron 官方推荐的方式,并且给出了保持应用单例模式的方案实例。示例:

improt { app } from 'electron';
let myWindow = null;
// 请求获取实例锁,若成功则返回 true,否则表示已存在打开的应用实例
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// 当运行第二个实例时退出它,并聚焦到 myWindow 窗口
if (myWindow) {
if (myWindow.isMinimized()) myWindow.restore();
myWindow.focus();
}
})
app.whenReady().then(() => {
myWindow = createWindow()
})
}

相关 API 有:

2. 使用独立 session 允许 electron 应用多开

在我们的实际应用中,允许用户开启多个客户端登陆不同的账户,indexedDB 用于缓存大量的基础数据以降低内存占用减轻 GC 压力。
这种情况下缓存数据持久化不是必须的。需本地持久化的数据并不会太多,可以通过为不同用户创建不同的本地文件加密存储方式实现。

electron 在创建 BrowserWindow 时,可通过设置 session 或 partition 参数以使用不同的 session。session 的优先级高于 partition,但 partition 指定字符串的方式更为简洁。当 partition 的值以 persist: 开头时,创建的 session 会持久化存储在 app.getPath('userData')/Partitions 目录下,否则则只创建于内存当中。

需要注意的一点是,partition 相同的 BrowserWindow 才可以共享 session(各种存储、MessageChannel 等才可以互相共享和通信),所以在创建多个不同的窗口时,若相互之间需要 session 共享,则需要设置为相同的参数值。

于是我们可以通过如下方式实现 electron 应用多开的基本方案:

  • 检测是否存在应用多开
  • 如果为多开,则创建窗体时,设置不同的 partition 值,以使用独立的 session

2.1 检测应用是否多开检测的方法

在 windows 下使用 wmic 命令、Linux 与 Mac OS 下使用 ps 命令,可以获取系统中所有运行应用信息。通过应用进程的 pid 与 ppid 信息可以识别是否存在应用多开。示例:

/**
* 获取应用全部的 pid 与 ppid 与 pid 对应列表
*/
function getAppPids(appExecName?: string) {
const pidToPpid = {} as { [pid: number]: number };
try {
if (!appExecName) appExecName = path.basename(process.execPath);
const isWin = process.platform === 'win32';
const cmd = isWin ? `wmic process where name="${appExecName}" GET processId,parentProcessId` : `ps -ef`;
const str = execSync(cmd, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 }).trim();
if (isWin) {
str.split('\n')
.map(line => line.trim().split(/\s+/))
.slice(1)
.forEach(line => (pidToPpid[+line[1]] = +line[0]));
} else {
str.split('\n')
.filter(line => line.includes(appExecName))
.map(line => line.trim().split(/\s+/))
.forEach(line => (pidToPpid[+line[1]] = +line[0]));
}
} catch (err) {
logger.info('[process][getAppPids][error]', appExecName, err.message);
}
return pidToPpid;
},
/**
* 当前是否为应用多开
*
* @param {boolean} [isPrintDetail=true] 是否打印所有进程详情
*/
function isMultiOpen() {
const appName = path.basename(process.execPath).toLowerCase();
const pids = this.getAppPids(appName);
const app = electron.app || electron.remote.app;
const appMetrics = app.getAppMetrics().map(d => [d.pid, d.type] as const);
const isMultiOpen = Object.keys(pids).length > appMetrics.length;
const info = { appName, isMultiOpen, pids, appMetrics };
logger.info('[process]isMultiOpen', info);
return info;
}

由于 electron 本身提供了 app.requestSingleInstanceLock API,也可以简单的使用它来判别本次启动是否为多开:

function isMultiOpen() {
return !app.requestSingleInstanceLock();
}

2.2 为多开应用设置独立的 session

如果存于 indexedDB 中的数据需要持久化以便优化二次启动数据加载性能,可以为每个实例指定独立的持久化 session,否则可以创建为内存中的临时 session。主要逻辑示例:

const options: Electron.BrowserWindowConstructorOptions = {
useContentSize: true,
webPreferences: {
nodeIntegration: true,
devTools: IS_DEV,
nodeIntegrationInWorker: true,
webviewTag: true,
},
};
/** 获取可复用的 partition ID */
function getPersisitId() {}
if (isMultiOpen) {
// session 持久化,主要注意可复用逻辑、避免随机创建过多持久化数据导致磁盘占用膨胀
opts.webPreferences.partition = `persist:part${getPersisitId()}`;
// 创建为内存中的临时 session
// opts.webPreferences.partition = `persist_${process.pid}`;
}
const mainWindow = new BrowserWindow(options);

注意创建多个 BrowserWindow 时 partition 参数值应相同。

3. 相关参考

  • https://github.com/electron/electron/issues/10792
  • https://www.electronjs.org/zh/docs/latest/api/browser-window#new-browserwindowoptions

正文完
 0
任侠
版权声明:本站原创文章,由 任侠 于2021-12-25发表,共计3537字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(2 条评论)
验证码
gavin 评论达人 LV.1
2024-01-17 20:23:26 回复
Google Chrome 119.0.0.0 Google Chrome 119.0.0.0 Windows 10 x64 Edition Windows 10 x64 Edition

你好 获取可复用的 partition ID 这个有什么具体思路吗 假如我想根据登陆后的用户id设置partition 大拿不吝赐教!!

 Windows  Chrome  中国福建省厦门市电信
    2024-01-18 18:51:55 回复
    Microsoft Edge 120.0.0.0 Microsoft Edge 120.0.0.0 Windows 10 x64 Edition Windows 10 x64 Edition

    @gavin 

    文章中的示例代码基本包含了全部主要内容,具体怎么设计取决于你项目具体的需求了
    如果你想创建系统级的分离,可以在拿到用户 id 后为其创建独立的窗体,窗体创建过程中按用户 id 设置 partition id 的取值即可

     Windows  Edge  中国广东省广州市电信