import 'dart:async'; import 'dart:convert'; import 'dart:io'; /// 单实例 + 唤醒端口:应用只允许一个常驻实例。 /// /// zaxprint:// 唤起或用户再次双击图标时,OS 会启动第二个进程——第二实例连上 /// 唤醒端口发送 wake 后立即退出,常驻实例收到后把主窗口带到前台。 /// (比 app_links 的方案更跨平台一致:mac/win 都只依赖回环 socket。) class SingleInstance { static const int wakePort = 38210; /// 尝试成为主实例。 /// /// 返回 true = 本实例是主实例(已开始监听唤醒,[onWake] 在收到唤醒时回调); /// 返回 false = 已有常驻实例(已通知其显示窗口),调用方应当退出进程。 static Future acquire({void Function()? onWake}) async { try { final server = await ServerSocket.bind(InternetAddress.loopbackIPv4, wakePort); server.listen((socket) { socket .cast>() .transform(utf8.decoder) .listen((message) { if (message.contains('wake')) { onWake?.call(); } }, onDone: () { socket.destroy(); }); }, onError: (_) {}); return true; } on SocketException { // 端口被占:已有常驻实例,通知它显示窗口 try { final socket = await Socket.connect( InternetAddress.loopbackIPv4, wakePort, timeout: const Duration(seconds: 2), ); socket.add(utf8.encode('wake')); await socket.flush(); await socket.close(); } catch (_) { // 唤醒端口被非本应用进程占用等异常:放弃唤醒,按第二实例退出 } return false; } } }