Flutter Windows应用使用uni_links_desktop时多实例重复启动问题求助
Flutter Windows 单实例与自定义协议 URL 处理解决方案
针对你遇到的「点击自定义协议链接重复创建应用实例、仅能读取初始 URL」问题,以下是针对性的修复步骤(注意你之前修改AndroidManifest.xml的操作对Windows平台无效,无需再关注):
一、修复Windows端单实例检测逻辑
修改windows/runner/main.cpp,在应用启动时检测已有实例,并将新URL传递给现有实例:
#include <windows.h> #include <shellapi.h> int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t* cmd_line, _In_ int show_cmd) { // 替换为你应用的实际窗口标题(与pubspec.yaml的name字段一致,或你自定义的标题) const wchar_t* APP_TITLE = L"你的应用名称"; HWND existing_hwnd = FindWindow(L"FLUTTER_RUNNER_WIN32_WINDOW", APP_TITLE); if (existing_hwnd != NULL) { // 获取当前命令行中的协议URL int argc; wchar_t** argv = CommandLineToArgvW(GetCommandLineW(), &argc); if (argc > 1) { // 通过WM_COPYDATA传递URL给现有实例 COPYDATASTRUCT cds; cds.dwData = 0; // 自定义标识,用于区分消息类型 cds.cbData = (wcslen(argv[1]) + 1) * sizeof(wchar_t); cds.lpData = argv[1]; SendMessage(existing_hwnd, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&cds); } // 激活并前置现有窗口 ShowWindow(existing_hwnd, SW_SHOWNORMAL); SetForegroundWindow(existing_hwnd); LocalFree(argv); return 0; } // 保留原有Flutter初始化代码 Win32Window window; window.SetTitle(APP_TITLE); if (!window.CreateAndShow(APP_TITLE, kFlutterWindowOrigin, kFlutterWindowSize)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); FlutterEngine engine; FlutterEngineArgs engine_args{}; engine_args.swapchain_format = window.GetSwapChainFormat(); if (!engine.Initialize(window.GetHandle(), window.GetSize(), engine_args)) { return EXIT_FAILURE; } window.SetFlutterEngine(&engine); ::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } engine.Shutdown(); return EXIT_SUCCESS; }
二、处理现有实例的URL接收
修改windows/runner/win32_window.cpp,在窗口消息处理中添加WM_COPYDATA的处理逻辑,将URL传递给Flutter端:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { switch (msg) { // 新增WM_COPYDATA消息处理 case WM_COPYDATA: { COPYDATASTRUCT* cds = reinterpret_cast<COPYDATASTRUCT*>(lparam); if (cds != nullptr && cds->dwData == 0) { wchar_t* url_str = reinterpret_cast<wchar_t*>(cds->lpData); std::wstring wide_url(url_str); std::string narrow_url(wide_url.begin(), wide_url.end()); // 通过BasicMessageChannel将URL发送给Flutter if (window_->flutter_controller()) { window_->flutter_controller()->engine()->SendChannelMessage( "app/deep_link", std::make_unique<std::vector<uint8_t>>( narrow_url.begin(), narrow_url.end())); } } return TRUE; } // 保留原有消息处理逻辑 case WM_DESTROY: ::PostQuitMessage(0); return 0; default: return ::DefWindowProc(hwnd, msg, wparam, lparam); } }
三、Flutter端监听后续URL
在main.dart中,除了初始URL监听,还要通过消息通道接收Windows端传递的后续URL:
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:uni_links_desktop/uni_links_desktop.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // 处理初始启动URL final initialUri = await getInitialUri(); if (initialUri != null) { _handleDeepLink(initialUri); } // 监听Windows端传递的后续URL final BasicMessageChannel<String> _deepLinkChannel = BasicMessageChannel('app/deep_link', StringCodec()); _deepLinkChannel.setMessageHandler((String? url) async { if (url != null) { final uri = Uri.parse(url); _handleDeepLink(uri); } return null; }); runApp(const MyApp()); } // 统一处理URL逻辑 void _handleDeepLink(Uri uri) { // 这里编写你的URL业务逻辑,比如导航到对应页面 print('收到链接:$uri'); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData(primarySwatch: Colors.blue), home: const Scaffold(body: Center(child: Text('Deep Link Demo'))), ); } }
四、常见问题排查
- 窗口标题不匹配:
FindWindow的第二个参数必须与你应用的实际窗口标题完全一致(包括大小写、空格),可在main.cpp的window.SetTitle(APP_TITLE)处确认标题值。 - 协议注册错误:确保Windows自定义协议注册正确,可检查
windows/runner/Runner.rc中的协议配置,或在打包后确认系统协议关联指向你的应用。
内容的提问来源于stack exchange,提问作者MarvinKweyu
相关产品推荐
相关产品推荐

