深度链接技能Skill deep-linking

深度链接技能是移动应用开发的核心技术,用于实现iOS Universal Links、Android App Links和自定义URL方案,支持跨平台深度链接配置、延迟深度链接处理、应用内导航路由和链接验证。该技能涵盖AASA文件配置、assetlinks.json设置、SwiftUI/UIKit/Kotlin/React Native实现、服务器验证和最佳实践,帮助开发者构建无缝的用户体验,实现从网页、邮件、通知到应用内特定页面的直接跳转。

移动开发 0 次安装 3 次浏览 更新于 2/25/2026

name: deep-linking description: 用于实现iOS Universal Links、Android App Links、自定义URL方案以及跨移动平台延迟深度链接的通用链接和深度链接技能。 allowed-tools: Read, Grep, Write, Bash, Edit, Glob, WebFetch

深度链接技能

为iOS和Android提供全面的深度链接实现,包括通用链接、应用链接、自定义URL方案和延迟深度链接。

概述

此技能提供了跨移动平台实现深度链接的能力,使用户能够从外部来源(如网页链接、通知、电子邮件和其他应用)直接导航到您应用内的特定内容。

能力

iOS通用链接

  • 配置apple-app-site-association (AASA) 文件
  • 设置关联域权限
  • 实现NSUserActivity处理
  • 验证通用链接配置
  • 处理回退到App Store

Android应用链接

  • 配置assetlinks.json (数字资产链接)
  • 为应用链接设置意图过滤器
  • 实现深度链接处理
  • 验证应用链接配置
  • 处理回退到Play Store

自定义URL方案

  • 注册自定义URL方案
  • 处理URL方案回调
  • 解析URL参数
  • 实现方案验证
  • 跨应用通信

延迟深度链接

  • 配置Branch.io或Firebase动态链接
  • 处理首次打开归因
  • 通过安装传递深度链接数据
  • 跟踪深度链接转化
  • 实现回退流程

深度链接路由

  • 设计URL结构和路由
  • 实现应用内导航
  • 处理认证要求
  • 管理深度链接状态持久化
  • 跟踪深度链接分析

先决条件

iOS开发

# 在Xcode中启用关联域能力
# Signing & Capabilities > + Capability > Associated Domains

# 添加域名: applinks:example.com

Android开发

// 基本应用链接无需额外依赖
// 对于Firebase动态链接:
dependencies {
    implementation platform('com.google.firebase:firebase-bom:32.7.0')
    implementation 'com.google.firebase:firebase-dynamic-links'
}

// 对于Branch.io:
dependencies {
    implementation 'io.branch.sdk.android:library:5.+'
}

Web服务器要求

# iOS: 在以下位置托管AASA文件
# https://example.com/.well-known/apple-app-site-association

# Android: 在以下位置托管assetlinks.json文件
# https://example.com/.well-known/assetlinks.json

使用模式

Apple应用网站关联 (AASA) 文件

{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "TEAMID.com.example.app",
        "paths": [
          "/products/*",
          "/users/*",
          "/orders/*",
          "NOT /admin/*"
        ]
      }
    ]
  },
  "webcredentials": {
    "apps": ["TEAMID.com.example.app"]
  }
}

iOS通用链接实现 (SwiftUI)

import SwiftUI

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    handleDeepLink(url)
                }
        }
    }

    func handleDeepLink(_ url: URL) {
        guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
              let host = components.host else {
            return
        }

        let path = components.path
        let queryItems = components.queryItems ?? []

        // 基于路径路由
        switch (host, path) {
        case ("example.com", let p) where p.hasPrefix("/products/"):
            let productId = String(p.dropFirst("/products/".count))
            DeepLinkRouter.shared.navigateTo(.product(id: productId))

        case ("example.com", let p) where p.hasPrefix("/users/"):
            let userId = String(p.dropFirst("/users/".count))
            DeepLinkRouter.shared.navigateTo(.profile(userId: userId))

        case ("example.com", "/orders"):
            DeepLinkRouter.shared.navigateTo(.orders)

        default:
            DeepLinkRouter.shared.navigateTo(.home)
        }
    }
}

// 深度链接路由器
class DeepLinkRouter: ObservableObject {
    static let shared = DeepLinkRouter()

    @Published var currentDestination: Destination = .home

    enum Destination: Equatable {
        case home
        case product(id: String)
        case profile(userId: String)
        case orders
    }

    func navigateTo(_ destination: Destination) {
        DispatchQueue.main.async {
            self.currentDestination = destination
        }
    }
}

iOS通用链接 (UIKit)

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
        guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
              let url = userActivity.webpageURL else {
            return
        }

        handleUniversalLink(url)
    }

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let url = URLContexts.first?.url else { return }
        handleCustomScheme(url)
    }

    private func handleUniversalLink(_ url: URL) {
        // 路由到适当的视图控制器
        let router = DeepLinkRouter.shared
        router.route(url: url)
    }

    private func handleCustomScheme(_ url: URL) {
        // 处理myapp://方案
        guard url.scheme == "myapp" else { return }
        let router = DeepLinkRouter.shared
        router.route(url: url)
    }
}

Android assetlinks.json

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.app",
      "sha256_cert_fingerprints": [
        "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
      ]
    }
  }
]

Android应用链接实现 (Kotlin)

// AndroidManifest.xml
/*
<activity android:name=".MainActivity"
    android:exported="true">
    <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="example.com"
              android:pathPrefix="/products" />
        <data android:scheme="https"
              android:host="example.com"
              android:pathPrefix="/users" />
    </intent-filter>

    <!-- 自定义URL方案 -->
    <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" />
    </intent-filter>
</activity>
*/

// MainActivity.kt
class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 处理冷启动时的深度链接
        handleIntent(intent)

        setContent {
            MyApp()
        }
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        // 处理应用已在运行时的深度链接
        handleIntent(intent)
    }

    private fun handleIntent(intent: Intent?) {
        val action = intent?.action
        val data = intent?.data

        if (action == Intent.ACTION_VIEW && data != null) {
            handleDeepLink(data)
        }
    }

    private fun handleDeepLink(uri: Uri) {
        val path = uri.path ?: return
        val host = uri.host

        when {
            path.startsWith("/products/") -> {
                val productId = path.removePrefix("/products/")
                navigateToProduct(productId)
            }
            path.startsWith("/users/") -> {
                val userId = path.removePrefix("/users/")
                navigateToProfile(userId)
            }
            path == "/orders" -> {
                navigateToOrders()
            }
            else -> {
                navigateToHome()
            }
        }
    }
}

Jetpack Compose导航与深度链接

import androidx.navigation.compose.*
import androidx.navigation.navDeepLink

@Composable
fun AppNavigation() {
    val navController = rememberNavController()

    NavHost(navController = navController, startDestination = "home") {
        composable("home") {
            HomeScreen()
        }

        composable(
            route = "product/{productId}",
            deepLinks = listOf(
                navDeepLink {
                    uriPattern = "https://example.com/products/{productId}"
                },
                navDeepLink {
                    uriPattern = "myapp://product/{productId}"
                }
            )
        ) { backStackEntry ->
            val productId = backStackEntry.arguments?.getString("productId")
            ProductScreen(productId = productId)
        }

        composable(
            route = "profile/{userId}",
            deepLinks = listOf(
                navDeepLink {
                    uriPattern = "https://example.com/users/{userId}"
                }
            )
        ) { backStackEntry ->
            val userId = backStackEntry.arguments?.getString("userId")
            ProfileScreen(userId = userId)
        }
    }
}

React Native深度链接

// App.js
import { Linking } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';

const linking = {
  prefixes: ['https://example.com', 'myapp://'],
  config: {
    screens: {
      Home: '',
      Product: 'products/:productId',
      Profile: 'users/:userId',
      Orders: 'orders',
    },
  },
};

function App() {
  return (
    <NavigationContainer linking={linking}>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Product" component={ProductScreen} />
        <Stack.Screen name="Profile" component={ProfileScreen} />
        <Stack.Screen name="Orders" component={OrdersScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

// 手动处理深度链接
useEffect(() => {
  const handleDeepLink = (event) => {
    const url = event.url;
    // 解析并导航
  };

  Linking.addEventListener('url', handleDeepLink);

  // 检查初始URL(冷启动)
  Linking.getInitialURL().then((url) => {
    if (url) {
      handleDeepLink({ url });
    }
  });

  return () => {
    Linking.removeEventListener('url', handleDeepLink);
  };
}, []);

与Babysitter SDK集成

任务定义示例

const deepLinkTask = defineTask({
  name: 'deep-link-setup',
  description: '为移动应用配置深度链接',

  inputs: {
    platform: { type: 'string', required: true, enum: ['ios', 'android', 'both'] },
    domain: { type: 'string', required: true },
    paths: { type: 'array', items: { type: 'string' }, required: true },
    customScheme: { type: 'string' },
    projectPath: { type: 'string', required: true }
  },

  outputs: {
    aasaFile: { type: 'string' },
    assetlinksFile: { type: 'string' },
    appConfiguration: { type: 'object' },
    verificationSteps: { type: 'array' }
  },

  async run(inputs, taskCtx) {
    return {
      kind: 'skill',
      title: `为${inputs.domain}配置深度链接`,
      skill: {
        name: 'deep-linking',
        context: {
          operation: 'configure',
          platform: inputs.platform,
          domain: inputs.domain,
          paths: inputs.paths,
          customScheme: inputs.customScheme,
          projectPath: inputs.projectPath
        }
      },
      io: {
        inputJsonPath: `tasks/${taskCtx.effectId}/input.json`,
        outputJsonPath: `tasks/${taskCtx.effectId}/result.json`
      }
    };
  }
});

验证命令

iOS通用链接验证

# 验证AASA文件
curl -I https://example.com/.well-known/apple-app-site-association

# 检查AASA内容
curl https://example.com/.well-known/apple-app-site-association | jq

# 使用Apple的CDN验证器
curl "https://app-site-association.cdn-apple.com/a/v1/example.com"

# 在设备上测试 (Console.app)
# 通过"swcd"过滤查看通用链接调试信息

Android应用链接验证

# 验证assetlinks.json
curl -I https://example.com/.well-known/assetlinks.json

# 检查内容
curl https://example.com/.well-known/assetlinks.json | jq

# 在设备上验证
adb shell pm get-app-links com.example.app

# 重置验证状态
adb shell pm set-app-links --package com.example.app 0 all
adb shell pm verify-app-links --re-verify com.example.app

最佳实践

  1. 验证服务器配置:确保AASA和assetlinks.json正确提供
  2. 处理所有状态:深度链接应在前台、后台和冷启动时都能工作
  3. 实现回退机制:如果应用未安装,重定向到网页或应用商店
  4. 保护深度链接:在操作前验证深度链接参数
  5. 跟踪分析:记录深度链接来源和转化
  6. 全面测试:部署前测试所有路径和边界情况

参考资料