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

Flutter iOS端Universal Links在APP终止时无法跳转指定页面

iOS冷启动时Universal Links无法触发GoRouter路由的解决方案

1. 处理iOS原生层的Universal Links回调

APP处于终止状态时,iOS会通过AppDelegate的application(_:continue:restorationHandler:)方法传递Universal Links的URL,需要在这里把URL传递给Flutter端的GoRouter:

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        continue userActivity: NSUserActivity,
        restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
    ) -> Bool {
        if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
           let url = userActivity.webpageURL {
            let channel = FlutterMethodChannel(name: "universal_links", binaryMessenger: window?.rootViewController as! FlutterBinaryMessenger)
            channel.invokeMethod("handleLink", arguments: url.absoluteString)
            return true
        }
        return super.application(application, continue: userActivity, restorationHandler: restorationHandler)
    }
}

2. Flutter端接收URL并配置GoRouter初始路由

在main函数中先监听原生传递的URL,初始化GoRouter时动态设置initialLocation:

import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  String? initialLocation;
  const channel = MethodChannel('universal_links');
  
  // 监听冷启动后传递的链接
  channel.setMethodCallHandler((call) async {
    if (call.method == 'handleLink') {
      final url = call.arguments as String;
      final uri = Uri.parse(url);
      initialLocation = uri.path + (uri.hasQuery ? '?${uri.query}' : '');
      // 若APP已初始化完成,直接跳转目标路由
      if (WidgetsBinding.instance.hasScheduledFrame) {
        GoRouter.of(WidgetsBinding.instance.context).go(initialLocation!);
      }
    }
  });
  
  // 获取冷启动时的初始链接
  try {
    final url = await channel.invokeMethod<String>('getInitialLink');
    if (url != null) {
      final uri = Uri.parse(url);
      initialLocation = uri.path + (uri.hasQuery ? '?${uri.query}' : '');
    }
  } catch (e) {
    print('获取初始链接失败: $e');
  }

  runApp(MyApp(initialLocation: initialLocation));
}

class MyApp extends StatelessWidget {
  final String? initialLocation;

  const MyApp({super.key, this.initialLocation});

  @override
  Widget build(BuildContext context) {
    final router = GoRouter(
      initialLocation: initialLocation ?? '/',
      routes: [
        // 其他路由配置...
        GoRoute(
          name: RoutesName.resetPassword,
          path: RoutesName.resetPassword, // 对应"/reset-password"
          builder: (context, state) {
            final email = state.uri.queryParameters['email'];
            final token = state.uri.queryParameters['token'];
            return ResetPasswordView(token, email);
          },
        ),
      ],
    );

    return MaterialApp.router(
      routerConfig: router,
    );
  }
}

3. 补充原生层初始链接获取方法

在AppDelegate中添加获取初始链接的逻辑,并注册到MethodChannel:

extension AppDelegate {
  @objc func getInitialLink(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    if let userActivity = UIApplication.shared.delegate?.application?.userActivities.first(where: { $0.activityType == NSUserActivityTypeBrowsingWeb }),
       let url = userActivity.webpageURL {
      result(url.absoluteString)
    } else {
      result(nil)
    }
  }
}

在application(_:didFinishLaunchingWithOptions:)中注册该方法:

override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
  let channel = FlutterMethodChannel(name: "universal_links", binaryMessenger: controller.binaryMessenger)
  channel.setMethodCallHandler { [weak self] call, result in
    if call.method == "getInitialLink" {
      self?.getInitialLink(call, result: result)
    } else {
      result(FlutterMethodNotImplemented)
    }
  }
  GeneratedPluginRegistrant.register(with: self)
  return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

4. 验证Universal Links配置

确保apple-app-site-association文件包含正确的路径匹配规则:

{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "你的TeamID.你的BundleID",
        "paths": ["/reset-password*"]
      }
    ]
  }
}

测试注意事项

  • 必须完全终止APP(后台划掉)后测试,后台状态的逻辑不同
  • 使用真机测试,模拟器对Universal Links支持有限
  • 确认XCode的Signing & Capabilities中已开启Associated Domains,并添加applinks:你的域名

内容的提问来源于stack exchange,提问作者Dušan Perić

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 15:22:37