You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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'))),
    );
  }
}

四、常见问题排查

  1. 窗口标题不匹配:FindWindow的第二个参数必须与你应用的实际窗口标题完全一致(包括大小写、空格),可在main.cpp的window.SetTitle(APP_TITLE)处确认标题值。
  2. 协议注册错误:确保Windows自定义协议注册正确,可检查windows/runner/Runner.rc中的协议配置,或在打包后确认系统协议关联指向你的应用。

内容的提问来源于stack exchange,提问作者MarvinKweyu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 13:05:37