原生移动应用的深度链接:路由、令牌与“在应用中打开”
了解原生移动应用的深度链接:如何规划路由、处理“在应用中打开”行为,并在 Kotlin 与 SwiftUI 中安全传递令牌,无需混乱的自定义路由代码。

深度链接应做什么,用通俗的话说
当有人在手机上点击一个链接时,他们只希望一个结果:立即到达正确的位置。不是大致正确的地方,也不是带搜索栏的主屏幕,更不是忘记他们来意的登录页。
良好的深度链接体验大致如下:
- 如果应用已安装,打开到链接指向的准确界面。
- 如果应用未安装,点击仍有帮助(例如打开网页回退或应用商店页面,并能在安装后带回同一目的地)。
- 如果需要登录,用户只需登录一次并到达目标界面,而不是回到应用默认起点。
- 如果链接携带某个操作(接受邀请、查看订单、确认邮箱),该操作应明确且安全。
大多数挫败感来自“差不多能用”的链接:用户看到错误的界面、丢失原有操作,或陷入循环:点链接、登录、到达仪表盘、再点链接、再登录。哪怕多一步,也会让用户放弃,尤其是一次性操作(如邀请或重设密码)。
在写任何 Kotlin 或 SwiftUI 代码之前,先决定链接应意味着什么。哪些界面可以被外部打开?应用已关闭与已运行时行为有何不同?用户登出时应如何处理?
规划能避免绝大部分后续痛点:清晰的路由、可预期的“在应用中打开”行为,以及在不把秘密直接放入 URL 的情况下安全交接一次性代码。
深度链接类型与“在应用中打开”常出错的地方
并非所有“能打开应用的链接”都以相同方式工作。把它们视为可互换会导致常见故障:链接打开了错误位置、打开了浏览器而不是应用,或仅在某个平台有效。
常见三类:
- 自定义 scheme(例如 myapp:)。设置简单,但许多应用和浏览器会谨慎处理。
- Universal Links(iOS)和 App Links(Android)。 使用普通网页链接,已安装应用时可打开应用,未安装时回退到网站。
- 内嵌浏览器中的链接。 在邮件或即时通讯应用内打开的链接,行为常与 Safari 或 Chrome 不同。
“在应用中打开”在不同点击环境下含义不同。Safari 中的点击可能直接跳转到应用;同一链接在邮件或聊天应用内点击可能先打开内嵌 WebView,用户还得按一次“在应用中打开”按钮(或者根本看不到)。在 Android 上,Chrome 可能尊重 App Links,而社交应用的内嵌浏览器可能忽略它们。
另一个陷阱是冷启动与应用已在运行时的差异。
- 冷启动: 操作系统启动你的应用,应用进行初始化,然后你才收到深度链接。如果启动流程会显示闪屏、检查认证或加载远程配置,链接可能会丢失,除非你保存并在应用就绪后重放它。
- 应用已在运行: 在会话中收到链接时,导航栈存在,相同目的地可能需要不同处理(是 push 一个新界面还是重置栈)。
举个简单例子:在 Telegram 中点击邀请链接通常会先在内嵌浏览器打开。如果你的应用假定操作系统总会直接交接,用户会看到一个网页并以为链接坏了。事先为这些环境做规划,可以减少平台特定的黏合代码。
在实现前先规划路由
大多数深度链接 Bug 并不是 Kotlin 或 SwiftUI 的问题,而是规划问题。链接与界面之间没有清晰映射,或携带太多“也许”的选项。
从一个与用户认知一致的路由模式开始:列表、详情、设置、结账、邀请。保持可读且稳定,因为你会在邮件、二维码和网页中复用它们。
一个简单的路由集合可能包括:
- 首页
- 订单列表与订单详情(orderId)
- 账户设置
- 接受邀请(inviteId)
- 搜索(query、tab)
然后定义参数:
- 对于单个对象使用 ID(如 orderId)。
- 对于 UI 状态使用可选参数(tab、filter)。
- 决定默认值,使每个链接都有一个最佳目的地。
还要决定当链接出错时的处理:缺少数据、无效 ID 或用户无权访问的内容。最安全的默认是打开最近的稳定页面(如列表)并显示简短提示。避免把人丢到空白页或没有上下文的登录页。
最后按来源来规划。二维码通常需要短且能快速打开的路由并能容忍网络不稳定;邮件链接可以更长并包含更多上下文;网页链接应优雅降档:如果应用未安装,用户仍应到达能说明后续步骤的页面。
如果你使用后端驱动的方法(例如生成 API 端点并用像 AppMaster(appmaster.io)之类的平台生成页面),这个路由计划就成了共享契约:应用知道去哪里,后端知道哪些 ID 和状态有效。
在不把秘密放 URL 的情况下安全交接令牌
深度链接常被当成一个安全信封,但它不是。URL 中的任何内容都可能出现在浏览器历史、截图、分享预览、分析日志或被复制到聊天中。
避免在链接中放秘密,包括长期有效的访问令牌、刷新令牌、密码、个人数据或任何能让别人以用户身份操作的东西。
更安全的做法是使用短期、一次性代码。链接仅携带该代码,应用打开后与后端交换以获取真实会话。如果链接被窃取,该代码应在一分钟或几分钟后或首次兑换后失效。
一个简单的交接流程:
- 链接包含一次性代码,而不是会话令牌。
- 应用打开并调用后端去兑换该代码。
- 后端验证过期与未被使用,然后标记为已使用。
- 后端返回常规的认证会话给应用。
- 应用在兑换后清除内存中的代码。
即便兑换成功,在执行敏感操作前也要在应用内再次确认身份。如果链接用于批准付款、修改邮箱或导出数据,应要求生物识别或重新登录等快速确认。
安全存储会话。iOS 上通常使用 Keychain;Android 上使用受 Keystore 保护的存储。只存必要数据,并在登出、删除账户或检测到可疑重用时清除它们。
一个具体例子:你发送一个邀请链接加入工作区。链接携带一个 10 分钟后过期的一次性代码。应用兑换后展示一屏,清楚说明接下来将发生什么(加入哪个工作区)。只有用户确认后才完成加入。
如果你用 AppMaster 构建,这可以直接映射到一个兑换代码并返回会话的端点,而移动端 UI 在任何高影响操作前负责确认步骤。
认证与“登录后继续”
深度链接常指向包含私有数据的界面。首先决定哪些是公开可打开的(public),哪些需要登录(protected)。这个决定能避免大多数“测试时可用”的意外。
一个简单经验法则:先深度链接到一个安全的落地状态,确认用户已认证后再导航到受保护界面。
决定哪些是公开的与受保护的
把深度链接当作可能被转发给错误人的东西来处理。
- 公开(Public): 营销页、帮助文章、密码重置起始页、邀请接受起始页(尚未展示数据)
- 受保护(Protected): 订单详情、消息、账户设置、管理员界面
- 混合(Mixed): 可以有预览页,但在登录前只显示非敏感占位内容
登录后返回到正确位置
可靠的方法是:解析链接,存储预期目的地,然后根据认证状态路由。
示例:用户在登出状态下点击“在应用中打开”的支持工单链接。应用应先打开到中性页面,要求登录,然后自动跳转到该工单。
为可靠性,将一个小的“返回目标”(路由名加工单 ID)本地存储并设置短期过期。登录完成后读取一次,导航并清除。若登录失败或目标过期,则回退到安全的首页。
谨慎处理边缘情况:
- 会话过期: 显示简短信息,重新认证,然后继续。
- 权限被撤销: 打开目标壳体,显示“你不再有访问权限”,并提供下一步建议。
同时避免在锁屏预览、应用切换截图或通知预览中显示私有数据。在数据加载并确认会话前让敏感界面保持空白或占位。
一种避免自定义导航烂摊子的路由方法
当每个界面都自行解析 URL 时,深度链接会变得混乱。小的决策(哪个参数是可选的、哪个是必需的、格式是否合法)会散落到整个应用,导致难以安全修改。
把路由当作共享管道。保持一张路由表和一个解析器,让 UI 接收干净的输入。
使用一张共享路由表
让 iOS 与 Android 使用同一套、可人为理解的路由列表,把它视为契约。
每条路由映射到:
- 一个界面,和
- 一个小的输入模型。
例如,“订单详情”映射到 Order 界面,输入为 OrderRouteInput(id)。如果路由需要额外值(如来源 ref),它们应在该输入模型内,而不是散落在视图代码中。
集中解析与校验
把解析、解码与校验集中在一处。UI 不应该在问“这个 token 在吗?”或“这个 ID 合法吗?”。它应接收到要么是有效的路由输入,要么是明确的错误状态。
一个实用流程:
- 接收 URL(点击、扫码、分享面板)
- 将其解析为已知路由
- 校验必需字段与格式
- 生成界面目标和输入模型
- 通过单一入口点导航
添加一个“未知链接”回退页。让它有用,而不是死胡同:展示无法打开的内容、用通俗语言解释原因,并提供返回首页、搜索或登录等下一步操作。
逐步指南:设计深度链接与“在应用中打开”行为
好的深度链接最让人无感,因为它们总是按预期工作。无论应用是否已安装、冷启动或用户已登录,点击即到达正确位置。
第 1 步:选出关键入口点
列出前 10 个用户真正会用到的链接类型:邀请、密码重置、订单凭证、查看工单、促销链接。有意保持少量。
第 2 步:将模式写成契约
为每个入口点定义一个规范模式和打开正确界面所需的最小数据。优先使用稳定的 ID 而非名称。决定哪些是必需,哪些是可选。
有用的规则:
- 每条路由只为一个目的(invite、reset、receipt)。
- 必需参数总是存在;可选参数有安全默认值。
- 在 iOS(SwiftUI)和 Android(Kotlin)上使用相同模式。
- 若预期会变更,可保留简单的版本前缀(如 v1)。
- 定义参数缺失时的处理(显示错误页而不是空白)。
第 3 步:确定登录行为与登录后的目标
针对每种链接类型写明是否需要登录。如果需要,记住目标并在登录后继续。
示例:收据链接可以在未登录时显示预览,但点击“下载发票”可能需要登录并应在登录后返回到该收据。
第 4 步:设置令牌传递规则(不要把秘密放在 URL)
若链接需要一次性令牌(邀请接受、重置、魔法登录),定义其有效期与使用方式。
实际做法:URL 携带短期、单次使用的代码,应用兑换该代码以获取真实会话。
第 5 步:测试三种真实状态
深度链接在边界情况下容易失效。为每种链接类型测试:
- 冷启动(应用关闭)
- 热启动(应用在内存中)
- 未安装应用(链接仍应着陆到合理页面)
如果把路由、认证检查与令牌兑换规则集中管理,就能避免在 Kotlin 与 SwiftUI 界面间散落自定义路由逻辑。
常见错误及避免方法
深度链接失败多半是因为琐碎的原因:一个小假设、重命名了页面,或“临时”的令牌被到处放置。
常见故障与修复
-
在 URL 中放置访问令牌(并泄露到日志)。 查询字符串会被复制、共享、存入浏览器历史并被分析或崩溃日志捕获。修复:链接中仅放短期一次性代码,应用内兑换并迅速过期。
-
假设应用已安装(没有回退)。 若链接打开错误页或没有反应,用户会放弃。修复:提供回退网页,说明将发生什么并给出安装路径。即使只是“打开应用以继续”的简单页面,也比无反应好。
-
未处理同一设备上的多账户情况。 在错误账户下打开正确页面比链接无效更糟糕。修复:接收到链接后检查当前激活账户,提示用户确认或切换,然后继续。若动作依赖特定工作区,应在链接中包含工作区标识(非秘密)并做校验。
-
屏幕或路由变更导致链接失效。 若路由与 UI 名称耦合,一旦重命名旧链接就失效。修复:设计稳定的、基于意图的路由(invite、ticket、order),并保持旧版本兼容。
-
出问题时缺乏可追溯性。 没有可复现记录,支持只能猜测。修复:在链接中包含非敏感的请求 ID,在服务器和应用中记录,并在错误信息中显示该 ID 以便排查。
现实检验:想象一个群聊里发出的邀请链接。有人在办公手机上打开(有两个账户),应用未在平板上安装,链接又被转发给同事。如果链接仅包含邀请代码、支持回退行为、提示切换账户并记录请求 ID,那么这个链接在各种情况下都能成功且不暴露秘密。
示例:一个每次都能打开正确页面的邀请链接
邀请是经典的深度链接场景:他人在聊天中发来链接,接收者期望一次点击就到邀请界面,而不是通用首页。
场景:经理邀请一名新客服加入“Support Team”工作区。客服在 Telegram 中点击邀请。
若应用已安装,系统应打开应用并把邀请详情传给它。若未安装,用户应到达一个简单网页,说明邀请用途并提供安装路径。安装并首次启动后,应用应仍能完成邀请流程,无需用户再去找链接。
在应用内,Kotlin 与 SwiftUI 的流程一致:
- 从传入链接读取邀请代码。
- 检查用户是否已登录。
- 向后端验证邀请,然后路由到正确界面。
验证是关键。链接不应包含长期会话类的秘密,只应携带服务端可验证的一次性代码。
用户体验应可预期:
- 未登录: 看到登录页,登录后返回邀请接受页。
- 已登录: 看到一次“加入工作区”确认,然后进入正确的工作区。
若邀请过期或已被使用,不要把用户丢到空白错误页。给出明确信息和下一步建议:请求新邀请、切换账户或联系管理员。“该邀请已被接受”总比“无效令牌”更有帮助。
快速清单与下一步
当深度链接在冷启动、热启动以及用户已登录时表现一致时,才算“完成”。
发布前快速检查清单
在真实设备和不同系统版本上测试每一项:
- 链接在冷启动与热启动时都能打开正确界面。
- URL 中不含敏感数据;若必须传令牌,令牌应短期且最好一次性。
- 对于未知、过期或已使用的链接,有清楚的回退界面和下一步操作。
- 链接能在邮件客户端、浏览器、二维码扫描器和聊天预览中工作(部分会预先打开链接)。
- 日志记录展示发生了什么(接收到链接、解析路由、是否需要认证、成功或失败原因)。
一种简单的验证方法是挑出几个必须生效的链接(邀请、密码重置、订单详情、支持工单、促销),并在相同测试流程下反复测试:从邮件点击、从聊天点击、扫码、重装后打开。
下一步(保持可维护性)
如果深度链接逻辑开始散落到各个界面,把路由与认证当作共享管道集中管理,而不是在每个屏幕写特定代码。把路由解析集中到一处,让每个目标界面只接受干净参数(而非原始 URL)。认证同理:一个认证闸门决定“现在继续”还是“先登录再继续”。
如果想减少自定义粘合代码,把后端、认证与移动端一起构建会有帮助。AppMaster(appmaster.io)是一个无代码平台,可以生成生产就绪的后端和原生移动应用,有助于在需求变化时保持路由名和一次性代码兑换端点的一致。
如果你这周只做一件事:把你的规范路由写下来,并为每种失败情况记录精确的回退行为,然后在单一路由层中实现这些规则。
常见问题
深度链接应该打开链接所指向的确切界面,而不是通用的首页或仪表盘。如果应用未安装,链接仍应提供帮助,例如打开一个合理的网页落地页,并在安装后引导用户回到相同目的地。
Universal Links(iOS)和 App Links(Android)使用普通的网页 URL,安装了应用时可以直接打开应用,未安装时可优雅回退到网站。自定义 scheme 更容易配置,但可能会被浏览器或其他应用限制或以不一致的方式处理,因此应作为备选方案。
许多邮件和即时通讯应用会在内嵌浏览器中打开链接,这些内嵌浏览器可能不会像 Safari 或 Chrome 那样将控制权交给操作系统。因此请为网页回退做好准备,并处理用户先看到网页再打开应用的情况。
在冷启动时,应用可能会展示闪屏、运行启动检查或加载远程配置,从而在导航就绪前丢失深度链接。可靠的解决办法是立即存储传入的目标,完成初始化后再“重放”导航。
不要在 URL 中放置长期有效的访问令牌、刷新令牌、密码或个人数据,因为 URL 可能被记录、共享或缓存。建议在链接中仅放一个短期、一次性使用的代码,应用打开后将其与后端交换以获取会话。
解析链接并存储预期目的地,然后根据认证状态路由,这样用户只需登录一次即可回到正确界面。将存储的“返回目标”保持小且有时间限制,使用后清除它。
把路由当作一个共享契约,集中解析和校验,然后传递干净的输入给界面,而不是让每个屏幕自己处理原始 URL。这样可以避免各处散落的处理规则和错误边界情况。
接收链接时先检查当前激活账户是否与链接所指的工作区或租户匹配,如不匹配则提示用户确认或切换账户,然后再展示私有内容。比把错误内容直接展示在错误账户下更安全。
遇到无效、已过期或缺失数据的链接时,应默认打开最近的稳定页面(例如列表页),并展示简短信息解释无法打开的原因。避免空白页、无提示的失败或把用户丢到没有上下文的登录页。
在真实设备和真实来源上测试每个重要链接类型的三种状态:应用已关闭、应用已在运行、以及应用未安装。AppMaster(appmaster.io)可以帮助保持后端与原生应用间的路由名称和一次性代码兑换端点一致,从而减少维护自定义粘合代码的需要,但无论如何都要在真实环境中验证行为。


