Appearance
深链 (Deep Link)、App Link 与合成返回栈治理
回到总览:状态管理、路由、架构
相关模块:Flutter 路由:go_router / Navigator 体系 · ViewModel 架构、SavedStateHandle 与进程被杀恢复
一句话定义
深链(Deep Link)是指通过 Scheme 协议或 HTTP(S) URL 直接唤醒移动端应用并精准跳转至特定二级页面的机制;配合 App Link (Android) / Universal Link (iOS) 域名权威校验以及 合成返回栈 (Synthetic Back Stack) 构建,保障了外部唤醒时的安全性与符合用户直觉的返回导航体验。
代码索引
| 主题 | Lab 说明 | 源码 |
|---|---|---|
| deeplink-navigation-demo | App Link 命中详情页并验证返回键先回 Home 的合成返回栈体验 | DeepLinkHomeActivity.kt · DeepLinkDetailActivity.kt |
为什么需要
- 普通自定义 Scheme (如
myapp://detail?id=1) 与 App Link / Universal Link (如https://example.com/detail/1) 有什么本质区别?- 一句话答:自定义 Scheme 缺乏域名所有权校验,任何恶意 App 均可在 Manifest 中声明相同的 Scheme 抢占唤醒(Scheme 劫持);而 App Link 必须在服务器部署
assetlinks.json完成域名双向校验,只会被目标官方 App 直接打开,无弹窗拦截且安全。
- 一句话答:自定义 Scheme 缺乏域名所有权校验,任何恶意 App 均可在 Manifest 中声明相同的 Scheme 抢占唤醒(Scheme 劫持);而 App Link 必须在服务器部署
- 外部唤醒应用进入深层详情页后,用户点击系统“返回键”直接退出了应用,体验极差,如何解决?
- 一句话答:必须使用
TaskStackBuilder构建合成返回栈 (Synthetic Back Stack);在唤醒深层页面时,自动在 Task 栈中顺带插入 Parent Activity(如首页),确保点击返回时能逐级回退至 App 首页而非退出。
- 一句话答:必须使用
- 在 Flutter 声明式路由 (
go_router) 中如何统一治理 Deep Link?- 一句话答:
go_router基于 Navigator 2.0 架构,原生支持 URL 路由映射;通过在 Router 配置中解析 Deep Link 参数,自动匹配 URL path 并构建深层页面栈,天然支持跨平台 URL 驱动。
- 一句话答:
底层机制
1. App Link 域名双向校验机制 (AssetLinks.json)
图注补充:Web: 浏览器 / 微信 / 外部 App;OS: Android 系统 (PackageManager);Server: 官方服务器 (https://example.com);OS: 1. 用户点击 https://example.com/detail/10086;TargetApp: 2. 检查 App 的 AndroidManifest.xml 是否包含 autoVerify=true;Server: 3. 向 https://example.com/.well-known/assetlinks.json 发起 HTTPS 请求;OS: 4. 返回包含 App 包名与 SHA256 签名证书的 JSON;OS: 5. 比对本地 App 签名与服务端 SHA256;TargetApp: 6. 跳过应用选择弹窗,直接打开官方 App 详情页!。
2. 合成返回栈 (Synthetic Back Stack) 构建原理
图注补充:2. 传统 Deep Link 直接唤醒 (无返回栈 - 隐患);直接退出到桌面/第三方 App (体验差);3. 合成返回栈 (TaskStackBuilder)。
代码示例与合成返回栈构建
1. Android Native TaskStackBuilder 构建合成返回栈
kotlin
fun openDetailWithSyntheticStack(context: Context, detailId: String) {
// 1. 创建指向详情页的 Intent
val detailIntent = Intent(context, DetailActivity::class.java).apply {
putExtra("EXTRA_DETAIL_ID", detailId)
}
// 2. 使用 TaskStackBuilder 自动读取 Manifest 中的 parentActivityName 并合成返回栈
TaskStackBuilder.create(context)
.addNextIntentWithParentStack(detailIntent) // 自动将 MainActivity 压入底层
.startActivities()
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
2. Manifest 配置与 App Link 自动校验
xml
<activity
android:name=".DetailActivity"
android:exported="true"
android:parentActivityName=".MainActivity"> <!-- 声明 Parent Activity -->
<intent-filter android:autoVerify="true"> <!-- 启用 App Link 自动校验 -->
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="example.com"
android:pathPrefix="/detail/" />
</intent-filter>
</activity>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Android / Flutter / Web / Backend 对照
| 维 | Android 原生 | Flutter (go_router) | iOS 苹果生态 |
|---|---|---|---|
| 安全 HTTP 深链 | App Link (autoVerify=true) | 原生协议挂载 + go_router 解析 | Universal Link (apple-app-site-association) |
| 配置文件 | /.well-known/assetlinks.json | 继承 iOS / Android 原生配置 | /.well-known/apple-app-site-association |
| 返回栈构建 | TaskStackBuilder | GoRoute 嵌套子路由天然支持栈构建 | UINavigationController 栈重构 |
常见场景
1. 外部 App 唤醒后如果用户未登录处理
在 Deep Link 拦截器中,先校验 Token;若未登录,将真实的 Deep Link 目标 URL 存入 SavedStateHandle / Memory,跳转至 LoginActivity,待用户登录成功后自动重定向至原 Deep Link 页面。
常见误配、事故后果与排障
1. 事故:自定义 Scheme 被竞争对手 App 恶意劫持
- 误配原因:使用
myproject://pay作为支付结果回调,且未做域名强校验。 - 后果:恶意 App 在 Manifest 中也注册了
myproject://pay,导致支付完成后的敏感 Token 被恶意 App 拦截截获! - 排障与修法:放弃自定义 Scheme 协议,全面升级为必须通过域名证书校验的 App Links (Android) / Universal Links (iOS)。
与相近概念对比
| 深链类型 | URL 格式 | 验证机制 | 体验优势 |
|---|---|---|---|
| Custom Scheme | myapp://detail/1 | 无(易被竞争对手劫持) | 兼容老版本系统,但会弹窗让用户选择打开方式 |
| App Link / Universal Link | https://example.com/detail/1 | 权威双向证书校验 | 无弹窗直达 App;未安装 App 时优雅降级打开 H5 |
对应实验
| Lab | 说明 | 源码 |
|---|---|---|
| deeplink-navigation-demo | Scheme / App Link 入口与合成返回栈验证 | DeepLinkHomeActivity.kt · DeepLinkDetailActivity.kt |
复习检查题
为什么 App Link(或 Universal Link)比自定义 Scheme(如
custom://path)更加安全?答:自定义 Scheme 在系统中没有唯一性校验,任何恶意 App 均可在 Manifest 中注册相同的 Scheme 来抢扣 Intent 触发“Scheme 劫持”。而 App Link 要求必须使用标准的
https://域名,且 Android 系统在安装时会强制请求服务器根目录下的assetlinks.json校验签名 SHA256,只有匹配官方签名的 App 才能打开,彻底杜绝了被劫持风险。外部深链直接打开 App 内的二级页面时,为什么需要构建“合成返回栈 (Synthetic Back Stack)”?
答:因为如果是直接打开二级页面,Task 栈中只有这单一 Activity。当用户浏览完页面点击系统返回键时,应用会直接退出回到桌面或第三方 App,体验断层。构建合成返回栈会自动在 Task 栈底层插入 Parent Activity(如首页),使得用户点击返回时能自然回退至首页,符合用户习惯。
速记
- 安全首选:弃用 Custom Scheme,全面拥抱 App Link 域名双向校验。
- 合成返回栈:
TaskStackBuilder补全 Parent Activity,点击返回不掉队。 - 未登拦截:Deep Link 命中未登录先存目标 URL,登完无感重定向。