资讯动态

Unity手游iOS深度链接全链路实战:从URL Scheme到C#参数投递

发布时间:2026/9/28 15:10:22 来源:尧图企业网站定制
1. 为什么手游团队绕不开 Deep Link 这件事做过手游投放或者运营活动的人大概率都碰过这样一个场景用户在微信里点了一条分享链接或者在浏览器里点了一个广告位结果跳进 App Store 装完游戏之后打开游戏却停在了登录页之前那个“邀请码”“房间号”“活动 ID”全丢了。用户一脸懵运营那边数据对不上投放的归因也断了。这个问题的核心就是Deep Link深度链接没有打通。在 Unity 手游 iOS 这套组合里Deep Link 的完整链路其实比很多人想象的要长从系统层的 URL Scheme 或 Universal Links 被触发到 App 冷启动/热启动再到 Unity 引擎初始化最后把参数投递到 C# 业务层中间任何一环断了参数就丢了。我见过不少团队只做了“能唤起 App”这一步就以为万事大吉结果热启动场景、冷启动场景、App 未安装场景全都没覆盖上线后一堆客诉。这篇内容就是把我自己在几个 Unity 手游项目里踩过的坑、验证过的方案整理出来。适合正在做 iOS 端分享拉新、广告归因、活动跳转的 Unity 开发者也适合刚接触 Deep Link 想搞清楚全链路的同学。我会从 URL Scheme 和 Universal Links 的选型讲起一路讲到 C# 层怎么拿到参数、怎么处理冷热启动差异中间穿插具体的代码和排查经验。看完你应该能直接照着搭一套可用的方案。2. URL Scheme 与 Universal Links 的选型逻辑2.1 两种方案的本质区别先说清楚这两个东西到底是什么不然后面全是糊涂账。URL Scheme是 iOS 很早就有的机制形式类似mygame://open?room123。App 在 Info.plist 里注册一个自定义协议头系统识别到这个协议就交给对应 App 处理。它的优点是接入简单、跨 App 调用直接缺点是任何 App 都能注册同名 Scheme存在被劫持的风险而且如果目标 App 没装系统不会给任何反馈用户就卡在那里。Universal Links是 iOS 9 之后推的机制形式是普通的https://链接比如https://game.example.com/open?room123。它依赖你在域名根目录放一个apple-app-site-association简称 AASA文件系统会校验域名和 App 的绑定关系。用户点击这个链接时如果装了 App 就直接进 App没装就跳网页。它的优势是安全、可信、没装也能兜底缺点是配置链路长AASA 文件、Team ID、Bundle ID、域名 HTTPS 证书一个都不能错。我一般给团队的建议是能上 Universal Links 就上 Universal LinksURL Scheme 作为补充。因为现在 iOS 对 Scheme 的弹窗提示越来越严格用户体验也差而 Universal Links 在微信、Safari 里的表现相对可控微信有自己的拦截策略这个后面单独说。2.2 选型时容易忽略的三个点第一个点是冷启动和热启动的处理路径完全不同。冷启动时 App 还没起来系统会把链接信息通过application:didFinishLaunchingWithOptions:的launchOptions传进来热启动时 App 已经在后台走的是application:continueUserActivity:restorationHandler:或application:openURL:options:。这两条路径如果只处理了一条就会出现“有时候能拿到参数有时候拿不到”的诡异现象。第二个点是Universal Links 的首次点击行为。iOS 有个约定俗成的规则如果用户是在 Safari 地址栏直接输入域名或者从同一个域名的网页点击链接系统可能不会唤起 App而是留在网页。这是苹果为了防止滥用做的限制。所以测试的时候一定要用备忘录、信息这类“外部”入口去点不然你会以为配置没生效。第三个点是参数编码。URL 里的参数如果包含中文、特殊符号必须做 URL Encode否则在传递过程中会被截断或乱码。我见过有人直接把房间名带中文拼进链接结果 C# 层拿到的是一堆百分号编码还得手动解码。2.3 配置层面的关键差异对比项URL SchemeUniversal Links配置位置Info.plist 的 CFBundleURLTypes域名 AASA 文件 Xcode Capabilities未安装表现无反馈卡住跳转网页兜底安全性低可被抢注高域名绑定校验微信内表现常被拦截相对可用但需域名白名单调试难度低中依赖缓存和证书这张表建议收藏选型评审的时候直接拿出来对。我个人经验是如果项目只做内部测试或者安卓 iOS 都要快速跑通URL Scheme 先顶上一旦要正式投放Universal Links 必须补齐。3. iOS 原生层的接入与参数捕获3.1 Xcode 工程侧的配置细节Unity 导出的 iOS 工程配置 Deep Link 有两个地方要动。URL Scheme 直接在Info.plist里加keyCFBundleURLTypes/key array dict keyCFBundleURLName/key stringcom.example.mygame/string keyCFBundleURLSchemes/key array stringmygame/string /array /dict /arrayUniversal Links 则要在 Xcode 的 Signing Capabilities 里加 Associated Domains填applinks:game.example.com。注意这里不要带https://只写applinks:加域名。AASA 文件的内容大概长这样{ applinks: { apps: [], details: [ { appID: TEAMID.com.example.mygame, paths: [/open/*, /share/*] } ] } }appID是 Team ID 加 Bundle ID中间用点连接。这个文件必须放在域名根目录通过 HTTPS 可访问Content-Type 是application/json而且不能有重定向。我踩过的坑是CDN 缓存了旧的 AASA 文件改完半天不生效后来加了版本号路径才解决。3.2 原生回调方法的完整实现在 Unity 导出的 Xcode 工程里UnityAppController.mm是入口。我们需要在里面补三个回调。冷启动场景在application:didFinishLaunchingWithOptions:里检查launchOptions- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { NSURL *url launchOptions[UIApplicationLaunchOptionsURLKey]; if (url) { [self handleDeepLink:url.absoluteString]; } NSUserActivity *activity launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey][UIApplicationLaunchOptionsUserActivityTypeIdentifier]; if (activity [activity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { [self handleDeepLink:activity.webpageURL.absoluteString]; } return [super application:application didFinishLaunchingWithOptions:launchOptions]; }热启动场景补两个方法- (BOOL)application:(UIApplication*)app openURL:(NSURL*)url options:(NSDictionaryUIApplicationOpenURLOptionsKey,id*)options { [self handleDeepLink:url.absoluteString]; return YES; } - (BOOL)application:(UIApplication*)application continueUserActivity:(NSUserActivity*)userActivity restorationHandler:(void (^)(NSArrayidUIUserActivityRestoring*))restorationHandler { if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { [self handleDeepLink:userActivity.webpageURL.absoluteString]; } return YES; }这里有个关键点冷启动时 Unity 引擎还没初始化完不能直接调用 C# 方法。我的做法是把 URL 先存到一个静态变量或者NSUserDefaults里等 Unity 发来“我准备好了”的消息再回传。3.3 参数暂存与时机控制为什么不能直接调 C#因为didFinishLaunchingWithOptions执行的时候Unity 的UnitySendMessage目标对象还不存在调了也是石沉大海。我一般这样处理static NSString *pendingDeepLink nil; - (void)handleDeepLink:(NSString*)urlString { if (UnityIsInitialized()) { UnitySendMessage(DeepLinkManager, OnDeepLinkReceived, [urlString UTF8String]); } else { pendingDeepLink [urlString copy]; } }然后在 Unity 侧 C# 的Start或Awake里主动问一次[DllImport(__Internal)] private static extern string GetPendingDeepLink();原生侧实现GetPendingDeepLink返回pendingDeepLink并清空。这样冷启动的参数就不会丢。实测下来这套“暂存 主动拉取”的组合最稳比单纯依赖回调可靠得多。注意UnitySendMessage的字符串参数是const char*中文要确保是 UTF-8 编码否则 C# 侧会乱码。4. Unity C# 层的参数投递与业务分发4.1 桥接层的设计思路C# 层我建议单独做一个DeepLinkManager职责就三件事接收原生传来的原始 URL、解析成结构化参数、分发给业务模块。不要让业务代码直接去解析 URL那样耦合太深后面加参数会疯。一个典型的解析逻辑public static void HandleUrl(string rawUrl) { var uri new Uri(rawUrl); var query System.Web.HttpUtility.ParseQueryString(uri.Query); string room query[room]; string activityId query[activity]; DeepLinkEvent.Raise(new DeepLinkData { Scheme uri.Scheme, Host uri.Host, Path uri.AbsolutePath, Params query }); }System.Web.HttpUtility在 Unity 里需要引用System.Web如果打包报错可以自己写一个简单的 Query 解析器按和切分注意做 URL Decode。4.2 冷热启动的统一处理冷启动和热启动在 C# 层最好收敛成一个入口。我的做法是原生侧不管什么场景最终都调用同一个OnDeepLinkReceivedC# 侧只认这一个方法。冷启动时通过GetPendingDeepLink在Awake里补一次热启动时通过UnitySendMessage实时进来。void Awake() { DontDestroyOnLoad(gameObject); #if UNITY_IOS !UNITY_EDITOR string pending GetPendingDeepLink(); if (!string.IsNullOrEmpty(pending)) { HandleUrl(pending); } #endif } public void OnDeepLinkReceived(string url) { HandleUrl(url); }这里有个顺序问题如果Awake里就分发事件而业务模块还没注册监听事件就丢了。所以我会加一个队列把解析好的数据缓存起来等业务模块主动来取或者延迟一帧再分发。4.3 参数投递到具体业务的时机不同业务对时机的敏感度不一样。比如“跳转到指定房间”必须等登录完成、房间列表拉取完才能执行而“记录邀请码”这种越早越好最好在登录请求里就带上。我的经验是给 DeepLink 数据加一个“消费状态”业务模块处理完标记一下避免重复触发。比如用户从后台切回来又触发一次热启动如果不做去重可能重复跳转。public class DeepLinkData { public string RawUrl; public Dictionarystring, string Params; public bool Consumed; }业务侧判断Consumed再决定要不要处理。这个细节看起来小但线上真的能省掉很多莫名其妙的重复跳转 bug。5. 全链路实操流程与验证方法5.1 从零搭建的完整步骤我把整个流程拆成可执行的清单照着做基本不会漏。确定域名和 Team IDUniversal Links 必须用真实域名测试环境可以用 ngrok 之类的临时域名但 AASA 文件要能公网访问。配置 AASA 文件放到域名根目录/.well-known/apple-app-site-association注意没有后缀名Content-Type 是application/json。Xcode 加 Associated Domains格式applinks:yourdomain.com不要带路径。Info.plist 加 URL Scheme作为兜底方案。原生回调补全冷启动、热启动、Universal Links 三条路径都要覆盖。桥接层实现UnitySendMessageGetPendingDeepLink双通道。C# 解析与分发统一入口队列缓存消费去重。业务接入各模块监听事件按需消费。5.2 验证是否生效的几种手段最直接的是用备忘录写一条链接点一下看是否唤起 App。但要注意如果你之前点过同域名的链接系统可能记住了你的选择这时候要去 Safari 里清除或者换个域名测试。调试原生层可以用 Xcode 的 Console 看日志在handleDeepLink里打一行NSLog确认回调有没有进来。C# 层用Debug.Log输出解析结果。两端日志对不上就说明桥接断了。还有一个技巧用xcrun simctl openurl booted mygame://open?room123可以直接在模拟器里触发 URL Scheme省得手动点。Universal Links 在模拟器上支持有限建议真机测。5.3 参数传递的编码规范我整理了一个参数约定团队里统一用参数名含义是否必填示例activity活动标识否summer2024room房间号否10086invite邀请码否ABC123channel渠道来源否wechat所有值做 URL EncodeC# 侧统一 Decode。这样即使后面加参数也不会因为编码问题翻车。6. 常见问题与排查技巧实录6.1 点了链接没反应怎么办先分场景。如果是 Universal Links第一步检查 AASA 文件能不能公网访问用浏览器直接打开https://yourdomain.com/.well-known/apple-app-site-association看返回的 JSON 对不对。第二步检查 Team ID 和 Bundle ID 是否匹配一个字符都不能错。第三步看是不是在 Safari 地址栏直接输入的换成备忘录点击测试。如果是 URL Scheme检查 Info.plist 里的 Scheme 名字和链接是否一致大小写敏感。还有一点iOS 会弹窗问“是否在 App 中打开”如果用户点了取消后续可能不再提示需要重置。6.2 冷启动拿不到参数九成是时机问题。原生回调进来了但 C# 还没准备好。确认GetPendingDeepLink有没有被调用pendingDeepLink有没有被提前清空。我遇到过一种情况原生侧在didFinishLaunching里调了UnitySendMessage结果 Unity 还没初始化消息丢了而pendingDeepLink又没存参数就彻底没了。所以“暂存 主动拉取”这个模式一定要做。6.3 热启动重复触发用户从后台切回来系统可能再次触发continueUserActivity导致业务重复跳转。解决办法是在 C# 侧对同一个 URL 做短时间内的去重比如 2 秒内相同 URL 只处理一次。或者用Consumed标记处理完就不再响应。6.4 微信内打不开微信对 Universal Links 有自己的策略不是所有域名都能直接唤起。常见做法是引导用户点右上角“在浏览器中打开”或者用微信开放标签。这块没有银弹只能根据实际投放渠道去适配。我的建议是准备一个中间页检测环境后给出对应引导。6.5 排查速查表现象可能原因排查方向完全没反应Scheme 未注册 / AASA 不可访问检查 Info.plist 和域名文件冷启动丢参数时机不对未暂存加 pending 机制热启动重复回调多次触发加去重逻辑参数乱码未 URL Encode统一编码规范微信内失效平台拦截中间页引导7. 我在实际项目里的一些体会这套链路我前后在三个项目里落地过最大的感受是Deep Link 的难点从来不在写代码而在配置和时机。代码逻辑其实就那么几十行但 AASA 文件的一个字符错误、Team ID 的一个大小写、回调方法漏了一个都能让你调一整天。另外一个体会是一定要在项目早期就把这套东西搭好不要等到投放前才临时加。因为 Deep Link 涉及原生工程、域名、证书、Unity 桥接多个环节任何一个环节的改动都可能影响打包流程。早期搭好后面加参数只是改解析逻辑成本很低。最后分享一个小技巧在 C# 层做一个 Deep Link 的调试面板把最近收到的 URL、解析结果、消费状态都显示出来测试的时候一目了然比翻日志快多了。这个面板在 Editor 下可以模拟输入 URL 触发真机上也能看历史记录团队里用过都说好。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑