2. URI Scheme 原理与注册流程

URI Scheme 是操作系统识别并路由深层链接的核心机制。本章从 Scheme 语法规范、双平台注册方式、冲突处理到安全校验,完整覆盖从原理到生产的实现路径。

URI Scheme 注册与路由流程图 定义 Scheme Android 注册 iOS 注册 多 Scheme 冲突检测 系统路由表裁决 安全校验 启动目标页 拒绝 / 重定向 关键说明: • 同一 Scheme 可能被多个 App 注册(冲突),由系统弹窗让用户选择默认处理应用。 • 安全校验必须检查 originate、data 等字段,防止恶意 App 伪造 URI 劫持。

2.1 URI Scheme 语法基础

URI Scheme 遵循 RFC 3986 规范,标准格式为:

scheme://host/path?query#fragment

各部分含义:

部分示例说明
schememyapp协议名称,需要小写字母开头,后跟字母/数字/+/./-
hostopen通常为商品类目或动作标识
path/product/123资源路径,与 HTTP URL 的路径类似
query?from=share参数,用于传递上下文
fragment#detail锚点,部分平台忽略

命名建议:自定义 Scheme 建议采用反向域名形式(例如 com.example.myapp://),可以有效降低与其他应用的冲突概率,同时便于团队维护。

2.2 Android 注册方式

Android 通过 AndroidManifest.xml 中的 <intent-filter> 声明 Scheme:

<activity android:name=".DeepLinkActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data
            android:scheme="myapp"
            android:host="open"
            android:pathPrefix="/product" />
    </intent-filter>
</activity>
  • android:scheme — 必须,指定 URI 协议名。
  • android:host — 可选,用于二次过滤。
  • android:pathPrefix / android:pathPattern — 可选,匹配路径。
  • android.intent.category.BROWSABLE — 允许来自浏览器的跳转。

2.3 iOS 注册方式

iOS 在 Info.plist 中配置 CFBundleURLTypes:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLName</key>
        <string>com.example.myapp</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>myapp</string>
        </array>
    </dict>
</array>

同时需要在 SceneDelegate (iOS 13+) 或 AppDelegate 中实现处理回调:

// iOS 13+ SceneDelegate
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else { return }
    handleDeepLink(url: url)
}

// iOS 12 及以下 AppDelegate
func application(_ app: UIApplication, open url: URL,
                 options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    handleDeepLink(url: url)
    return true
}

2.4 多 Scheme 冲突处理

当多个应用注册相同 Scheme 时,系统行为因平台而异:

平台默认行为用户选择开发者应对
Android弹出“完成操作方式”选择器可勾选“一律使用”优先使用独特前缀(如域名反转)
iOS仅首个注册 App 生效(无选择器)用户可手动修改系统设置建议使用 Universal Link 替代纯 Scheme
HarmonyOS与 Android 类似,弹出选择器可记忆选择显式声明 priority 属性

常见错误:使用 http/https 作为自定义 Scheme 会导致与系统浏览器冲突,应严格避免。推荐长度不少于 6 字符、包含至少一个点号(如 shop.myapp)。

2.5 安全校验

URI Scheme 的安全校验必须在业务处理前完成,防止恶意 App 构造虚假 URI 劫持或欺骗用户。校验清单:

  1. 来源校验:检查 referrer(Android)或 sourceApplication(iOS)是否为已知可信包
  2. host & path 白名单:只处理预定义的合法 host 和 path
  3. 参数消毒:对 query 参数进行 URL 解码后校验,防止 SQL 注入/XSS
  4. 深度字符限制:限制 URI 总长度(一般 ≤2048 字符)
  5. 签名校验(Android):通过 PackageManager 获取调用者签名,比对白名单

安全校验代码示例(Android):

fun isTrustedSource(context: Context, intent: Intent): Boolean {
    val referrer = intent.getStringExtra("referrer") ?: return false
    val packageName = extractPackageFromReferrer(referrer) ?: return false
    val trustedPackages = listOf("com.example.legitimate")
    if (packageName !in trustedPackages) return false
    val packageManager = context.packageManager
    val signature = packageManager.getPackageInfo(packageName,
        PackageManager.GET_SIGNATURES).signatures[0].toCharsString()
    return signature == EXPECTED_SIGNATURE_HASH
}

2.6 调试技巧

测试 URI Scheme 是否正常注册:

  • Android:执行 adb shell am start -W -a android.intent.action.VIEW -d "myapp://open/product/123",观察日志中 ActivityManager 的输出。
  • iOS:在 Safari 地址栏直接输入 myapp://,检查是否弹出提示“打开此页面”。
  • 通用:在终端使用 open 命令(macOS)或第三方工具 URL Scanner 验证。

2.7 常见错误与稳定性优化

错误场景原因解决方案
URI 无响应(App 未弹出)Scheme 未正确声明或大小写不匹配检查 Manifest/Info.plist;Scheme 强制小写
Android Intent 参数丢失未正确调用 getData() 或 getExtras()使用 intent.data 读取完整 URI
iOS 中从外部调用无回调未实现 SceneDelegate 的 openURLContexts添加对应代理方法,注意 iOS 13+ 的分支
冲突导致弹窗干扰用户与第三方应用共用通用 Scheme升级为唯一反向域名 Scheme;或采用 Android App Links / iOS Universal Link
安全漏洞(任意 App 可打开)缺少签名校验或来源阻断实施第 2.5 节中的完整校验流程

性能优化建议:在 onCreate 或 SceneDelegate 入口处尽快处理深度链接,避免主线程阻塞。将 URI 解析、参数校验放到异步协程/队列中,确保启动耗时低于 100ms。