fix(client): 单实例锁改用 TCP loopback socket

文件锁在 Windows 上行为不一致,改用绑定本地端口(54317)作为互斥锁,
进程退出时 OS 自动释放,Windows/macOS 均可靠生效。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-15 12:09:34 +08:00
parent 0779bf57e3
commit a8c684ad0c
+11 -9
View File
@@ -1,11 +1,13 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
// Keep the RAF open so the OS lock stays alive for the lifetime of the process. // Keep the server socket alive for the lifetime of the process.
// ignore: unused_element // The OS releases the port automatically when the process exits.
RandomAccessFile? _raf; ServerSocket? _server;
// Port unique to this app; chosen from the private range 4915265535.
const _kLockPort = 54317;
/// Returns true if this process successfully acquired the single-instance lock. /// Returns true if this process successfully acquired the single-instance lock.
/// Returns false if another instance is already running. /// Returns false if another instance is already running.
@@ -15,11 +17,11 @@ Future<bool> acquire() async {
if (!Platform.isWindows && !Platform.isMacOS) return true; if (!Platform.isWindows && !Platform.isMacOS) return true;
try { try {
final dir = await getApplicationSupportDirectory(); _server = await ServerSocket.bind(
final lockFile = File('${dir.path}/jiu.lock'); InternetAddress.loopbackIPv4,
final raf = await lockFile.open(mode: FileMode.write); _kLockPort,
await raf.lock(FileLock.exclusive); shared: false,
_raf = raf; );
return true; return true;
} catch (_) { } catch (_) {
return false; return false;