在桌面應用程式開發中,防止使用者重複開啟多個應用程式實例是常見需求。Electron 提供了 app.requestSingleInstanceLock() API 來實現「單一實例」機制。
為何需要單一實例限制
若允許多個 Electron 視窗同時執行,可能造成:
- 多個視窗顯示相同內容,使用者困惑
- 系統資源重複佔用
- 應用程式狀態不一致(如資料庫連線衝突)
- 系統常駐程式出現多個 tray icon
requestSingleInstanceLock() 運作機制
app.requestSingleInstanceLock() 使用作業系統的鎖定機制(Windows 使用 Named Mutex,Linux/macOS 使用 Unix socket):
- 第一個實例:取得鎖定成功,
requestSingleInstanceLock() 回傳 true,正常啟動
- 第二個實例:鎖定已被第一個實例持有,回傳
false,應立即退出
- 同時,Electron 自動觸發第一個實例的
second-instance 事件,通知它有新實例嘗試啟動
現代 Electron 實作(Electron 4+)
app.makeSingleInstance() 已在 Electron 4 中棄用,改用 requestSingleInstanceLock():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
const { app, BrowserWindow } = require('electron');
let mainWindow = null;
// 嘗試取得單一實例鎖定
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
// 已有實例在執行,直接退出
app.quit();
} else {
// 監聽第二個實例嘗試啟動的事件
app.on('second-instance', (event, commandLine, workingDirectory) => {
// 有人嘗試啟動第二個實例,將焦點移到現有視窗
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore(); // 從最小化恢復
}
if (!mainWindow.isVisible()) {
mainWindow.show(); // 顯示隱藏的視窗(如 System Tray 情況)
}
mainWindow.focus(); // 移動焦點到視窗
}
});
app.whenReady().then(() => {
mainWindow = new BrowserWindow({
width: 1024,
height: 768,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
mainWindow.loadFile('index.html');
});
}
|
傳遞啟動參數給第一個實例
second-instance 事件的回呼函式可以接收第二個實例的命令列參數,實現參數傳遞:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
app.on('second-instance', (event, commandLine, workingDirectory) => {
// commandLine:第二個實例的完整命令列參數陣列
// workingDirectory:第二個實例的工作目錄
console.log('第二個實例啟動參數:', commandLine);
// 例如:若第二個實例用 --open-file=/path/to/file 啟動
// 可以在第一個實例中開啟該檔案
const fileArg = commandLine.find(arg => arg.startsWith('--open-file='));
if (fileArg) {
const filePath = fileArg.replace('--open-file=', '');
mainWindow.webContents.send('open-file', filePath);
}
// 將焦點移到現有視窗
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
|
舊版寫法(Electron 4 以前,已棄用)
1
2
3
4
5
6
7
8
9
10
11
12
|
// ⚠️ 此 API 已棄用,僅供參考
const isSecondInstance = app.makeSingleInstance((commandLine, workingDirectory) => {
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.show();
mainWindow.focus();
}
});
if (isSecondInstance) {
app.quit();
}
|
與 System Tray 搭配使用
當應用程式縮小到系統匣(System Tray)時,單一實例機制特別重要:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
app.on('second-instance', () => {
if (mainWindow) {
// 若視窗被隱藏(縮到 Tray),重新顯示
mainWindow.show();
mainWindow.focus();
}
});
// 視窗關閉時縮到 Tray 而非退出
mainWindow.on('close', (event) => {
if (!app.isQuitting) {
event.preventDefault();
mainWindow.hide(); // 隱藏而非關閉
}
});
|
參考資料