03-超级App软件平台@路由规则设计-【Universal-Links】与【App-Links详解】

本专题为软件平台路由规则设计 (含移动端、Web、电脑、手表与手环等智能穿戴)。本文对应「Universal Links / App Links」阶段,介绍基于 HTTPS 的可验证深链接在 iOSAndroidHarmonyOSFlutterMacOSWebAppReactNative 等平台上的配置、校验流程与 App 内路由对接,含多端代码示例与流程图。多端总览见 08-软件体系与多平台路由对照


一、概念与定位

  • Universal Links (iOS 9+):使用标准 HTTPS URL (如 https://example.com/app/detail/1)打开 App 内指定内容。通过域名下的 apple-app-site-association 文件证明「该域名由该 App 拥有」,系统在用户点击链接时可直接打开 App 而无需经过 Safari 或弹窗,未安装时则在浏览器打开同一 URL,实现「一条链接、两种体验」。
  • App Links (Android 6.0+):与 Universal Links 理念一致,使用 HTTPS + assetlinks.json (或 Digital Asset Links )在服务器证明域名与 App 的关联,系统可无歧义地将链接打开到指定 App(不弹出「用哪个应用打开」)。

二者都是可验证的、基于域名的深链接方案,相比 URL Scheme 更安全、可突破部分容器对自定义 Scheme 的拦截,且与 Web 和 SEO 友好。

1.2 知识结构(思维导图)

mindmap root((Universal Links / App Links)) 概念 基于 HTTPS 域名归属验证 无歧义打开 / 无弹窗 iOS / MacOS AASA Associated Domains NSUserActivity Android assetlinks.json autoVerify HarmonyOS HTTPS 关联 类似思路 Flutter / ReactNative 宿主配置 app_links / Linking WebApp URL 即深度 同源/多域 配置与调试 文件位置 签名 HTTPS

二、整体校验与打开流程(泳道图)

flowchart TB subgraph 用户与系统 A[用户点击 https://example.com/app/detail/1] B[系统解析 URL] C{已安装关联 App?} end subgraph iOS D[iOS 读取已缓存的 AASA] E[匹配 applinks details] F[直接打开 App 并传 URL] end subgraph Android G[读取 assetlinks.json] H[验证签名与包名] I[直接启动对应 Activity] end subgraph 服务器 J[HTTPS 提供 AASA / assetlinks] end A --> B B --> C C -->|是| D C -->|否| K[在浏览器打开] D --> E --> F B --> G --> H --> I D -.->|安装时或定期| J G -.-> J

3.1 服务器配置:apple-app-site-association(AASA)

在域名根路径或 .well-known 下提供 apple-app-site-association无后缀),可通过以下两种 URL 访问:

  • https://yourdomain.com/apple-app-site-association
  • https://yourdomain.com/.well-known/apple-app-site-association

内容示例(JSON,且需 HTTPS):

json 复制代码
{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "TEAM_ID.com.yourapp.bundleid",
        "paths": [
          "/app/*",
          "/detail/*",
          "/news/*"
        ]
      }
    ]
  }
}
  • appID:苹果开发者 Team ID + 点 + Bundle ID。
  • paths:匹配路径,支持 * 通配符;前面加 NOT 表示排除(如 NOT /admin/*)。

3.2 Xcode 配置:Associated Domains

  1. 在 Signing & Capabilities 中增加 Associated Domains
  2. 添加条目:applinks:yourdomain.com(不要写 https://)。
makefile 复制代码
applinks:yourdomain.com

3.3 App 内接收并交给路由(Swift)

AppDelegate(冷启动 / 从后台被链接唤起)

swift 复制代码
// AppDelegate.swift
func application(
    _ application: UIApplication,
    continue userActivity: NSUserActivity,
    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
          let url = userActivity.webpageURL else { return false }
    return AppRouter.shared.handleOpenURL(url)
}

SceneDelegate(若使用 Scene 生命周期)

swift 复制代码
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
          let url = userActivity.webpageURL else { return }
    AppRouter.shared.handleOpenURL(url)
}

统一路由解析 path(与 Scheme 共用一套路由表):

swift 复制代码
// AppRouter 中增加对 HTTPS 的处理
func handleOpenURL(_ url: URL) -> Bool {
    if url.scheme == "myapp" {
        return handleSchemeURL(url)  // 见 02-URL-Scheme 文档
    }
    if url.scheme == "https", url.host == "yourdomain.com" {
        // 将 https://yourdomain.com/app/detail/1 转为 path: /app/detail/1
        let path = url.path
        let params = url.queryItems ?? [:]
        return navigate(path: path, params: params)
    }
    return false
}

4.1 服务器配置:assetlinks.json

在以下任一路径提供 assetlinks.json(HTTPS):

  • https://yourdomain.com/.well-known/assetlinks.json

内容示例

json 复制代码
[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.yourapp",
      "sha256_cert_fingerprints": [
        "AA:BB:CC:..."
      ]
    }
  }
]

sha256_cert_fingerprints 为签名的 SHA256(冒号分隔),可通过:

bash 复制代码
keytool -list -v -keystore your.keystore

获取。调试时需加入 debug 签名的指纹

4.2 AndroidManifest 配置

在接收链接的 Activity 的 <intent-filter> 中增加 autoVerify ,并保持 ACTION_VIEWBROWSABLEDEFAULThttps 的 data:

xml 复制代码
<intent-filter android:autoVerify="true">
    <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="yourdomain.com"
        android:pathPrefix="/app"
        android:pathPattern="/detail/.*" />
</intent-filter>

安装或更新后,系统会访问 assetlinks.json 做验证;可在系统设置中查看该应用「打开支持的链接」状态。

4.3 Activity 中解析 Intent(Kotlin)

与普通深链接一致,通过 Intent.data 获取 Uri:

kotlin 复制代码
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    handleDeepLink(intent)
}

override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    intent?.let { handleDeepLink(it) }
}

private fun handleDeepLink(intent: Intent?) {
    val uri = intent?.data ?: return
    if (uri.scheme != "https" || uri.host != "yourdomain.com") return
    val path = uri.path ?: return
    val params = uri.queryParameterNames.associateWith { uri.getQueryParameter(it) ?: "" }
    AppRouter.handle(this, path, params)
}

五、Flutter 实现详解

5.1 依赖与平台配置

pubspec.yaml

yaml 复制代码
dependencies:
  app_links: ^6.3.0

iOS:在 Xcode 中配置 Associated Domains(applinks:yourdomain.com)。

Android:如上节配置 assetlinks.jsonintent-filter android:autoVerify="true"

5.2 监听初始链接与后续链接(Dart)

dart 复制代码
// lib/deep_link_handler.dart
import 'package:app_links/app_links.dart';

class DeepLinkHandler {
  static final AppLinks _appLinks = AppLinks();

  static Future<void> init() async {
    // 冷启动:通过 Universal Link / App Link 打开
    final initialUri = await _appLinks.getInitialLink();
    if (initialUri != null) {
      _routeFromUri(initialUri);
    }
    // 热启动:App 在后台时点击链接
    _appLinks.uriLinkStream.listen((Uri uri) {
      _routeFromUri(uri);
    });
  }

  static void _routeFromUri(Uri uri) {
    // 同时支持 myapp:// 与 https://yourdomain.com
    if (uri.scheme == 'myapp') {
      GlobalRouter.navigateFromScheme(uri);
      return;
    }
    if (uri.scheme == 'https' && uri.host == 'yourdomain.com') {
      GlobalRouter.navigate(uri.path, params: uri.queryParameters);
    }
  }
}

六、校验与打开时序图

sequenceDiagram participant U as 用户 participant OS as 系统 participant CDN as 服务器/CDN participant App as App Note over U,App: 安装/更新时:系统拉取 AASA 或 assetlinks OS->>CDN: GET apple-app-site-association / assetlinks.json CDN-->>OS: 返回 JSON OS->>OS: 校验并缓存 U->>OS: 点击 https://yourdomain.com/app/detail/1 OS->>OS: 匹配已缓存的关联 alt 已关联且已安装 App OS->>App: 启动 App 并传入 URL(NSUserActivity / Intent) App->>App: 解析 path + query App->>App: 路由表 → 打开目标页 else 未安装或未关联 OS->>U: 在浏览器打开 URL end

客户端不直接校验文件内容,由系统在安装/更新时拉取并缓存;以下描述服务端/运维视角 的校验逻辑与App 内收到 URL 后的处理。

markdown 复制代码
服务端确保 AASA 可访问(iOS):
  1. 文件路径: https://<domain>/apple-app-site-association 或 /.well-known/...
  2. Content-Type 建议为 application/json
  3. 无需 .json 后缀;HTTPS 且证书有效

App 内收到 Universal Link / App Link 后:
  1. 系统已根据缓存的 AASA/assetlinks 将链接关联到本 App,并传入 URL
  2. 从 URL 解析 path 与 query(与 Scheme 解析方式一致)
  3. 若为 https,path = uri.path,params = uri.queryParameters
  4. 调用统一 Router.navigate(path, params)

八、与 URL Scheme 的对比与选型

维度 URL Scheme Universal Links / App Links
URL 形式 myapp://host/path https://yourdomain.com/path
验证 无,易冲突 域名 + 文件校验,归属明确
未安装时 通常无反应 可在浏览器打开同一页面
容器限制 易被微信等拦截 多数场景可正常跳转
配置成本 需服务器与域名、签名配置

建议:App 内路由统一按 path + query 设计,对外同时支持 Scheme(兼容旧链、简单场景)与 Universal Links / App Links(新链、分享、合规与体验)。

8.1 各平台可验证 HTTPS 深链接支持

平台 支持情况 配置文件 / 备注
iOS / MacOS ✓ Universal Links AASA,Associated Domains
Android ✓ App Links assetlinks.json,autoVerify
HarmonyOS ✓ 支持 HTTPS 关联 需在工程与服务器配置关联
Flutter / ReactNative ✓ 依赖宿主 使用 iOS/Android 配置 + app_links / Linking
WinOS 部分(无统一 AASA 类标准) 多为自定义协议或启动参数
WebApp 不适用(URL 即深度) 同源/多域路由
WatchOS 一般不单独配置 多与 iPhone 协同

九、小结

  • Universal Links / App Links 通过 HTTPS + 服务器配置文件证明「域名归属」,实现可验证、无歧义的深链接。
  • iOS :AASA 文件 + Associated Domains + NSUserActivityTypeBrowsingWebAndroid :assetlinks.json + autoVerify + Intent.data;Flutterapp_linksgetInitialLink / uriLinkStream
  • App 内应将 path + query 统一交给同一套路由层(与 URL Scheme 共用路由表与拦截器),便于维护与扩展。

十、HTTPS 深链接统一入口示例(多端)

无论入口是 Scheme 还是 Universal Link / App Link,进入 App 后都转为 (path, params) 再交给同一 Router,便于维护。

iOS(Swift) :在 handleOpenURL 中区分 scheme,统一成 path:

swift 复制代码
func handleOpenURL(_ url: URL) -> Bool {
    let path: String
    let params: [String: String]
    if url.scheme == "https", url.host == "yourdomain.com" {
        path = url.path
        params = url.queryItems ?? [:]
    } else if url.scheme == "myapp" {
        path = "/" + (url.host ?? "page") + url.path
        params = url.queryItems ?? [:]
    } else { return false }
    return AppRouter.shared.navigate(path: path, params: params)
}

Android(Kotlin):在 Activity 中统一处理 Intent.data:

kotlin 复制代码
private fun handleDeepLink(intent: Intent?) {
    val uri = intent?.data ?: return
    val path = when {
        uri.scheme == "https" && uri.host == "yourdomain.com" -> uri.path ?: return
        uri.scheme == "myapp" -> "/${uri.host ?: "page"}${uri.path ?: ""}"
        else -> return
    }
    val params = uri.queryParameterNames.associateWith { uri.getQueryParameter(it) ?: "" }
    AppRouter.handle(this, path, params)
}

Flutter(Dart) :在 _routeFromUri 中已按 scheme 分支,最终都调用 GlobalRouter.navigate(path, params: params),无需重复实现。


参考文献

  • Apple. Supporting Universal Links.
  • Android. Verify Android App Links.
  • Flutter. Deep linking.
  • 《01-软件平台路由规则设计-总纲》§2.2、§6.
相关推荐
没有故事的Zhang同学2 小时前
01-超级App软件平台@路由规则设计-【总纲】
程序员
没有故事的Zhang同学2 小时前
04-超级App软件平台@路由规则设计-【组件化路由框架详解】
程序员
没有故事的Zhang同学2 小时前
07-超级App软件平台@路由规则设计-【通用路由管理组件设计】
程序员
没有故事的Zhang同学6 小时前
02-Debug调试@网络-Wireshark网络抓包工具:从原理到实践
程序员
SimonKing6 小时前
GitHub 10万星的OpenCode,正在悄悄改变我们的工作流
java·后端·程序员
xiezhr7 小时前
36岁程序员被曝复工当晚猝死出租屋内
程序员·996·程序员日常·猝死·加班
xiezhr7 小时前
米哈游36岁程序员被曝复工当晚猝死出租屋内
游戏·程序员·游戏开发
codetown1 天前
2026年Zig编程语言权威指南:从系统级底层架构到现代软件工程实践
后端·程序员