Contents

electron不會重覆開啟程式

在桌面應用程式開發中,防止使用者重複開啟多個應用程式實例是常見需求。Electron 提供了 app.requestSingleInstanceLock() API 來實現「單一實例」機制。

為何需要單一實例限制

若允許多個 Electron 視窗同時執行,可能造成:

  • 多個視窗顯示相同內容,使用者困惑
  • 系統資源重複佔用
  • 應用程式狀態不一致(如資料庫連線衝突)
  • 系統常駐程式出現多個 tray icon

requestSingleInstanceLock() 運作機制

app.requestSingleInstanceLock() 使用作業系統的鎖定機制(Windows 使用 Named Mutex,Linux/macOS 使用 Unix socket):

  1. 第一個實例:取得鎖定成功,requestSingleInstanceLock() 回傳 true,正常啟動
  2. 第二個實例:鎖定已被第一個實例持有,回傳 false,應立即退出
  3. 同時,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(); // 隱藏而非關閉
  }
});

參考資料